fix: P0-04 prevent password reset code replay attack

ResetPasswordByPhone and ResetPassword now immediately consume
(delete) the verification code/token after successful validation,
before proceeding with password reset. This prevents replay attacks
where the same code could be used multiple times.

Security fix:验证码/Token验证通过后立即删除,防止Replay攻击
This commit is contained in:
2026-04-18 10:26:36 +08:00
parent bb7c5e7fe2
commit bba44e820a

View File

@@ -113,6 +113,12 @@ func (s *PasswordResetService) ResetPassword(ctx context.Context, token, newPass
return errors.New("重置Token数据异常")
}
// 安全修复: 验证通过后立即删除Token防止Replay攻击
// Token消耗后立即失效避免密码重置被重复触发
if err := s.cache.Delete(ctx, cacheKey); err != nil {
return fmt.Errorf("清理重置Token失败: %w", err)
}
user, err := s.userRepo.GetByID(ctx, userID)
if err != nil {
return errors.New("用户不存在")
@@ -122,9 +128,6 @@ func (s *PasswordResetService) ResetPassword(ctx context.Context, token, newPass
return err
}
if err := s.cache.Delete(ctx, cacheKey); err != nil {
return fmt.Errorf("清理重置Token失败: %w", err)
}
return nil
}
@@ -229,6 +232,10 @@ func (s *PasswordResetService) ResetPasswordByPhone(ctx context.Context, req *Re
return errors.New("验证码不正确")
}
// 安全修复: 验证通过后立即删除验证码防止Replay攻击
// 在密码重置完成前消耗验证码,确保同一验证码只能使用一次
s.cache.Delete(ctx, codeKey)
// 获取用户ID
cacheKey := fmt.Sprintf("pwd_reset_sms:%s", req.Phone)
val, ok := s.cache.Get(ctx, cacheKey)
@@ -241,6 +248,9 @@ func (s *PasswordResetService) ResetPasswordByPhone(ctx context.Context, req *Re
return errors.New("验证码数据异常")
}
// 安全修复: 立即删除手机->用户ID映射防止重复使用
s.cache.Delete(ctx, cacheKey)
user, err := s.userRepo.GetByID(ctx, userID)
if err != nil {
return errors.New("用户不存在")
@@ -250,10 +260,6 @@ func (s *PasswordResetService) ResetPasswordByPhone(ctx context.Context, req *Re
return err
}
// 清理验证码
s.cache.Delete(ctx, codeKey)
s.cache.Delete(ctx, cacheKey)
return nil
}