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:
@@ -7,8 +7,27 @@ import (
|
||||
|
||||
// ==================== User DTOs ====================
|
||||
|
||||
// UserResponse 用户信息响应
|
||||
// UserResponse 用户公开信息响应(不包含敏感信息)
|
||||
type UserResponse struct {
|
||||
ID string `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Nickname string `json:"nickname"`
|
||||
EmailVerified bool `json:"email_verified"`
|
||||
Avatar string `json:"avatar"`
|
||||
CoverURL string `json:"cover_url"`
|
||||
Bio string `json:"bio"`
|
||||
Website string `json:"website"`
|
||||
Location string `json:"location"`
|
||||
PostsCount int `json:"posts_count"`
|
||||
FollowersCount int `json:"followers_count"`
|
||||
FollowingCount int `json:"following_count"`
|
||||
IsFollowing bool `json:"is_following"`
|
||||
IsFollowingMe bool `json:"is_following_me"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
}
|
||||
|
||||
// UserSelfResponse 用户自身信息响应(包含敏感信息,仅本人可见)
|
||||
type UserSelfResponse struct {
|
||||
ID string `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Nickname string `json:"nickname"`
|
||||
@@ -16,28 +35,7 @@ type UserResponse struct {
|
||||
Phone *string `json:"phone,omitempty"`
|
||||
EmailVerified bool `json:"email_verified"`
|
||||
Avatar string `json:"avatar"`
|
||||
CoverURL string `json:"cover_url"` // 头图URL
|
||||
Bio string `json:"bio"`
|
||||
Website string `json:"website"`
|
||||
Location string `json:"location"`
|
||||
PostsCount int `json:"posts_count"`
|
||||
FollowersCount int `json:"followers_count"`
|
||||
FollowingCount int `json:"following_count"`
|
||||
IsFollowing bool `json:"is_following"` // 当前用户是否关注了该用户
|
||||
IsFollowingMe bool `json:"is_following_me"` // 该用户是否关注了当前用户
|
||||
CreatedAt string `json:"created_at"`
|
||||
}
|
||||
|
||||
// UserDetailResponse 用户详情响应
|
||||
type UserDetailResponse struct {
|
||||
ID string `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Nickname string `json:"nickname"`
|
||||
Email *string `json:"email"`
|
||||
EmailVerified bool `json:"email_verified"`
|
||||
Phone *string `json:"phone,omitempty"` // 仅当前用户自己可见
|
||||
Avatar string `json:"avatar"`
|
||||
CoverURL string `json:"cover_url"` // 头图URL
|
||||
CoverURL string `json:"cover_url"`
|
||||
Bio string `json:"bio"`
|
||||
Website string `json:"website"`
|
||||
Location string `json:"location"`
|
||||
@@ -45,8 +43,28 @@ type UserDetailResponse struct {
|
||||
FollowersCount int `json:"followers_count"`
|
||||
FollowingCount int `json:"following_count"`
|
||||
IsVerified bool `json:"is_verified"`
|
||||
IsFollowing bool `json:"is_following"` // 当前用户是否关注了该用户
|
||||
IsFollowingMe bool `json:"is_following_me"` // 该用户是否关注了当前用户
|
||||
CreatedAt string `json:"created_at"`
|
||||
}
|
||||
|
||||
// UserDetailResponse 用户详情响应(仅本人可见,包含敏感信息)
|
||||
type UserDetailResponse struct {
|
||||
ID string `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Nickname string `json:"nickname"`
|
||||
Email *string `json:"email,omitempty"`
|
||||
EmailVerified bool `json:"email_verified"`
|
||||
Phone *string `json:"phone,omitempty"`
|
||||
Avatar string `json:"avatar"`
|
||||
CoverURL string `json:"cover_url"`
|
||||
Bio string `json:"bio"`
|
||||
Website string `json:"website"`
|
||||
Location string `json:"location"`
|
||||
PostsCount int `json:"posts_count"`
|
||||
FollowersCount int `json:"followers_count"`
|
||||
FollowingCount int `json:"following_count"`
|
||||
IsVerified bool `json:"is_verified"`
|
||||
IsFollowing bool `json:"is_following"`
|
||||
IsFollowingMe bool `json:"is_following_me"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user