106 lines
2.8 KiB
Go
106 lines
2.8 KiB
Go
package controllers
|
|
|
|
import (
|
|
"context"
|
|
"crypto/rand"
|
|
"fmt"
|
|
"math/big"
|
|
"time"
|
|
|
|
"doormile/config"
|
|
"doormile/db"
|
|
"doormile/dto"
|
|
"doormile/internal/mail"
|
|
"doormile/utils"
|
|
|
|
"github.com/gofiber/fiber/v2"
|
|
"github.com/redis/go-redis/v9"
|
|
)
|
|
|
|
const (
|
|
otpTTL = 5 * time.Minute
|
|
otpMaxAttempts = 5
|
|
)
|
|
|
|
func generateOtpCode() string {
|
|
n, err := rand.Int(rand.Reader, big.NewInt(1000000))
|
|
if err != nil {
|
|
return "000000"
|
|
}
|
|
return fmt.Sprintf("%06d", n.Int64())
|
|
}
|
|
|
|
func otpKey(email string) string { return fmt.Sprintf("otp:email:%s", email) }
|
|
func otpAttemptsKey(email string) string { return fmt.Sprintf("otp:email:%s:attempts", email) }
|
|
|
|
func SendCustomerEmailOtp(cfg *config.Config) fiber.Handler {
|
|
return func(c *fiber.Ctx) error {
|
|
req := new(dto.SendEmailOtpRequest)
|
|
if err := c.BodyParser(req); err != nil {
|
|
return utils.BadRequest(c, "invalid request body")
|
|
}
|
|
if req.Email == "" {
|
|
return utils.BadRequest(c, "email is required")
|
|
}
|
|
|
|
if db.Rdb == nil {
|
|
return utils.Internal(c, "verification service unavailable")
|
|
}
|
|
|
|
code := generateOtpCode()
|
|
ctx := context.Background()
|
|
|
|
if err := db.Rdb.Set(ctx, otpKey(req.Email), code, otpTTL).Err(); err != nil {
|
|
return utils.Internal(c, "failed to generate verification code")
|
|
}
|
|
db.Rdb.Del(ctx, otpAttemptsKey(req.Email))
|
|
|
|
if err := mail.SendOTPEmail(cfg, req.Email, code); err != nil {
|
|
utils.Warn("failed to send OTP email", "email", req.Email, "error", err)
|
|
return utils.Internal(c, "failed to send verification email")
|
|
}
|
|
|
|
return utils.Message(c, "verification code sent")
|
|
}
|
|
}
|
|
|
|
func VerifyCustomerEmailOtp() fiber.Handler {
|
|
return func(c *fiber.Ctx) error {
|
|
req := new(dto.VerifyEmailOtpRequest)
|
|
if err := c.BodyParser(req); err != nil {
|
|
return utils.BadRequest(c, "invalid request body")
|
|
}
|
|
if req.Email == "" || req.Otp == "" {
|
|
return utils.BadRequest(c, "email and otp are required")
|
|
}
|
|
|
|
if db.Rdb == nil {
|
|
return utils.Internal(c, "verification service unavailable")
|
|
}
|
|
|
|
ctx := context.Background()
|
|
key := otpKey(req.Email)
|
|
|
|
stored, err := db.Rdb.Get(ctx, key).Result()
|
|
if err == redis.Nil {
|
|
return utils.BadRequest(c, "verification code expired or not found, please resend")
|
|
} else if err != nil {
|
|
return utils.Internal(c, "failed to verify code")
|
|
}
|
|
|
|
if stored != req.Otp {
|
|
attemptsKey := otpAttemptsKey(req.Email)
|
|
attempts, _ := db.Rdb.Incr(ctx, attemptsKey).Result()
|
|
db.Rdb.Expire(ctx, attemptsKey, otpTTL)
|
|
if attempts >= otpMaxAttempts {
|
|
db.Rdb.Del(ctx, key, attemptsKey)
|
|
return utils.BadRequest(c, "too many incorrect attempts, please request a new code")
|
|
}
|
|
return utils.Unauthorized(c, "incorrect verification code")
|
|
}
|
|
|
|
db.Rdb.Del(ctx, key, otpAttemptsKey(req.Email))
|
|
return utils.Message(c, "email verified successfully")
|
|
}
|
|
}
|