UPDATE: Outh2 google
This commit is contained in:
@@ -6,17 +6,22 @@ import (
|
||||
"history-api/internal/dtos/response"
|
||||
"history-api/internal/services"
|
||||
"history-api/pkg/validator"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v3"
|
||||
"github.com/google/uuid"
|
||||
"golang.org/x/oauth2"
|
||||
"google.golang.org/api/idtoken"
|
||||
)
|
||||
|
||||
type AuthController struct {
|
||||
service services.AuthService
|
||||
oauth *oauth2.Config
|
||||
}
|
||||
|
||||
func NewAuthController(svc services.AuthService) *AuthController {
|
||||
return &AuthController{service: svc}
|
||||
func NewAuthController(svc services.AuthService, oauth *oauth2.Config) *AuthController {
|
||||
return &AuthController{service: svc, oauth: oauth}
|
||||
}
|
||||
|
||||
// Signin godoc
|
||||
@@ -50,6 +55,21 @@ func (h *AuthController) Signin(c fiber.Ctx) error {
|
||||
Message: err.Error(),
|
||||
})
|
||||
}
|
||||
c.Cookie(&fiber.Cookie{
|
||||
Name: "access_token",
|
||||
Value: res.AccessToken,
|
||||
HTTPOnly: true,
|
||||
Secure: c.Protocol() == "https",
|
||||
SameSite: "Lax",
|
||||
})
|
||||
|
||||
c.Cookie(&fiber.Cookie{
|
||||
Name: "refresh_token",
|
||||
Value: res.RefreshToken,
|
||||
HTTPOnly: true,
|
||||
Secure: c.Protocol() == "https",
|
||||
SameSite: "Lax",
|
||||
})
|
||||
|
||||
return c.Status(fiber.StatusOK).JSON(response.CommonResponse{
|
||||
Status: true,
|
||||
@@ -88,6 +108,22 @@ func (h *AuthController) Signup(c fiber.Ctx) error {
|
||||
})
|
||||
}
|
||||
|
||||
c.Cookie(&fiber.Cookie{
|
||||
Name: "access_token",
|
||||
Value: res.AccessToken,
|
||||
HTTPOnly: true,
|
||||
Secure: c.Protocol() == "https",
|
||||
SameSite: "Lax",
|
||||
})
|
||||
|
||||
c.Cookie(&fiber.Cookie{
|
||||
Name: "refresh_token",
|
||||
Value: res.RefreshToken,
|
||||
HTTPOnly: true,
|
||||
Secure: c.Protocol() == "https",
|
||||
SameSite: "Lax",
|
||||
})
|
||||
|
||||
return c.Status(fiber.StatusOK).JSON(response.CommonResponse{
|
||||
Status: true,
|
||||
Data: res,
|
||||
@@ -117,6 +153,22 @@ func (h *AuthController) RefreshToken(c fiber.Ctx) error {
|
||||
})
|
||||
}
|
||||
|
||||
c.Cookie(&fiber.Cookie{
|
||||
Name: "access_token",
|
||||
Value: res.AccessToken,
|
||||
HTTPOnly: true,
|
||||
Secure: c.Protocol() == "https",
|
||||
SameSite: "Lax",
|
||||
})
|
||||
|
||||
c.Cookie(&fiber.Cookie{
|
||||
Name: "refresh_token",
|
||||
Value: res.RefreshToken,
|
||||
HTTPOnly: true,
|
||||
Secure: c.Protocol() == "https",
|
||||
SameSite: "Lax",
|
||||
})
|
||||
|
||||
return c.Status(fiber.StatusOK).JSON(response.CommonResponse{
|
||||
Status: true,
|
||||
Data: res,
|
||||
@@ -193,8 +245,7 @@ func (h *AuthController) CreateToken(c fiber.Ctx) error {
|
||||
|
||||
return c.Status(fiber.StatusOK).JSON(response.CommonResponse{
|
||||
Status: true,
|
||||
Data: nil,
|
||||
Message: "Token created successfully",
|
||||
Message: "If this email exists, an OTP has been sent",
|
||||
})
|
||||
}
|
||||
|
||||
@@ -234,4 +285,106 @@ func (h *AuthController) ForgotPassword(c fiber.Ctx) error {
|
||||
Data: nil,
|
||||
Message: "Password reset successfully",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// GoogleLogin godoc
|
||||
// @Summary Initiate Google OAuth2 login
|
||||
// @Description Generates a state string, sets it in a cookie, and redirects the user to Google's consent page.
|
||||
// @Tags Auth
|
||||
// @Success 302 {string} string "Redirect to Google"
|
||||
// @Router /auth/google/login [get]
|
||||
func (h *AuthController) GoogleLogin(c fiber.Ctx) error {
|
||||
_, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
state := uuid.New().String()
|
||||
|
||||
secure := c.Protocol() == "https"
|
||||
|
||||
c.Cookie(&fiber.Cookie{
|
||||
Name: "oauth_state",
|
||||
Value: state,
|
||||
Expires: time.Now().Add(15 * time.Minute),
|
||||
HTTPOnly: true,
|
||||
Secure: secure,
|
||||
SameSite: "Lax",
|
||||
})
|
||||
|
||||
url := h.oauth.AuthCodeURL(state)
|
||||
return c.Redirect().To(url)
|
||||
}
|
||||
|
||||
// GoogleCallback godoc
|
||||
// @Summary Handle Google OAuth2 callback
|
||||
// @Description Receives the auth code from Google, exchanges it for tokens, creates/logs in the user, and redirects back to the frontend with application tokens.
|
||||
// @Tags Auth
|
||||
// @Param state query string true "Security state string"
|
||||
// @Param code query string true "Authorization code from Google"
|
||||
// @Success 302 {string} string "Redirect to Frontend with JWTs"
|
||||
// @Failure 401 {object} response.CommonResponse "Invalid state"
|
||||
// @Failure 500 {object} response.CommonResponse "Internal Server Error"
|
||||
// @Router /auth/google/callback [get]
|
||||
func (h *AuthController) GoogleCallback(c fiber.Ctx) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
stateFromGoogle := c.Query("state")
|
||||
stateFromCookie := c.Cookies("oauth_state")
|
||||
|
||||
if stateFromGoogle == "" || stateFromGoogle != stateFromCookie {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Invalid state"})
|
||||
}
|
||||
c.ClearCookie("oauth_state")
|
||||
|
||||
code := c.Query("code")
|
||||
token, err := h.oauth.Exchange(context.Background(), code)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Token exchange failed"})
|
||||
}
|
||||
|
||||
idToken, ok := token.Extra("id_token").(string)
|
||||
if !ok {
|
||||
return c.Status(500).JSON(fiber.Map{"error": "No id_token"})
|
||||
}
|
||||
|
||||
parts := strings.Split(idToken, ".")
|
||||
if len(parts) < 2 {
|
||||
return c.Status(500).JSON(fiber.Map{"error": "Invalid id_token"})
|
||||
}
|
||||
|
||||
payload, err := idtoken.Validate(ctx, idToken, h.oauth.ClientID)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Token verification failed"})
|
||||
}
|
||||
|
||||
googleUser := request.SigninWithGoogleDto{
|
||||
Sub: payload.Subject,
|
||||
Email: payload.Claims["email"].(string),
|
||||
Name: payload.Claims["name"].(string),
|
||||
Picture: payload.Claims["picture"].(string),
|
||||
}
|
||||
|
||||
res, err := h.service.SigninWithGoogle(ctx, &googleUser)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(response.CommonResponse{
|
||||
Status: false,
|
||||
Message: err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
c.Cookie(&fiber.Cookie{
|
||||
Name: "access_token",
|
||||
Value: res.AccessToken,
|
||||
HTTPOnly: true,
|
||||
Secure: c.Protocol() == "https",
|
||||
SameSite: "Lax",
|
||||
})
|
||||
|
||||
c.Cookie(&fiber.Cookie{
|
||||
Name: "refresh_token",
|
||||
Value: res.RefreshToken,
|
||||
HTTPOnly: true,
|
||||
Secure: c.Protocol() == "https",
|
||||
SameSite: "Lax",
|
||||
})
|
||||
|
||||
return c.Redirect().To("http://localhost:5500")
|
||||
}
|
||||
|
||||
@@ -209,8 +209,8 @@ func (h *UserController) ChangeRoleUser(c fiber.Ctx) error {
|
||||
})
|
||||
}
|
||||
return c.Status(fiber.StatusOK).JSON(response.CommonResponse{
|
||||
Status: true,
|
||||
Data: user,
|
||||
Status: true,
|
||||
Data: user,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -273,4 +273,4 @@ func (h *UserController) Search(c fiber.Ctx) error {
|
||||
})
|
||||
}
|
||||
return c.Status(fiber.StatusOK).JSON(res)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,9 @@ type ForgotPasswordDto struct {
|
||||
NewPassword string `json:"new_password" validate:"required,min=8,max=64"`
|
||||
}
|
||||
|
||||
type SigninWith3rdDto struct {
|
||||
Provider string `json:"provider" validate:"required,oneof=google github facebook"`
|
||||
AccessToken string `json:"access_token" validate:"required"`
|
||||
type SigninWithGoogleDto struct {
|
||||
Sub string `json:"sub"` // GoogleID
|
||||
Email string `json:"email"`
|
||||
Name string `json:"name"`
|
||||
Picture string `json:"picture"`
|
||||
}
|
||||
|
||||
@@ -13,9 +13,9 @@ type CommonResponse struct {
|
||||
}
|
||||
|
||||
type JWTClaims struct {
|
||||
UId string `json:"uid"`
|
||||
Roles []constants.Role `json:"roles"`
|
||||
TokenVersion int32 `json:"token_version"`
|
||||
UId string `json:"uid"`
|
||||
Roles []constants.Role `json:"roles"`
|
||||
TokenVersion int32 `json:"token_version"`
|
||||
jwt.RegisteredClaims
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ type UserResponse struct {
|
||||
ID string `json:"id"`
|
||||
Email string `json:"email"`
|
||||
Profile *UserProfileSimpleResponse `json:"profile"`
|
||||
IsVerified bool `json:"is_verified"`
|
||||
TokenVersion int32 `json:"token_version"`
|
||||
IsDeleted bool `json:"is_deleted"`
|
||||
CreatedAt *time.Time `json:"created_at"`
|
||||
|
||||
@@ -23,8 +23,11 @@ func JwtAccess(userRepo repositories.UserRepository) fiber.Handler {
|
||||
SigningKey: jwtware.SigningKey{Key: []byte(jwtSecret)},
|
||||
ErrorHandler: jwtError,
|
||||
SuccessHandler: jwtSuccess(userRepo),
|
||||
Extractor: extractors.FromAuthHeader("Bearer"),
|
||||
Claims: &response.JWTClaims{},
|
||||
Extractor: extractors.Chain(
|
||||
extractors.FromAuthHeader("Bearer"),
|
||||
extractors.FromCookie("access_token"),
|
||||
),
|
||||
Claims: &response.JWTClaims{},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -38,15 +41,16 @@ func JwtRefresh(userRepo repositories.UserRepository) fiber.Handler {
|
||||
SigningKey: jwtware.SigningKey{Key: []byte(jwtRefreshSecret)},
|
||||
ErrorHandler: jwtError,
|
||||
SuccessHandler: jwtSuccess(userRepo),
|
||||
Extractor: extractors.FromAuthHeader("Bearer"),
|
||||
Claims: &response.JWTClaims{},
|
||||
Extractor: extractors.Chain(
|
||||
extractors.FromAuthHeader("Bearer"),
|
||||
extractors.FromCookie("refresh_token"),
|
||||
),
|
||||
Claims: &response.JWTClaims{},
|
||||
})
|
||||
}
|
||||
|
||||
func jwtSuccess(userRepo repositories.UserRepository) fiber.Handler {
|
||||
return func(c fiber.Ctx) error {
|
||||
user := jwtware.FromContext(c)
|
||||
|
||||
unauthorized := func() error {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(response.CommonResponse{
|
||||
Status: false,
|
||||
@@ -54,11 +58,12 @@ func jwtSuccess(userRepo repositories.UserRepository) fiber.Handler {
|
||||
})
|
||||
}
|
||||
|
||||
if user == nil {
|
||||
jwtToken := jwtware.FromContext(c)
|
||||
if jwtToken == nil {
|
||||
return unauthorized()
|
||||
}
|
||||
|
||||
claims, ok := user.Claims.(*response.JWTClaims)
|
||||
claims, ok := jwtToken.Claims.(*response.JWTClaims)
|
||||
if !ok {
|
||||
return unauthorized()
|
||||
}
|
||||
@@ -71,10 +76,12 @@ func jwtSuccess(userRepo repositories.UserRepository) fiber.Handler {
|
||||
}
|
||||
|
||||
var pgID pgtype.UUID
|
||||
|
||||
err := pgID.Scan(claims.UId)
|
||||
if err != nil {
|
||||
return unauthorized()
|
||||
}
|
||||
|
||||
tokenVersion, err := userRepo.GetTokenVersion(c.Context(), pgID)
|
||||
if err != nil {
|
||||
return unauthorized()
|
||||
@@ -89,10 +96,10 @@ func jwtSuccess(userRepo repositories.UserRepository) fiber.Handler {
|
||||
|
||||
c.Locals("uid", claims.UId)
|
||||
c.Locals("user_claims", claims)
|
||||
|
||||
return c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func jwtError(c fiber.Ctx, err error) error {
|
||||
if err.Error() == "Missing or malformed JWT" {
|
||||
return c.Status(fiber.StatusBadRequest).
|
||||
|
||||
@@ -11,7 +11,6 @@ type UserEntity struct {
|
||||
Email string `json:"email"`
|
||||
PasswordHash string `json:"password_hash"`
|
||||
Profile *UserProfileSimple `json:"profile"`
|
||||
IsVerified bool `json:"is_verified"`
|
||||
TokenVersion int32 `json:"token_version"`
|
||||
GoogleID string `json:"google_id"`
|
||||
AuthProvider string `json:"auth_provider"`
|
||||
@@ -42,7 +41,6 @@ func (u *UserEntity) ToResponse() *response.UserResponse {
|
||||
return &response.UserResponse{
|
||||
ID: u.ID,
|
||||
Email: u.Email,
|
||||
IsVerified: u.IsVerified,
|
||||
TokenVersion: u.TokenVersion,
|
||||
IsDeleted: u.IsDeleted,
|
||||
CreatedAt: u.CreatedAt,
|
||||
|
||||
@@ -16,4 +16,6 @@ func AuthRoutes(app *fiber.App, controller *controllers.AuthController, userRepo
|
||||
route.Post("/token/create", controller.CreateToken)
|
||||
route.Post("/token/verify", controller.VerifyToken)
|
||||
route.Post("/forgot-password", controller.ForgotPassword)
|
||||
route.Get("/google/login", controller.GoogleLogin)
|
||||
route.Get("/google/callback", controller.GoogleCallback)
|
||||
}
|
||||
|
||||
@@ -18,6 +18,12 @@ func UserRoutes(app *fiber.App, controller *controllers.UserController, userRepo
|
||||
middlewares.RequireAnyRole(constants.ADMIN, constants.MOD),
|
||||
controller.Search,
|
||||
)
|
||||
|
||||
route.Get(
|
||||
"/current",
|
||||
middlewares.JwtAccess(userRepo),
|
||||
controller.GetUserCurrent,
|
||||
)
|
||||
route.Get(
|
||||
"/:id",
|
||||
middlewares.JwtAccess(userRepo),
|
||||
@@ -25,6 +31,12 @@ func UserRoutes(app *fiber.App, controller *controllers.UserController, userRepo
|
||||
controller.Search,
|
||||
)
|
||||
|
||||
route.Put(
|
||||
"/:id",
|
||||
middlewares.JwtAccess(userRepo),
|
||||
controller.UpdateProfile,
|
||||
)
|
||||
|
||||
route.Delete(
|
||||
"/:id",
|
||||
middlewares.JwtAccess(userRepo),
|
||||
@@ -50,16 +62,4 @@ func UserRoutes(app *fiber.App, controller *controllers.UserController, userRepo
|
||||
middlewares.JwtAccess(userRepo),
|
||||
controller.ChangePassword,
|
||||
)
|
||||
|
||||
route.Get(
|
||||
"/current",
|
||||
middlewares.JwtAccess(userRepo),
|
||||
controller.GetUserCurrent,
|
||||
)
|
||||
|
||||
route.Put(
|
||||
"/:id",
|
||||
middlewares.JwtAccess(userRepo),
|
||||
controller.UpdateProfile,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,11 @@ package services
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"crypto/subtle"
|
||||
"database/sql"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"history-api/internal/dtos/request"
|
||||
"history-api/internal/dtos/response"
|
||||
@@ -31,7 +36,7 @@ type AuthService interface {
|
||||
ForgotPassword(ctx context.Context, dto *request.ForgotPasswordDto) error
|
||||
VerifyToken(ctx context.Context, dto *request.VerifyTokenDto) (*response.VerifyTokenResponse, error)
|
||||
CreateToken(ctx context.Context, dto *request.CreateTokenDto) error
|
||||
SigninWith3rd(ctx context.Context, dto *request.SigninWith3rdDto) error
|
||||
SigninWithGoogle(ctx context.Context, dto *request.SigninWithGoogleDto) (*response.AuthResponse, error)
|
||||
RefreshToken(ctx context.Context, id string) (*response.AuthResponse, error)
|
||||
}
|
||||
|
||||
@@ -130,6 +135,10 @@ func (a *authService) Signin(ctx context.Context, dto *request.SignInDto) (*resp
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
if user.AuthProvider != constants.LocalProvider.String() && user.PasswordHash == "" {
|
||||
return nil, fiber.NewError(fiber.StatusUnauthorized, "Please sign in with "+user.AuthProvider)
|
||||
}
|
||||
|
||||
if err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(dto.Password)); err != nil {
|
||||
return nil, fiber.NewError(fiber.StatusUnauthorized, "Invalid identity or password!")
|
||||
}
|
||||
@@ -341,10 +350,113 @@ func (a *authService) ForgotPassword(ctx context.Context, dto *request.ForgotPas
|
||||
return nil
|
||||
}
|
||||
|
||||
// SigninWith3rd implements [AuthService].
|
||||
func (a *authService) SigninWith3rd(ctx context.Context, dto *request.SigninWith3rdDto) error {
|
||||
panic("unimplemented")
|
||||
func (a *authService) SigninWithGoogle(ctx context.Context, dto *request.SigninWithGoogleDto) (*response.AuthResponse, error) {
|
||||
user, err := a.userRepo.GetByEmail(ctx, dto.Email)
|
||||
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
if user != nil {
|
||||
userId, err := convert.StringToUUID(user.ID)
|
||||
if err != nil {
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
data, err := a.genToken(user)
|
||||
if err != nil {
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
err = a.saveNewRefreshToken(
|
||||
ctx,
|
||||
sqlc.UpdateUserRefreshTokenParams{
|
||||
ID: userId,
|
||||
RefreshToken: pgtype.Text{
|
||||
String: data.RefreshToken,
|
||||
Valid: data.RefreshToken != "",
|
||||
},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
user, err = a.userRepo.UpsertUser(
|
||||
ctx,
|
||||
sqlc.UpsertUserParams{
|
||||
Email: dto.Email,
|
||||
AuthProvider: constants.GoogleProvider.String(),
|
||||
GoogleID: pgtype.Text{
|
||||
String: dto.Sub,
|
||||
Valid: dto.Sub != "",
|
||||
},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
userId, err := convert.StringToUUID(user.ID)
|
||||
if err != nil {
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
_, err = a.userRepo.CreateProfile(
|
||||
ctx,
|
||||
sqlc.CreateUserProfileParams{
|
||||
UserID: userId,
|
||||
DisplayName: pgtype.Text{
|
||||
String: dto.Name,
|
||||
Valid: dto.Name != "",
|
||||
},
|
||||
AvatarUrl: pgtype.Text{
|
||||
String: dto.Picture,
|
||||
Valid: dto.Picture != "",
|
||||
},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
role, err := a.roleRepo.GetByname(ctx, constants.USER.String())
|
||||
if err != nil {
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
roleId, err := convert.StringToUUID(role.ID)
|
||||
if err != nil {
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
err = a.roleRepo.AddUserRole(
|
||||
ctx,
|
||||
sqlc.AddUserRoleParams{
|
||||
UserID: userId,
|
||||
Column2: []pgtype.UUID{roleId},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
data, err := a.genToken(user)
|
||||
if err != nil {
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
err = a.saveNewRefreshToken(
|
||||
ctx,
|
||||
sqlc.UpdateUserRefreshTokenParams{
|
||||
ID: userId,
|
||||
RefreshToken: pgtype.Text{
|
||||
String: data.RefreshToken,
|
||||
Valid: data.RefreshToken != "",
|
||||
},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (a *authService) GenerateOTP() (string, error) {
|
||||
max := big.NewInt(900000)
|
||||
n, err := rand.Int(rand.Reader, max)
|
||||
@@ -358,46 +470,86 @@ func (a *authService) GenerateOTP() (string, error) {
|
||||
func (a *authService) CreateToken(ctx context.Context, dto *request.CreateTokenDto) error {
|
||||
ok, err := a.tokenRepo.CheckCooldown(ctx, dto.Email, dto.TokenType)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Internal Server Error")
|
||||
}
|
||||
|
||||
if ok {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Please wait before requesting another token")
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Too many requests. Please try again later.")
|
||||
}
|
||||
|
||||
otp, err := a.GenerateOTP()
|
||||
user, err := a.userRepo.GetByEmail(ctx, dto.Email)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Internal Server Error")
|
||||
}
|
||||
|
||||
token := &models.TokenEntity{
|
||||
Email: dto.Email,
|
||||
Token: otp,
|
||||
TokenType: dto.TokenType,
|
||||
shouldSend := true
|
||||
if (dto.TokenType == constants.TokenEmailVerify && user != nil) ||
|
||||
(dto.TokenType == constants.TokenPasswordReset && user == nil) {
|
||||
shouldSend = false
|
||||
}
|
||||
|
||||
err = a.tokenRepo.Create(ctx, token)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
if shouldSend {
|
||||
otp, err := a.GenerateOTP()
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Internal Server Error")
|
||||
}
|
||||
hash := sha256.Sum256([]byte(otp))
|
||||
hashString := hex.EncodeToString(hash[:])
|
||||
token := &models.TokenEntity{
|
||||
Email: dto.Email,
|
||||
Token: hashString,
|
||||
TokenType: dto.TokenType,
|
||||
}
|
||||
err = a.tokenRepo.Create(ctx, token)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Internal Server Error")
|
||||
}
|
||||
|
||||
token.Token = otp
|
||||
a.c.PublishTask(ctx, constants.StreamEmailName, constants.TaskTypeSendEmailOTP, token)
|
||||
}
|
||||
|
||||
a.c.PublishTask(ctx, constants.StreamEmailName, constants.TaskTypeSendEmailOTP, token)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *authService) VerifyToken(ctx context.Context, dto *request.VerifyTokenDto) (*response.VerifyTokenResponse, error) {
|
||||
genericError := fiber.NewError(fiber.StatusBadRequest, "Invalid or expired token")
|
||||
token, err := a.tokenRepo.Get(ctx, dto.Email, dto.TokenType)
|
||||
if err != nil || token == nil {
|
||||
return nil, genericError
|
||||
}
|
||||
|
||||
userOtpHash := sha256.Sum256([]byte(dto.Token))
|
||||
userOtpHashString := hex.EncodeToString(userOtpHash[:])
|
||||
actualHash := []byte(token.Token)
|
||||
expectedHash := []byte(userOtpHashString)
|
||||
|
||||
if len(actualHash) != len(expectedHash) {
|
||||
return nil, genericError
|
||||
}
|
||||
|
||||
if subtle.ConstantTimeCompare(actualHash, expectedHash) != 1 {
|
||||
return nil, genericError
|
||||
}
|
||||
|
||||
user, err := a.userRepo.GetByEmail(ctx, dto.Email)
|
||||
if err != nil {
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Internal Server Error")
|
||||
}
|
||||
if token == nil || token.Token != dto.Token {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid token")
|
||||
|
||||
if (dto.TokenType == constants.TokenEmailVerify && user != nil) ||
|
||||
(dto.TokenType == constants.TokenPasswordReset && user == nil) {
|
||||
return nil, genericError
|
||||
}
|
||||
|
||||
tokenId := uuid.New().String()
|
||||
err = a.tokenRepo.CreateVerified(ctx, dto.Email, dto.TokenType, tokenId)
|
||||
if err != nil {
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Internal Server Error")
|
||||
}
|
||||
|
||||
_ = a.tokenRepo.Delete(ctx, dto.Email, dto.TokenType)
|
||||
|
||||
return &response.VerifyTokenResponse{
|
||||
TokenID: tokenId,
|
||||
}, nil
|
||||
|
||||
Reference in New Issue
Block a user