Skip to content

Commit

Permalink
add: otp verification
Browse files Browse the repository at this point in the history
- otp verification route added
- architecture revamped in some places
- added session service and removed session repo
- removed unused code
  • Loading branch information
moonpatel committed Nov 8, 2024
1 parent e277d20 commit c686c97
Show file tree
Hide file tree
Showing 12 changed files with 216 additions and 107 deletions.
10 changes: 6 additions & 4 deletions internal/app/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ import (
)

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

var (
Expand All @@ -26,7 +27,8 @@ func GetContainer() *Container {
rds := database.NewRedisClient()

userRepo := repositories.NewUserRepository(gormDB)
authService := services.NewAuthService(userRepo, rds)
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),
}
}
41 changes: 36 additions & 5 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 @@ -51,9 +55,36 @@ func (ac *AuthController) SignUp(c *fiber.Ctx) error {
}

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

if err := c.BodyParser(userEmail); err != nil {
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!"})
}
6 changes: 3 additions & 3 deletions internal/database/redis.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import (
var rdb *RedisService

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

func NewRedisClient() *RedisService {
Expand All @@ -24,7 +24,7 @@ func NewRedisClient() *RedisService {
})
ctx := context.Background()

rdb = &RedisService{redisClient: redisClient, ctx: ctx}
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:"lastLogin"`
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:"fName"`
LastName string `gorm:"type:varchar(100);default:null" json:"lName"`
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:"isVerified"`
IsActive bool `gorm:"not null;default:false" json:"isActive"`
}
30 changes: 27 additions & 3 deletions internal/repositories/redis_repository.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,35 @@
package repositories

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

type RedisRepository struct {
rdb *database.RedisService
rds *database.RedisService
}

func NewRedisRepository(rds *database.RedisService) *RedisRepository {
return &RedisRepository{rdb: rds}
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
}

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

// set a key's value with expiration
func (rr *RedisRepository) SetEx(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
}
32 changes: 27 additions & 5 deletions internal/services/auth_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,23 @@ package services

import (
"fmt"
"time"

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

"keizer-auth-api/internal/database"
"keizer-auth-api/internal/models"
"keizer-auth-api/internal/repositories"
"keizer-auth-api/internal/utils"
"keizer-auth-api/internal/validators"
)

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

func NewAuthService(userRepo *repositories.UserRepository, rds *database.RedisService) *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 @@ -30,6 +32,11 @@ func (as *AuthService) RegisterUser(userRegister *validators.SignUpUser) error {
return fmt.Errorf("failed to generate OTP: %w", err)
}

err = as.redisRepo.SetEx("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 @@ -46,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
}
79 changes: 79 additions & 0 deletions internal/services/session_service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package services

import (
"encoding/json"
"fmt"
"keizer-auth-api/internal/models"
"keizer-auth-api/internal/repositories"
"keizer-auth-api/internal/utils"
"time"

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

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

func NewSessionService(redisRepo *repositories.RedisRepository) *SessionService {
return &SessionService{redisRepo: redisRepo}
}

func (ss *SessionService) CreateSession(uuid uuid.UUID) (string, error) {
sessionId, err := utils.GenerateSessionID()
if err != nil {
return "", fmt.Errorf("error in generating session %w", err)
}

var user *models.User
user, err = ss.userRepo.GetUser(uuid)
if err != nil {
return "", fmt.Errorf("error in getting user %w", err)
}
userJson, err := json.Marshal(user)
if err != nil {
return "", fmt.Errorf("error occured %w", err)
}

err = ss.redisRepo.SetEx("dashboard-user-session-"+sessionId, string(userJson), utils.SessionExpiresIn)
if err != nil {
return "", fmt.Errorf("error in setting session %w", err)
}

return sessionId, nil
}

func (ss *SessionService) GetSession(sessionId string) (*models.User, error) {
userSession, err := ss.redisRepo.Get("dashboard-user-session-" + sessionId)
if err != nil {
return nil, fmt.Errorf("no session found")
}
var userData *models.User
err = json.Unmarshal([]byte(userSession), userData)
if err != nil {
return nil, fmt.Errorf("error in unmarshalling")
}
return userData, nil
}

func (ss *SessionService) UpdateSession(sessionId string) error {
val, err := ss.redisRepo.Get("dashboard-user-session-" + sessionId)
if err != nil {
if err == redis.Nil {
return fmt.Errorf("session not found")
}
return err
}
err = ss.redisRepo.SetEx("dashboard-user-session-"+sessionId, val, utils.SessionExpiresIn)
if err != nil {
return fmt.Errorf("error in updating session %w", err)
}
return nil
}

func (ss *SessionService) TTL(sessionId string) (time.Duration, error) {
ttl, err := ss.redisRepo.TTL("dashboard-user-session-" + sessionId)
return ttl, err
}
Loading

0 comments on commit c686c97

Please sign in to comment.