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:
@@ -14,8 +14,10 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
verifyCodeTTL = 10 * time.Minute
|
||||
verifyCodeRateLimitTTL = 60 * time.Second
|
||||
verifyCodeTTL = 10 * time.Minute
|
||||
verifyCodeRateLimitTTL = 60 * time.Second
|
||||
verifyCodeIPRateLimitTTL = 60 * time.Second
|
||||
verifyCodeIPRateLimitCount = 5
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -33,7 +35,7 @@ type verificationCodePayload struct {
|
||||
}
|
||||
|
||||
type EmailCodeService interface {
|
||||
SendCode(ctx context.Context, purpose, email string) error
|
||||
SendCode(ctx context.Context, purpose, email, clientIP string) error
|
||||
VerifyCode(purpose, email, code string) error
|
||||
}
|
||||
|
||||
@@ -57,6 +59,10 @@ func verificationCodeRateLimitKey(purpose, email string) string {
|
||||
return fmt.Sprintf("auth:verify_code_rate_limit:%s:%s", purpose, strings.ToLower(strings.TrimSpace(email)))
|
||||
}
|
||||
|
||||
func verificationCodeIPRateLimitKey(clientIP string) string {
|
||||
return fmt.Sprintf("auth:verify_code_ip_rate_limit:%s", clientIP)
|
||||
}
|
||||
|
||||
func generateNumericCode(length int) (string, error) {
|
||||
if length <= 0 {
|
||||
return "", fmt.Errorf("invalid code length")
|
||||
@@ -73,7 +79,7 @@ func generateNumericCode(length int) (string, error) {
|
||||
return string(result), nil
|
||||
}
|
||||
|
||||
func (s *emailCodeServiceImpl) SendCode(ctx context.Context, purpose, email string) error {
|
||||
func (s *emailCodeServiceImpl) SendCode(ctx context.Context, purpose, email, clientIP string) error {
|
||||
if strings.TrimSpace(email) == "" || !utils.ValidateEmail(email) {
|
||||
return ErrInvalidEmail
|
||||
}
|
||||
@@ -89,6 +95,15 @@ func (s *emailCodeServiceImpl) SendCode(ctx context.Context, purpose, email stri
|
||||
return ErrVerificationCodeTooFrequent
|
||||
}
|
||||
|
||||
if clientIP != "" {
|
||||
ipRateLimitKey := verificationCodeIPRateLimitKey(clientIP)
|
||||
if count, ok := s.cache.Get(ipRateLimitKey); ok {
|
||||
if c, ok := count.(int); ok && c >= verifyCodeIPRateLimitCount {
|
||||
return ErrVerificationCodeTooFrequent
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
code, err := generateNumericCode(6)
|
||||
if err != nil {
|
||||
return fmt.Errorf("generate verification code failed: %w", err)
|
||||
@@ -103,6 +118,19 @@ func (s *emailCodeServiceImpl) SendCode(ctx context.Context, purpose, email stri
|
||||
s.cache.Set(cacheKey, payload, verifyCodeTTL)
|
||||
s.cache.Set(rateLimitKey, "1", verifyCodeRateLimitTTL)
|
||||
|
||||
if clientIP != "" {
|
||||
ipRateLimitKey := verificationCodeIPRateLimitKey(clientIP)
|
||||
if count, ok := s.cache.Get(ipRateLimitKey); ok {
|
||||
if c, ok := count.(int); ok {
|
||||
s.cache.Set(ipRateLimitKey, c+1, verifyCodeIPRateLimitTTL)
|
||||
} else {
|
||||
s.cache.Set(ipRateLimitKey, 1, verifyCodeIPRateLimitTTL)
|
||||
}
|
||||
} else {
|
||||
s.cache.Set(ipRateLimitKey, 1, verifyCodeIPRateLimitTTL)
|
||||
}
|
||||
}
|
||||
|
||||
subject, sceneText := verificationEmailMeta(purpose)
|
||||
textBody := fmt.Sprintf("【%s】验证码:%s\n有效期:10分钟\n请勿将验证码泄露给他人。", sceneText, code)
|
||||
htmlBody := buildVerificationEmailHTML(sceneText, code)
|
||||
|
||||
Reference in New Issue
Block a user