fix(security): security audit fixes for API vulnerabilities
All checks were successful
Build Backend / build (push) Successful in 4m47s
Build Backend / build-docker (push) Successful in 2m22s

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:
lafay
2026-03-19 13:49:51 +08:00
parent d387cc493e
commit b0f209fdf8
13 changed files with 221 additions and 162 deletions

View File

@@ -12,7 +12,7 @@ func getAvatarOrDefault(user *model.User) string {
return utils.GetAvatarOrDefault(user.Username, user.Nickname, user.Avatar)
}
// ConvertUserToResponse 将User转换为UserResponse
// ConvertUserToResponse 将User转换为UserResponse(公开信息,不包含敏感数据)
func ConvertUserToResponse(user *model.User) *UserResponse {
if user == nil {
return nil
@@ -21,8 +21,6 @@ func ConvertUserToResponse(user *model.User) *UserResponse {
ID: user.ID,
Username: user.Username,
Nickname: user.Nickname,
Email: user.Email,
Phone: user.Phone,
EmailVerified: user.EmailVerified,
Avatar: getAvatarOrDefault(user),
CoverURL: user.CoverURL,
@@ -36,7 +34,32 @@ func ConvertUserToResponse(user *model.User) *UserResponse {
}
}
// ConvertUserToResponseWithFollowing 将User转换为UserResponse包含关注状态
// ConvertUserToSelfResponse 将User转换为UserSelfResponse本人信息,包含敏感数据
func ConvertUserToSelfResponse(user *model.User, postsCount int) *UserSelfResponse {
if user == nil {
return nil
}
return &UserSelfResponse{
ID: user.ID,
Username: user.Username,
Nickname: user.Nickname,
Email: user.Email,
Phone: user.Phone,
EmailVerified: user.EmailVerified,
Avatar: getAvatarOrDefault(user),
CoverURL: user.CoverURL,
Bio: user.Bio,
Website: user.Website,
Location: user.Location,
PostsCount: postsCount,
FollowersCount: user.FollowersCount,
FollowingCount: user.FollowingCount,
IsVerified: user.IsVerified,
CreatedAt: FormatTime(user.CreatedAt),
}
}
// ConvertUserToResponseWithFollowing 将User转换为UserResponse包含关注状态公开信息
func ConvertUserToResponseWithFollowing(user *model.User, isFollowing bool) *UserResponse {
if user == nil {
return nil
@@ -45,8 +68,6 @@ func ConvertUserToResponseWithFollowing(user *model.User, isFollowing bool) *Use
ID: user.ID,
Username: user.Username,
Nickname: user.Nickname,
Email: user.Email,
Phone: user.Phone,
EmailVerified: user.EmailVerified,
Avatar: getAvatarOrDefault(user),
CoverURL: user.CoverURL,
@@ -57,12 +78,12 @@ func ConvertUserToResponseWithFollowing(user *model.User, isFollowing bool) *Use
FollowersCount: user.FollowersCount,
FollowingCount: user.FollowingCount,
IsFollowing: isFollowing,
IsFollowingMe: false, // 默认false需要单独计算
IsFollowingMe: false,
CreatedAt: FormatTime(user.CreatedAt),
}
}
// ConvertUserToResponseWithPostsCount 将User转换为UserResponse使用实时计算的帖子数量
// ConvertUserToResponseWithPostsCount 将User转换为UserResponse使用实时计算的帖子数量,公开信息
func ConvertUserToResponseWithPostsCount(user *model.User, postsCount int) *UserResponse {
if user == nil {
return nil
@@ -71,8 +92,6 @@ func ConvertUserToResponseWithPostsCount(user *model.User, postsCount int) *User
ID: user.ID,
Username: user.Username,
Nickname: user.Nickname,
Email: user.Email,
Phone: user.Phone,
EmailVerified: user.EmailVerified,
Avatar: getAvatarOrDefault(user),
CoverURL: user.CoverURL,
@@ -86,7 +105,7 @@ func ConvertUserToResponseWithPostsCount(user *model.User, postsCount int) *User
}
}
// ConvertUserToResponseWithMutualFollow 将User转换为UserResponse包含双向关注状态
// ConvertUserToResponseWithMutualFollow 将User转换为UserResponse包含双向关注状态,公开信息
func ConvertUserToResponseWithMutualFollow(user *model.User, isFollowing, isFollowingMe bool) *UserResponse {
if user == nil {
return nil
@@ -95,8 +114,6 @@ func ConvertUserToResponseWithMutualFollow(user *model.User, isFollowing, isFoll
ID: user.ID,
Username: user.Username,
Nickname: user.Nickname,
Email: user.Email,
Phone: user.Phone,
EmailVerified: user.EmailVerified,
Avatar: getAvatarOrDefault(user),
CoverURL: user.CoverURL,
@@ -112,7 +129,7 @@ func ConvertUserToResponseWithMutualFollow(user *model.User, isFollowing, isFoll
}
}
// ConvertUserToResponseWithMutualFollowAndPostsCount 将User转换为UserResponse包含双向关注状态和实时计算的帖子数量
// ConvertUserToResponseWithMutualFollowAndPostsCount 将User转换为UserResponse包含双向关注状态和实时计算的帖子数量,公开信息
func ConvertUserToResponseWithMutualFollowAndPostsCount(user *model.User, isFollowing, isFollowingMe bool, postsCount int) *UserResponse {
if user == nil {
return nil
@@ -121,8 +138,6 @@ func ConvertUserToResponseWithMutualFollowAndPostsCount(user *model.User, isFoll
ID: user.ID,
Username: user.Username,
Nickname: user.Nickname,
Email: user.Email,
Phone: user.Phone,
EmailVerified: user.EmailVerified,
Avatar: getAvatarOrDefault(user),
CoverURL: user.CoverURL,
@@ -138,7 +153,7 @@ func ConvertUserToResponseWithMutualFollowAndPostsCount(user *model.User, isFoll
}
}
// ConvertUserToDetailResponse 将User转换为UserDetailResponse
// ConvertUserToDetailResponse 将User转换为UserDetailResponse(仅本人可见,包含敏感信息)
func ConvertUserToDetailResponse(user *model.User) *UserDetailResponse {
if user == nil {
return nil
@@ -162,7 +177,7 @@ func ConvertUserToDetailResponse(user *model.User) *UserDetailResponse {
}
}
// ConvertUserToDetailResponseWithPostsCount 将User转换为UserDetailResponse使用实时计算的帖子数量
// ConvertUserToDetailResponseWithPostsCount 将User转换为UserDetailResponse使用实时计算的帖子数量,仅本人可见
func ConvertUserToDetailResponseWithPostsCount(user *model.User, postsCount int) *UserDetailResponse {
if user == nil {
return nil
@@ -173,7 +188,7 @@ func ConvertUserToDetailResponseWithPostsCount(user *model.User, postsCount int)
Nickname: user.Nickname,
Email: user.Email,
EmailVerified: user.EmailVerified,
Phone: user.Phone, // 仅当前用户自己可见
Phone: user.Phone,
Avatar: getAvatarOrDefault(user),
CoverURL: user.CoverURL,
Bio: user.Bio,