package handlers import ( "errors" "time" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication" jwtutil "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/jwt" "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response" "github.com/gofiber/fiber/v2" "go.uber.org/zap" ) // loginCustomerReq represents the request body for the LoginCustomer endpoint. type loginCustomerReq struct { Email string `json:"email" validate:"email" example:"john.doe@example.com"` PhoneNumber string `json:"phone_number" validate:"required_without=Email" example:"1234567890"` Password string `json:"password" validate:"required" example:"password123"` } // loginCustomerRes represents the response body for the LoginCustomer endpoint. type loginCustomerRes struct { AccessToken string `json:"access_token"` RefreshToken string `json:"refresh_token"` Role string `json:"role"` } // LoginCustomer godoc // @Summary Login customer // @Description Login customer // @Tags auth // @Accept json // @Produce json // @Param login body loginCustomerReq true "Login customer" // @Success 200 {object} loginCustomerRes // @Failure 400 {object} response.APIResponse // @Failure 401 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Router /auth/login [post] func (h *Handler) LoginCustomer(c *fiber.Ctx) error { var req loginCustomerReq if err := c.BodyParser(&req); err != nil { h.mongoLoggerSvc.Error("Failed to parse LoginCustomer request", zap.Int("status_code", fiber.StatusBadRequest), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") } if _, ok := h.validator.Validate(c, req); !ok { h.mongoLoggerSvc.Error("LoginCustomer validation failed", zap.Int("status_code", fiber.StatusBadRequest), zap.Any("request", req), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusBadRequest, "Invalid Request") } successRes, err := h.authSvc.Login(c.Context(), req.Email, req.PhoneNumber, req.Password) if err != nil { h.mongoLoggerSvc.Info("Login attempt failed", zap.Int("status_code", fiber.StatusUnauthorized), zap.String("email", req.Email), zap.String("phone", req.PhoneNumber), zap.Error(err), zap.Time("timestamp", time.Now()), ) switch { case errors.Is(err, authentication.ErrInvalidPassword), errors.Is(err, authentication.ErrUserNotFound): return fiber.NewError(fiber.StatusUnauthorized, "Invalid credentials") case errors.Is(err, authentication.ErrUserSuspended): return fiber.NewError(fiber.StatusUnauthorized, "User login has been locked") default: h.mongoLoggerSvc.Error("Login failed", zap.Int("status_code", fiber.StatusInternalServerError), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusInternalServerError, "Internal server error") } } accessToken, err := jwtutil.CreateJwt(successRes.UserId, successRes.Role, successRes.CompanyID, h.jwtConfig.JwtAccessKey, h.jwtConfig.JwtAccessExpiry) if err != nil { h.mongoLoggerSvc.Error("Failed to create access token", zap.Int("status_code", fiber.StatusInternalServerError), zap.Int64("user_id", successRes.UserId), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusInternalServerError, "Failed to generate access token") } res := loginCustomerRes{ AccessToken: accessToken, RefreshToken: successRes.RfToken, Role: string(successRes.Role), } h.mongoLoggerSvc.Info("Login successful", zap.Int("status_code", fiber.StatusOK), zap.Int64("user_id", successRes.UserId), zap.String("role", string(successRes.Role)), zap.Time("timestamp", time.Now()), ) return response.WriteJSON(c, fiber.StatusOK, "Login successful", res, nil) } type refreshToken struct { AccessToken string `json:"access_token" validate:"required" example:""` RefreshToken string `json:"refresh_token" validate:"required" example:""` } // RefreshToken godoc // @Summary Refresh token // @Description Refresh token // @Tags auth // @Accept json // @Produce json // @Param refresh body refreshToken true "tokens" // @Success 200 {object} loginCustomerRes // @Failure 400 {object} response.APIResponse // @Failure 401 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Router /auth/refresh [post] func (h *Handler) RefreshToken(c *fiber.Ctx) error { type loginCustomerRes struct { AccessToken string `json:"access_token"` RefreshToken string `json:"refresh_token"` Role string `json:"role"` } var req refreshToken if err := c.BodyParser(&req); err != nil { h.mongoLoggerSvc.Error("Failed to parse RefreshToken request", zap.Int("status_code", fiber.StatusBadRequest), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") } if valErrs, ok := h.validator.Validate(c, req); !ok { h.mongoLoggerSvc.Error("RefreshToken validation failed", zap.Int("status_code", fiber.StatusBadRequest), zap.Any("validation_errors", valErrs), zap.Time("timestamp", time.Now()), ) return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) } refreshToken, err := h.authSvc.RefreshToken(c.Context(), req.RefreshToken) if err != nil { h.mongoLoggerSvc.Info("Refresh token attempt failed", zap.Int("status_code", fiber.StatusUnauthorized), zap.String("refresh_token", req.RefreshToken), zap.Error(err), zap.Time("timestamp", time.Now()), ) switch { case errors.Is(err, authentication.ErrExpiredToken): return fiber.NewError(fiber.StatusUnauthorized, "The refresh token has expired") case errors.Is(err, authentication.ErrRefreshTokenNotFound): return fiber.NewError(fiber.StatusUnauthorized, "Refresh token not found") default: h.mongoLoggerSvc.Error("Refresh token failed", zap.Int("status_code", fiber.StatusInternalServerError), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusInternalServerError, "Internal server error") } } user, err := h.userSvc.GetUserByID(c.Context(), refreshToken.UserID) if err != nil { h.mongoLoggerSvc.Error("Failed to get user by ID during refresh", zap.Int("status_code", fiber.StatusInternalServerError), zap.Int64("user_id", refreshToken.UserID), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user information") } accessToken, err := jwtutil.CreateJwt(user.ID, user.Role, user.CompanyID, h.jwtConfig.JwtAccessKey, h.jwtConfig.JwtAccessExpiry) if err != nil { h.mongoLoggerSvc.Error("Failed to create new access token", zap.Int("status_code", fiber.StatusInternalServerError), zap.Int64("user_id", user.ID), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusInternalServerError, "Failed to generate access token") } res := loginCustomerRes{ AccessToken: accessToken, RefreshToken: req.RefreshToken, Role: string(user.Role), } h.mongoLoggerSvc.Info("Refresh token successful", zap.Int("status_code", fiber.StatusOK), zap.Int64("user_id", user.ID), zap.String("role", string(user.Role)), zap.Time("timestamp", time.Now()), ) return response.WriteJSON(c, fiber.StatusOK, "Refresh successful", res, nil) } type logoutReq struct { RefreshToken string `json:"refresh_token" validate:"required" example:""` } // LogOutCustomer godoc // @Summary Logout customer // @Description Logout customer // @Tags auth // @Accept json // @Produce json // @Param logout body logoutReq true "Logout customer" // @Success 200 {object} response.APIResponse // @Failure 400 {object} response.APIResponse // @Failure 401 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Router /auth/logout [post] func (h *Handler) LogOutCustomer(c *fiber.Ctx) error { var req logoutReq if err := c.BodyParser(&req); err != nil { h.mongoLoggerSvc.Error("Failed to parse LogOutCustomer request", zap.Int("status_code", fiber.StatusBadRequest), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") } if valErrs, ok := h.validator.Validate(c, req); !ok { h.mongoLoggerSvc.Error("LogOutCustomer validation failed", zap.Int("status_code", fiber.StatusBadRequest), zap.Any("validation_errors", valErrs), zap.Time("timestamp", time.Now()), ) return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) } err := h.authSvc.Logout(c.Context(), req.RefreshToken) if err != nil { h.mongoLoggerSvc.Info("Logout attempt failed", zap.Int("status_code", fiber.StatusUnauthorized), zap.String("refresh_token", req.RefreshToken), zap.Error(err), zap.Time("timestamp", time.Now()), ) switch { case errors.Is(err, authentication.ErrExpiredToken): return fiber.NewError(fiber.StatusUnauthorized, "The refresh token has expired") case errors.Is(err, authentication.ErrRefreshTokenNotFound): return fiber.NewError(fiber.StatusUnauthorized, "Refresh token not found") default: h.mongoLoggerSvc.Error("Logout failed", zap.Int("status_code", fiber.StatusInternalServerError), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusInternalServerError, "Internal server error") } } h.mongoLoggerSvc.Info("Logout successful", zap.Int("status_code", fiber.StatusOK), zap.Time("timestamp", time.Now()), ) return response.WriteJSON(c, fiber.StatusOK, "Logout successful", nil, nil) }