fix(security): security audit fixes for API vulnerabilities
Security fixes: - Fix sensitive data exposure: UserResponse no longer contains email/phone - Add UserSelfResponse for authenticated user's own data - Protect Gorse endpoints with admin role requirement - Add rate limiting (10 req/min) to auth endpoints - Fix CORS configuration to use allowed origins list - Add pagination parameter validation (max 100 per page) Changes: - dto/dto.go: Add UserSelfResponse type - dto/user_converter.go: Add ConvertUserToSelfResponse - handler/user_handler.go: Use secure response types - middleware/cors.go: Implement origin whitelist - middleware/ratelimit.go: Enhance rate limiter - router/router.go: Add auth rate limit, protect Gorse - service/admin_*.go: Use ConvertUserToResponse
This commit is contained in:
@@ -15,6 +15,21 @@ import (
|
||||
"carrot_bbs/internal/service"
|
||||
)
|
||||
|
||||
const (
|
||||
maxPageSize = 100
|
||||
defaultPageSize = 20
|
||||
)
|
||||
|
||||
func normalizePagination(page, pageSize int) (int, int) {
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize < 1 || pageSize > maxPageSize {
|
||||
pageSize = defaultPageSize
|
||||
}
|
||||
return page, pageSize
|
||||
}
|
||||
|
||||
// UserHandler 用户处理器
|
||||
type UserHandler struct {
|
||||
userService service.UserService
|
||||
@@ -110,7 +125,7 @@ func (h *UserHandler) Register(c *gin.Context) {
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"user": dto.ConvertUserToResponse(user),
|
||||
"user": dto.ConvertUserToSelfResponse(user, 0),
|
||||
"token": accessToken,
|
||||
"refresh_token": refreshToken,
|
||||
})
|
||||
@@ -177,7 +192,7 @@ func (h *UserHandler) Login(c *gin.Context) {
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"user": dto.ConvertUserToResponse(user),
|
||||
"user": dto.ConvertUserToSelfResponse(user, int(user.PostsCount)),
|
||||
"token": accessToken,
|
||||
"refresh_token": refreshToken,
|
||||
})
|
||||
@@ -195,7 +210,8 @@ func (h *UserHandler) SendRegisterCode(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.userService.SendRegisterCode(c.Request.Context(), req.Email); err != nil {
|
||||
clientIP := c.ClientIP()
|
||||
if err := h.userService.SendRegisterCode(c.Request.Context(), req.Email, clientIP); err != nil {
|
||||
response.HandleError(c, err, "failed to send register verification code")
|
||||
return
|
||||
}
|
||||
@@ -203,7 +219,6 @@ func (h *UserHandler) SendRegisterCode(c *gin.Context) {
|
||||
response.Success(c, gin.H{"success": true})
|
||||
}
|
||||
|
||||
// SendPasswordResetCode 发送找回密码验证码
|
||||
func (h *UserHandler) SendPasswordResetCode(c *gin.Context) {
|
||||
type SendCodeRequest struct {
|
||||
Email string `json:"email" binding:"required,email"`
|
||||
@@ -215,7 +230,8 @@ func (h *UserHandler) SendPasswordResetCode(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.userService.SendPasswordResetCode(c.Request.Context(), req.Email); err != nil {
|
||||
clientIP := c.ClientIP()
|
||||
if err := h.userService.SendPasswordResetCode(c.Request.Context(), req.Email, clientIP); err != nil {
|
||||
response.HandleError(c, err, "failed to send reset verification code")
|
||||
return
|
||||
}
|
||||
@@ -259,14 +275,12 @@ func (h *UserHandler) GetCurrentUser(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// 实时计算帖子数量
|
||||
postsCount, err := h.userService.GetUserPostCount(c.Request.Context(), userID)
|
||||
if err != nil {
|
||||
// 如果获取失败,使用数据库中的值
|
||||
postsCount = int64(user.PostsCount)
|
||||
}
|
||||
|
||||
response.Success(c, dto.ConvertUserToDetailResponseWithPostsCount(user, int(postsCount)))
|
||||
response.Success(c, dto.ConvertUserToSelfResponse(user, int(postsCount)))
|
||||
}
|
||||
|
||||
// GetUserByID 获取指定用户
|
||||
@@ -382,7 +396,7 @@ func (h *UserHandler) SendEmailVerifyCode(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.userService.SendCurrentUserEmailVerifyCode(c.Request.Context(), userID, req.Email); err != nil {
|
||||
if err := h.userService.SendCurrentUserEmailVerifyCode(c.Request.Context(), userID, req.Email, c.ClientIP()); err != nil {
|
||||
response.HandleError(c, err, "failed to send email verify code")
|
||||
return
|
||||
}
|
||||
@@ -562,12 +576,7 @@ func (h *UserHandler) GetBlockedUsers(c *gin.Context) {
|
||||
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize <= 0 {
|
||||
pageSize = 20
|
||||
}
|
||||
page, pageSize = normalizePagination(page, pageSize)
|
||||
|
||||
users, total, err := h.userService.GetBlockedUsers(c.Request.Context(), currentUserID, page, pageSize)
|
||||
if err != nil {
|
||||
@@ -734,7 +743,7 @@ func (h *UserHandler) SendChangePasswordCode(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
err := h.userService.SendChangePasswordCode(c.Request.Context(), currentUserID)
|
||||
err := h.userService.SendChangePasswordCode(c.Request.Context(), currentUserID, c.ClientIP())
|
||||
if err != nil {
|
||||
response.HandleError(c, err, "failed to send change password code")
|
||||
return
|
||||
@@ -782,6 +791,7 @@ func (h *UserHandler) Search(c *gin.Context) {
|
||||
keyword := c.Query("keyword")
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||||
page, pageSize = normalizePagination(page, pageSize)
|
||||
|
||||
users, total, err := h.userService.Search(c.Request.Context(), keyword, page, pageSize)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user