[bugfix/backend] /users/me handling
This commit is contained in:
parent
823bedd1fa
commit
d8d8e4b0d7
3 changed files with 228 additions and 156 deletions
|
@ -1,183 +1,183 @@
|
||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/golang-jwt/jwt/v5"
|
"github.com/golang-jwt/jwt/v5"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
type RegisterRequest struct {
|
type RegisterRequest struct {
|
||||||
Username string `json:"username" binding:"required"`
|
Username string `json:"username" binding:"required"`
|
||||||
Email string `json:"email" binding:"required,email"`
|
Email string `json:"email" binding:"required,email"`
|
||||||
Password string `json:"password" binding:"required,min=8"`
|
Password string `json:"password" binding:"required,min=8"`
|
||||||
Role string `json:"role" binding:"required,oneof=admin editor contributor"`
|
Role string `json:"role" binding:"required,oneof=admin editor contributor"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type LoginRequest struct {
|
type LoginRequest struct {
|
||||||
Username string `json:"username" binding:"required,min=3,max=32"`
|
Username string `json:"username" binding:"required,min=3,max=32"`
|
||||||
Password string `json:"password" binding:"required"`
|
Password string `json:"password" binding:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AuthResponse struct {
|
type AuthResponse struct {
|
||||||
Token string `json:"token"`
|
Token string `json:"token"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) Register(c *gin.Context) {
|
func (h *Handler) Register(c *gin.Context) {
|
||||||
// 检查是否启用注册功能
|
// 检查是否启用注册功能
|
||||||
if !h.config.Auth.Registration.Enabled {
|
if !h.config.Auth.Registration.Enabled {
|
||||||
message := h.config.Auth.Registration.Message
|
message := h.config.Auth.Registration.Message
|
||||||
if message == "" {
|
if message == "" {
|
||||||
message = "Registration is currently disabled"
|
message = "Registration is currently disabled"
|
||||||
}
|
}
|
||||||
c.JSON(http.StatusForbidden, gin.H{
|
c.JSON(http.StatusForbidden, gin.H{
|
||||||
"error": gin.H{
|
"error": gin.H{
|
||||||
"code": "REGISTRATION_DISABLED",
|
"code": "REGISTRATION_DISABLED",
|
||||||
"message": message,
|
"message": message,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var req RegisterRequest
|
var req RegisterRequest
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
"error": gin.H{
|
"error": gin.H{
|
||||||
"code": "INVALID_REQUEST",
|
"code": "INVALID_REQUEST",
|
||||||
"message": err.Error(),
|
"message": err.Error(),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := h.service.CreateUser(c.Request.Context(), req.Username, req.Email, req.Password, req.Role)
|
user, err := h.service.CreateUser(c.Request.Context(), req.Username, req.Email, req.Password, req.Role)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("Failed to create user")
|
log.Error().Err(err).Msg("Failed to create user")
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"error": gin.H{
|
"error": gin.H{
|
||||||
"code": "CREATE_USER_FAILED",
|
"code": "CREATE_USER_FAILED",
|
||||||
"message": "Failed to create user",
|
"message": "Failed to create user",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get user roles
|
// Get user roles
|
||||||
roles, err := h.service.GetUserRoles(c.Request.Context(), user.ID)
|
roles, err := h.service.GetUserRoles(c.Request.Context(), user.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("Failed to get user roles")
|
log.Error().Err(err).Msg("Failed to get user roles")
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"error": gin.H{
|
"error": gin.H{
|
||||||
"code": "GET_ROLES_FAILED",
|
"code": "GET_ROLES_FAILED",
|
||||||
"message": "Failed to get user roles",
|
"message": "Failed to get user roles",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract role names for JWT
|
// Extract role names for JWT
|
||||||
roleNames := make([]string, len(roles))
|
roleNames := make([]string, len(roles))
|
||||||
for i, r := range roles {
|
for i, r := range roles {
|
||||||
roleNames[i] = r.Name
|
roleNames[i] = r.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate JWT token
|
// Generate JWT token
|
||||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
||||||
"sub": user.ID,
|
"sub": int64(user.ID), // 将用户 ID 转换为 int64
|
||||||
"roles": roleNames,
|
"roles": roleNames,
|
||||||
"exp": time.Now().Add(24 * time.Hour).Unix(),
|
"exp": time.Now().Add(24 * time.Hour).Unix(),
|
||||||
})
|
})
|
||||||
|
|
||||||
tokenString, err := token.SignedString([]byte(h.cfg.JWT.Secret))
|
tokenString, err := token.SignedString([]byte(h.cfg.JWT.Secret))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("Failed to generate token")
|
log.Error().Err(err).Msg("Failed to generate token")
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"error": gin.H{
|
"error": gin.H{
|
||||||
"code": "GENERATE_TOKEN_FAILED",
|
"code": "GENERATE_TOKEN_FAILED",
|
||||||
"message": "Failed to generate token",
|
"message": "Failed to generate token",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusCreated, AuthResponse{Token: tokenString})
|
c.JSON(http.StatusCreated, AuthResponse{Token: tokenString})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) Login(c *gin.Context) {
|
func (h *Handler) Login(c *gin.Context) {
|
||||||
var req LoginRequest
|
var req LoginRequest
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
"error": gin.H{
|
"error": gin.H{
|
||||||
"code": "INVALID_REQUEST",
|
"code": "INVALID_REQUEST",
|
||||||
"message": err.Error(),
|
"message": err.Error(),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := h.service.GetUserByUsername(c.Request.Context(), req.Username)
|
user, err := h.service.GetUserByUsername(c.Request.Context(), req.Username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{
|
c.JSON(http.StatusUnauthorized, gin.H{
|
||||||
"error": gin.H{
|
"error": gin.H{
|
||||||
"code": "INVALID_CREDENTIALS",
|
"code": "INVALID_CREDENTIALS",
|
||||||
"message": "Invalid username or password",
|
"message": "Invalid username or password",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证密码
|
// 验证密码
|
||||||
err = bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(req.Password))
|
err = bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(req.Password))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{
|
c.JSON(http.StatusUnauthorized, gin.H{
|
||||||
"error": gin.H{
|
"error": gin.H{
|
||||||
"code": "INVALID_CREDENTIALS",
|
"code": "INVALID_CREDENTIALS",
|
||||||
"message": "Invalid username or password",
|
"message": "Invalid username or password",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get user roles
|
// Get user roles
|
||||||
roles, err := h.service.GetUserRoles(c.Request.Context(), user.ID)
|
roles, err := h.service.GetUserRoles(c.Request.Context(), user.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("Failed to get user roles")
|
log.Error().Err(err).Msg("Failed to get user roles")
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"error": gin.H{
|
"error": gin.H{
|
||||||
"code": "GET_ROLES_FAILED",
|
"code": "GET_ROLES_FAILED",
|
||||||
"message": "Failed to get user roles",
|
"message": "Failed to get user roles",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract role names for JWT
|
// Extract role names for JWT
|
||||||
roleNames := make([]string, len(roles))
|
roleNames := make([]string, len(roles))
|
||||||
for i, r := range roles {
|
for i, r := range roles {
|
||||||
roleNames[i] = r.Name
|
roleNames[i] = r.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate JWT token
|
// Generate JWT token
|
||||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
||||||
"sub": user.ID,
|
"sub": int64(user.ID), // 将用户 ID 转换为 int64
|
||||||
"roles": roleNames,
|
"roles": roleNames,
|
||||||
"exp": time.Now().Add(24 * time.Hour).Unix(),
|
"exp": time.Now().Add(24 * time.Hour).Unix(),
|
||||||
})
|
})
|
||||||
|
|
||||||
tokenString, err := token.SignedString([]byte(h.cfg.JWT.Secret))
|
tokenString, err := token.SignedString([]byte(h.cfg.JWT.Secret))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("Failed to generate token")
|
log.Error().Err(err).Msg("Failed to generate token")
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"error": gin.H{
|
"error": gin.H{
|
||||||
"code": "GENERATE_TOKEN_FAILED",
|
"code": "GENERATE_TOKEN_FAILED",
|
||||||
"message": "Failed to generate token",
|
"message": "Failed to generate token",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, AuthResponse{Token: tokenString})
|
c.JSON(http.StatusOK, AuthResponse{Token: tokenString})
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"tss-rocks-be/internal/types"
|
"tss-rocks-be/internal/types"
|
||||||
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UpdateCurrentUserRequest struct {
|
type UpdateCurrentUserRequest struct {
|
||||||
|
@ -160,24 +161,39 @@ func (h *Handler) DeleteUser(c *gin.Context) {
|
||||||
|
|
||||||
// GetCurrentUser returns the current user's information
|
// GetCurrentUser returns the current user's information
|
||||||
func (h *Handler) GetCurrentUser(c *gin.Context) {
|
func (h *Handler) GetCurrentUser(c *gin.Context) {
|
||||||
// 从上下文中获取用户ID(由认证中间件设置)
|
|
||||||
userID, exists := c.Get("user_id")
|
userID, exists := c.Get("user_id")
|
||||||
if !exists {
|
if !exists {
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 将用户ID转换为int64
|
||||||
|
var id int64
|
||||||
|
switch v := userID.(type) {
|
||||||
|
case int64:
|
||||||
|
id = v
|
||||||
|
case int:
|
||||||
|
id = int64(v)
|
||||||
|
case float64:
|
||||||
|
id = int64(v)
|
||||||
|
default:
|
||||||
|
log.Error().
|
||||||
|
Str("type", fmt.Sprintf("%T", userID)).
|
||||||
|
Interface("value", userID).
|
||||||
|
Msg("Invalid user_id type")
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Invalid user_id type"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// 获取用户信息
|
// 获取用户信息
|
||||||
user, err := h.service.GetUser(c.Request.Context(), userID.(int))
|
user, err := h.service.GetUser(c.Request.Context(), int(id))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("Failed to get user")
|
log.Error().Err(err).Msg("Failed to get user")
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get user information"})
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get user information"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, user)
|
||||||
"data": user,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateCurrentUser updates the current user's information
|
// UpdateCurrentUser updates the current user's information
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
package middleware
|
package middleware
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
@ -41,8 +44,61 @@ func AuthMiddleware(jwtSecret string) gin.HandlerFunc {
|
||||||
}
|
}
|
||||||
|
|
||||||
if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
|
if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
|
||||||
c.Set("user_id", claims["sub"])
|
// 添加调试信息
|
||||||
c.Set("user_role", claims["role"])
|
log.Debug().Interface("claims", claims).Msg("Token claims")
|
||||||
|
|
||||||
|
// 获取用户ID
|
||||||
|
sub, exists := claims["sub"]
|
||||||
|
if !exists {
|
||||||
|
log.Error().Msg("Token does not contain sub claim")
|
||||||
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid token format"})
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打印类型信息
|
||||||
|
log.Debug().
|
||||||
|
Str("type", fmt.Sprintf("%T", sub)).
|
||||||
|
Interface("value", sub).
|
||||||
|
Msg("User ID from token")
|
||||||
|
|
||||||
|
var userID int
|
||||||
|
switch v := sub.(type) {
|
||||||
|
case string:
|
||||||
|
var err error
|
||||||
|
userID, err = strconv.Atoi(v)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Str("sub", v).Msg("Failed to convert string user ID to int")
|
||||||
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid user ID format"})
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case float64:
|
||||||
|
userID = int(v)
|
||||||
|
case json.Number:
|
||||||
|
var err error
|
||||||
|
userID, err = strconv.Atoi(v.String())
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Str("sub", v.String()).Msg("Failed to convert json.Number user ID to int")
|
||||||
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid user ID format"})
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
log.Error().
|
||||||
|
Str("type", fmt.Sprintf("%T", sub)).
|
||||||
|
Interface("value", sub).
|
||||||
|
Msg("Unexpected user ID type")
|
||||||
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid user ID type"})
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将 userID 转换为 int64 以确保类型一致性
|
||||||
|
c.Set("user_id", int64(userID))
|
||||||
|
if roles, ok := claims["roles"].([]interface{}); ok {
|
||||||
|
c.Set("user_roles", roles)
|
||||||
|
}
|
||||||
c.Next()
|
c.Next()
|
||||||
} else {
|
} else {
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid token"})
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid token"})
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue