mirror of
https://github.com/Tencent/WeKnora.git
synced 2025-11-25 19:37:45 +08:00
344 lines
10 KiB
Go
344 lines
10 KiB
Go
package handler
|
|
|
|
import (
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
"github.com/Tencent/WeKnora/internal/errors"
|
|
"github.com/Tencent/WeKnora/internal/logger"
|
|
"github.com/Tencent/WeKnora/internal/types"
|
|
"github.com/Tencent/WeKnora/internal/types/interfaces"
|
|
)
|
|
|
|
// AuthHandler implements HTTP request handlers for user authentication
|
|
// Provides functionality for user registration, login, logout, and token management
|
|
// through the REST API endpoints
|
|
type AuthHandler struct {
|
|
userService interfaces.UserService
|
|
tenantService interfaces.TenantService
|
|
}
|
|
|
|
// NewAuthHandler creates a new auth handler instance with the provided services
|
|
// Parameters:
|
|
// - userService: An implementation of the UserService interface for business logic
|
|
// - tenantService: An implementation of the TenantService interface for tenant management
|
|
//
|
|
// Returns a pointer to the newly created AuthHandler
|
|
func NewAuthHandler(userService interfaces.UserService, tenantService interfaces.TenantService) *AuthHandler {
|
|
return &AuthHandler{
|
|
userService: userService,
|
|
tenantService: tenantService,
|
|
}
|
|
}
|
|
|
|
// Register handles the HTTP request for user registration
|
|
// It deserializes the request body into a registration request object, validates it,
|
|
// calls the service to create the user, and returns the result
|
|
// Parameters:
|
|
// - c: Gin context for the HTTP request
|
|
func (h *AuthHandler) Register(c *gin.Context) {
|
|
ctx := c.Request.Context()
|
|
|
|
logger.Info(ctx, "Start user registration")
|
|
|
|
var req types.RegisterRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
logger.Error(ctx, "Failed to parse registration request parameters", err)
|
|
appErr := errors.NewValidationError("Invalid registration parameters").WithDetails(err.Error())
|
|
c.Error(appErr)
|
|
return
|
|
}
|
|
|
|
// Validate required fields
|
|
if req.Username == "" || req.Email == "" || req.Password == "" {
|
|
logger.Error(ctx, "Missing required registration fields")
|
|
appErr := errors.NewValidationError("Username, email and password are required")
|
|
c.Error(appErr)
|
|
return
|
|
}
|
|
|
|
// Call service to register user
|
|
user, err := h.userService.Register(ctx, &req)
|
|
if err != nil {
|
|
logger.Errorf(ctx, "Failed to register user: %v", err)
|
|
appErr := errors.NewBadRequestError("Registration failed").WithDetails(err.Error())
|
|
c.Error(appErr)
|
|
return
|
|
}
|
|
|
|
// Return success response
|
|
response := &types.RegisterResponse{
|
|
Success: true,
|
|
Message: "Registration successful",
|
|
User: user,
|
|
}
|
|
|
|
logger.Infof(ctx, "User registered successfully: %s", user.Email)
|
|
c.JSON(http.StatusCreated, response)
|
|
}
|
|
|
|
// Login handles the HTTP request for user login
|
|
// It deserializes the request body into a login request object, validates it,
|
|
// calls the service to authenticate the user, and returns tokens
|
|
// Parameters:
|
|
// - c: Gin context for the HTTP request
|
|
func (h *AuthHandler) Login(c *gin.Context) {
|
|
ctx := c.Request.Context()
|
|
|
|
logger.Info(ctx, "Start user login")
|
|
|
|
var req types.LoginRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
logger.Error(ctx, "Failed to parse login request parameters", err)
|
|
appErr := errors.NewValidationError("Invalid login parameters").WithDetails(err.Error())
|
|
c.Error(appErr)
|
|
return
|
|
}
|
|
|
|
// Validate required fields
|
|
if req.Email == "" || req.Password == "" {
|
|
logger.Error(ctx, "Missing required login fields")
|
|
appErr := errors.NewValidationError("Email and password are required")
|
|
c.Error(appErr)
|
|
return
|
|
}
|
|
|
|
// Call service to authenticate user
|
|
response, err := h.userService.Login(ctx, &req)
|
|
if err != nil {
|
|
logger.Errorf(ctx, "Failed to login user: %v", err)
|
|
appErr := errors.NewUnauthorizedError("Login failed").WithDetails(err.Error())
|
|
c.Error(appErr)
|
|
return
|
|
}
|
|
|
|
// Check if login was successful
|
|
if !response.Success {
|
|
logger.Warnf(ctx, "Login failed: %s", response.Message)
|
|
c.JSON(http.StatusUnauthorized, response)
|
|
return
|
|
}
|
|
|
|
// User is already in the correct format from service
|
|
|
|
logger.Infof(ctx, "User logged in successfully: %s", req.Email)
|
|
c.JSON(http.StatusOK, response)
|
|
}
|
|
|
|
// Logout handles the HTTP request for user logout
|
|
// It extracts the token from the Authorization header and revokes it
|
|
// Parameters:
|
|
// - c: Gin context for the HTTP request
|
|
func (h *AuthHandler) Logout(c *gin.Context) {
|
|
ctx := c.Request.Context()
|
|
|
|
logger.Info(ctx, "Start user logout")
|
|
|
|
// Extract token from Authorization header
|
|
authHeader := c.GetHeader("Authorization")
|
|
if authHeader == "" {
|
|
logger.Error(ctx, "Missing Authorization header")
|
|
appErr := errors.NewValidationError("Authorization header is required")
|
|
c.Error(appErr)
|
|
return
|
|
}
|
|
|
|
// Parse Bearer token
|
|
tokenParts := strings.Split(authHeader, " ")
|
|
if len(tokenParts) != 2 || tokenParts[0] != "Bearer" {
|
|
logger.Error(ctx, "Invalid Authorization header format")
|
|
appErr := errors.NewValidationError("Invalid Authorization header format")
|
|
c.Error(appErr)
|
|
return
|
|
}
|
|
|
|
token := tokenParts[1]
|
|
|
|
// Revoke token
|
|
err := h.userService.RevokeToken(ctx, token)
|
|
if err != nil {
|
|
logger.Errorf(ctx, "Failed to revoke token: %v", err)
|
|
appErr := errors.NewInternalServerError("Logout failed").WithDetails(err.Error())
|
|
c.Error(appErr)
|
|
return
|
|
}
|
|
|
|
logger.Info(ctx, "User logged out successfully")
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": true,
|
|
"message": "Logout successful",
|
|
})
|
|
}
|
|
|
|
// RefreshToken handles the HTTP request for refreshing access tokens
|
|
// It extracts the refresh token from the request body and generates new tokens
|
|
// Parameters:
|
|
// - c: Gin context for the HTTP request
|
|
func (h *AuthHandler) RefreshToken(c *gin.Context) {
|
|
ctx := c.Request.Context()
|
|
|
|
logger.Info(ctx, "Start token refresh")
|
|
|
|
var req struct {
|
|
RefreshToken string `json:"refreshToken" binding:"required"`
|
|
}
|
|
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
logger.Error(ctx, "Failed to parse refresh token request", err)
|
|
appErr := errors.NewValidationError("Invalid refresh token request").WithDetails(err.Error())
|
|
c.Error(appErr)
|
|
return
|
|
}
|
|
|
|
// Call service to refresh token
|
|
accessToken, newRefreshToken, err := h.userService.RefreshToken(ctx, req.RefreshToken)
|
|
if err != nil {
|
|
logger.Errorf(ctx, "Failed to refresh token: %v", err)
|
|
appErr := errors.NewUnauthorizedError("Token refresh failed").WithDetails(err.Error())
|
|
c.Error(appErr)
|
|
return
|
|
}
|
|
|
|
logger.Info(ctx, "Token refreshed successfully")
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": true,
|
|
"message": "Token refreshed successfully",
|
|
"access_token": accessToken,
|
|
"refresh_token": newRefreshToken,
|
|
})
|
|
}
|
|
|
|
// GetCurrentUser handles the HTTP request for getting current user information
|
|
// It extracts the user from the context (set by auth middleware) and returns user info
|
|
// Parameters:
|
|
// - c: Gin context for the HTTP request
|
|
func (h *AuthHandler) GetCurrentUser(c *gin.Context) {
|
|
ctx := c.Request.Context()
|
|
|
|
logger.Debugf(ctx, "Get current user info")
|
|
|
|
// Get current user from service (which extracts from context)
|
|
user, err := h.userService.GetCurrentUser(ctx)
|
|
if err != nil {
|
|
logger.Errorf(ctx, "Failed to get current user: %v", err)
|
|
appErr := errors.NewUnauthorizedError("Failed to get user information").WithDetails(err.Error())
|
|
c.Error(appErr)
|
|
return
|
|
}
|
|
|
|
// Get tenant information
|
|
var tenant *types.Tenant
|
|
if user.TenantID > 0 {
|
|
tenant, err = h.tenantService.GetTenantByID(ctx, user.TenantID)
|
|
if err != nil {
|
|
logger.Warnf(ctx, "Failed to get tenant info for user %s, tenant ID %d: %v", user.Email, user.TenantID, err)
|
|
// Don't fail the request if tenant info is not available
|
|
} else {
|
|
logger.Debugf(ctx, "Retrieved tenant info for user %s: %s", user.Email, tenant.Name)
|
|
}
|
|
}
|
|
|
|
logger.Debugf(ctx, "Retrieved current user info: %s", user.Email)
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": true,
|
|
"data": gin.H{
|
|
"user": user.ToUserInfo(),
|
|
"tenant": tenant,
|
|
},
|
|
})
|
|
}
|
|
|
|
// ChangePassword handles the HTTP request for changing user password
|
|
// It extracts the current user and validates the old password before setting new one
|
|
// Parameters:
|
|
// - c: Gin context for the HTTP request
|
|
func (h *AuthHandler) ChangePassword(c *gin.Context) {
|
|
ctx := c.Request.Context()
|
|
|
|
logger.Info(ctx, "Start password change")
|
|
|
|
var req struct {
|
|
OldPassword string `json:"old_password" binding:"required"`
|
|
NewPassword string `json:"new_password" binding:"required,min=6"`
|
|
}
|
|
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
logger.Error(ctx, "Failed to parse password change request", err)
|
|
appErr := errors.NewValidationError("Invalid password change request").WithDetails(err.Error())
|
|
c.Error(appErr)
|
|
return
|
|
}
|
|
|
|
// Get current user
|
|
user, err := h.userService.GetCurrentUser(ctx)
|
|
if err != nil {
|
|
logger.Errorf(ctx, "Failed to get current user: %v", err)
|
|
appErr := errors.NewUnauthorizedError("Failed to get user information").WithDetails(err.Error())
|
|
c.Error(appErr)
|
|
return
|
|
}
|
|
|
|
// Change password
|
|
err = h.userService.ChangePassword(ctx, user.ID, req.OldPassword, req.NewPassword)
|
|
if err != nil {
|
|
logger.Errorf(ctx, "Failed to change password: %v", err)
|
|
appErr := errors.NewBadRequestError("Password change failed").WithDetails(err.Error())
|
|
c.Error(appErr)
|
|
return
|
|
}
|
|
|
|
logger.Infof(ctx, "Password changed successfully for user: %s", user.Email)
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": true,
|
|
"message": "Password changed successfully",
|
|
})
|
|
}
|
|
|
|
// ValidateToken handles the HTTP request for validating access tokens
|
|
// It extracts the token from the Authorization header and validates it
|
|
// Parameters:
|
|
// - c: Gin context for the HTTP request
|
|
func (h *AuthHandler) ValidateToken(c *gin.Context) {
|
|
ctx := c.Request.Context()
|
|
|
|
logger.Info(ctx, "Start token validation")
|
|
|
|
// Extract token from Authorization header
|
|
authHeader := c.GetHeader("Authorization")
|
|
if authHeader == "" {
|
|
logger.Error(ctx, "Missing Authorization header")
|
|
appErr := errors.NewValidationError("Authorization header is required")
|
|
c.Error(appErr)
|
|
return
|
|
}
|
|
|
|
// Parse Bearer token
|
|
tokenParts := strings.Split(authHeader, " ")
|
|
if len(tokenParts) != 2 || tokenParts[0] != "Bearer" {
|
|
logger.Error(ctx, "Invalid Authorization header format")
|
|
appErr := errors.NewValidationError("Invalid Authorization header format")
|
|
c.Error(appErr)
|
|
return
|
|
}
|
|
|
|
token := tokenParts[1]
|
|
|
|
// Validate token
|
|
user, err := h.userService.ValidateToken(ctx, token)
|
|
if err != nil {
|
|
logger.Errorf(ctx, "Failed to validate token: %v", err)
|
|
appErr := errors.NewUnauthorizedError("Token validation failed").WithDetails(err.Error())
|
|
c.Error(appErr)
|
|
return
|
|
}
|
|
|
|
logger.Infof(ctx, "Token validated successfully for user: %s", user.Email)
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": true,
|
|
"message": "Token is valid",
|
|
"user": user.ToUserInfo(),
|
|
})
|
|
}
|