Skip to content

Commit

Permalink
Merge pull request #24 from moonpatel/v-otp
Browse files Browse the repository at this point in the history
otp verification and some architectural changes
  • Loading branch information
Sudarsh1010 authored Nov 9, 2024
2 parents c80a30e + f34d23a commit 6cf40e4
Show file tree
Hide file tree
Showing 17 changed files with 312 additions and 97 deletions.
12 changes: 12 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,15 @@ DB_SCHEMA=public
MINIO_ROOT_USER=minioadmin
MINIO_ROOT_PASSWORD=minioadmin
MINIO_PORT=9000

# Redis
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=password

# STMP
SMTP_HOST=
SMTP_PORT=
EMAIL_FROM=
SMPT_USER=
SMPT_PASS=
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@ require (
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/andybalholm/brotli v1.1.1 // indirect
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/containerd/containerd v1.7.18 // indirect
github.com/containerd/log v0.1.0 // indirect
github.com/containerd/platforms v0.2.1 // indirect
github.com/cpuguy83/dockercfg v0.3.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/distribution/reference v0.6.0 // indirect
github.com/docker/docker v27.1.1+incompatible // indirect
github.com/docker/go-connections v0.5.0 // indirect
Expand Down Expand Up @@ -56,6 +58,7 @@ require (
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/redis/go-redis/v9 v9.7.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/shirou/gopsutil/v3 v3.23.12 // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect
Expand Down
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7X
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao=
github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4=
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
Expand All @@ -23,6 +25,8 @@ github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/docker v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY=
Expand Down Expand Up @@ -117,6 +121,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E=
github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
Expand Down
9 changes: 6 additions & 3 deletions internal/app/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ import (
)

type Container struct {
DB database.Service
AuthService *services.AuthService
DB database.Service
AuthService *services.AuthService
SessionService *services.SessionService
}

var (
Expand All @@ -22,9 +23,11 @@ func GetContainer() *Container {
once.Do(func() {
db := database.New()
gormDB := database.GetDB()
rds := database.NewRedisClient()

userRepo := repositories.NewUserRepository(gormDB)
authService := services.NewAuthService(userRepo)
redisRepo := repositories.NewRedisRepository(rds)
authService := services.NewAuthService(userRepo, redisRepo)

container = &Container{
DB: db,
Expand Down
2 changes: 1 addition & 1 deletion internal/app/controllers.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ type ServerControllers struct {

func GetControllers(container *Container) *ServerControllers {
return &ServerControllers{
Auth: controllers.NewAuthController(container.AuthService),
Auth: controllers.NewAuthController(container.AuthService, container.SessionService),
}
}
45 changes: 42 additions & 3 deletions internal/controllers/auth_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,24 @@ package controllers

import (
"errors"
"fmt"

"keizer-auth-api/internal/services"
"keizer-auth-api/internal/utils"
"keizer-auth-api/internal/validators"

"github.com/gofiber/fiber/v2"
"github.com/google/uuid"
"gorm.io/gorm"
)

type AuthController struct {
authService *services.AuthService
authService *services.AuthService
sessionService *services.SessionService
}

func NewAuthController(as *services.AuthService) *AuthController {
return &AuthController{authService: as}
func NewAuthController(as *services.AuthService, ss *services.SessionService) *AuthController {
return &AuthController{authService: as, sessionService: ss}
}

func (ac *AuthController) SignIn(c *fiber.Ctx) error {
Expand Down Expand Up @@ -49,3 +53,38 @@ func (ac *AuthController) SignUp(c *fiber.Ctx) error {

return c.JSON(fiber.Map{"message": "User Signed Up!"})
}

func (ac *AuthController) VerifyOTP(c *fiber.Ctx) error {
verifyOtpBody := new(validators.VerifyOTP)

if err := c.BodyParser(verifyOtpBody); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request body"})
}

isOtpValid, err := ac.authService.VerifyOTP(verifyOtpBody)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "OTP not found"})
}
if err.Error() == "otp expired" {
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": "OTP expired"})
}

return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to verify OTP"})
}
if !isOtpValid {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "OTP not valid"})
}

parsedUuid, err := uuid.Parse(verifyOtpBody.Id)
if err != nil {
return fmt.Errorf("error parsing uuid %w", err)
}
sessionId, err := ac.sessionService.CreateSession(parsedUuid)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to create session"})
}
utils.SetSessionCookie(c, sessionId)

return c.JSON(fiber.Map{"message": "OTP Verified!"})
}
30 changes: 30 additions & 0 deletions internal/database/redis.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package database

import (
"context"
"os"

"github.com/redis/go-redis/v9"
)

var rdb *RedisService

type RedisService struct {
RedisClient *redis.Client
Ctx context.Context
}

func NewRedisClient() *RedisService {
if rdb != nil {
return rdb
}
redisClient := redis.NewClient(&redis.Options{
Addr: os.Getenv("REDIS_HOST") + ":" + os.Getenv("REDIS_PORT"),
DB: 0,
})
ctx := context.Background()

rdb = &RedisService{RedisClient: redisClient, Ctx: ctx}

return rdb
}
14 changes: 7 additions & 7 deletions internal/models/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ package models
import "time"

type User struct {
LastLogin time.Time
Email string `gorm:"not null;default:null;unique,index;type:varchar(100)"`
PasswordHash string
FirstName string `gorm:"not null;type:varchar(100);default:null"`
LastName string `gorm:"type:varchar(100);default:null"`
LastLogin time.Time `json:"last_login"`
Email string `gorm:"not null;default:null;unique,index;type:varchar(100)" json:"email"`
PasswordHash string `json:"-"`
FirstName string `gorm:"not null;type:varchar(100);default:null" json:"first_name"`
LastName string `gorm:"type:varchar(100);default:null" json:"last_name"`
Base
Sessions []Session
IsVerified bool `gorm:"not null;default:false"`
IsActive bool `gorm:"not null;default:false"`
IsVerified bool `gorm:"not null;default:false" json:"is_verified"`
IsActive bool `gorm:"not null;default:false" json:"is_active"`
}
30 changes: 30 additions & 0 deletions internal/repositories/redis_repository.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package repositories

import (
"keizer-auth-api/internal/database"
"time"
)

type RedisRepository struct {
rds *database.RedisService
}

func NewRedisRepository(rds *database.RedisService) *RedisRepository {
return &RedisRepository{rds: rds}
}

func (rr *RedisRepository) Get(key string) (string, error) {
value, err := rr.rds.RedisClient.Get(rr.rds.Ctx, key).Result()
return value, err
}

// set a key's value with expiration
func (rr *RedisRepository) Set(key string, value string, expiration time.Duration) error {
err := rr.rds.RedisClient.Set(rr.rds.Ctx, key, value, expiration).Err()
return err
}

func (rr *RedisRepository) TTL(key string) (time.Duration, error) {
result, err := rr.rds.RedisClient.TTL(rr.rds.Ctx, key).Result()
return result, err
}
51 changes: 0 additions & 51 deletions internal/repositories/session_repository.go

This file was deleted.

15 changes: 15 additions & 0 deletions internal/repositories/user_repository.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package repositories

import (
"fmt"
"keizer-auth-api/internal/models"

"github.com/google/uuid"
"gorm.io/gorm"
)

Expand All @@ -17,3 +19,16 @@ func NewUserRepository(db *gorm.DB) *UserRepository {
func (r *UserRepository) CreateUser(user *models.User) error {
return r.db.Create(user).Error
}

func (r *UserRepository) GetUser(uuid uuid.UUID) (*models.User, error) {
var user models.User
result := r.db.First(&user, uuid.String())
if result.Error != nil {
if result.Error == gorm.ErrRecordNotFound {
return nil, fmt.Errorf("user not found")
}
return nil, fmt.Errorf("error in getting user: %w", result.Error)
}

return &user, nil
}
1 change: 1 addition & 0 deletions internal/server/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ func (s *FiberServer) RegisterFiberRoutes() {
auth := api.Group("/auth")
auth.Post("/sign-up", s.controllers.Auth.SignUp)
auth.Post("/sign-in", s.controllers.Auth.SignIn)
auth.Post("/verify-otp", s.controllers.Auth.VerifyOTP)

s.Static("/", "./web/dist")
s.Static("*", "./web/dist/index.html")
Expand Down
30 changes: 27 additions & 3 deletions internal/services/auth_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ package services

import (
"fmt"
"time"

"github.com/redis/go-redis/v9"

"keizer-auth-api/internal/models"
"keizer-auth-api/internal/repositories"
Expand All @@ -10,11 +13,12 @@ import (
)

type AuthService struct {
userRepo *repositories.UserRepository
userRepo *repositories.UserRepository
redisRepo *repositories.RedisRepository
}

func NewAuthService(userRepo *repositories.UserRepository) *AuthService {
return &AuthService{userRepo: userRepo}
func NewAuthService(userRepo *repositories.UserRepository, redisRepo *repositories.RedisRepository) *AuthService {
return &AuthService{userRepo: userRepo, redisRepo: redisRepo}
}

func (as *AuthService) RegisterUser(userRegister *validators.SignUpUser) error {
Expand All @@ -28,6 +32,11 @@ func (as *AuthService) RegisterUser(userRegister *validators.SignUpUser) error {
return fmt.Errorf("failed to generate OTP: %w", err)
}

err = as.redisRepo.Set("registration-verification-otp-"+userRegister.Email, otp, time.Minute)
if err != nil {
return fmt.Errorf("failed to save otp in redis: %w", err)
}

// TODO: email should be sent using async func
if err = SendOTPEmail(userRegister.Email, otp); err != nil {
return fmt.Errorf("failed to send OTP email: %w", err)
Expand All @@ -44,3 +53,18 @@ func (as *AuthService) RegisterUser(userRegister *validators.SignUpUser) error {

return nil
}

func (as *AuthService) VerifyOTP(verifyOtpBody *validators.VerifyOTP) (bool, error) {
val, err := as.redisRepo.Get(verifyOtpBody.Email)
if err != nil {
if err == redis.Nil {
return false, fmt.Errorf("otp expired")
}
return false, fmt.Errorf("failed to get otp from redis %w", err)
}

if val != verifyOtpBody.Otp {
return false, nil
}
return true, nil
}
Loading

0 comments on commit 6cf40e4

Please sign in to comment.