From 149cf7ea7778767704edab3d8b203af03c311fa9 Mon Sep 17 00:00:00 2001 From: hailin Date: Tue, 10 Mar 2026 19:56:34 -0700 Subject: [PATCH] =?UTF-8?q?fix(auth):=20=E4=BF=AE=E5=A4=8D=E6=89=BE?= =?UTF-8?q?=E5=9B=9E=E5=AF=86=E7=A0=81=E6=B5=81=E7=A8=8B=E7=9A=84=E4=B8=89?= =?UTF-8?q?=E4=B8=AA=20bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Backend: - password.service.ts: resetPassword 补齐验证码暴力破解防护, 输错时累计 attempts,超过 5 次拒绝,与 loginBySms 逻辑一致 Frontend (forgot_password_page.dart): - 发送验证码错误消息改为提取真实 message,不再显示原始异常类名 - 重置密码错误消息同上处理 - 新增 _sendingSms 标志,发送请求期间禁用按钮,防止重复发短信 Co-Authored-By: Claude Sonnet 4.6 --- .../application/services/password.service.ts | 11 ++++++++-- .../pages/auth/forgot_password_page.dart | 21 ++++++++++++++----- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/backend/services/auth-service/src/application/services/password.service.ts b/backend/services/auth-service/src/application/services/password.service.ts index c0bf6bb5..58c45fbd 100644 --- a/backend/services/auth-service/src/application/services/password.service.ts +++ b/backend/services/auth-service/src/application/services/password.service.ts @@ -45,8 +45,15 @@ export class PasswordService { SmsVerificationType.RESET_PASSWORD, ); - if (!verification || verification.code !== dto.smsCode) { - throw new BadRequestException('验证码错误或已过期'); + if (!verification) { + throw new BadRequestException('验证码已过期或不存在'); + } + if (verification.attempts >= 5) { + throw new BadRequestException('验证码尝试次数过多,请重新获取'); + } + if (verification.code !== dto.smsCode) { + await this.smsVerificationRepository.incrementAttempts(verification.id); + throw new BadRequestException('验证码错误'); } // 标记验证码已使用 diff --git a/frontend/mining-app/lib/presentation/pages/auth/forgot_password_page.dart b/frontend/mining-app/lib/presentation/pages/auth/forgot_password_page.dart index 58816b8b..78919971 100644 --- a/frontend/mining-app/lib/presentation/pages/auth/forgot_password_page.dart +++ b/frontend/mining-app/lib/presentation/pages/auth/forgot_password_page.dart @@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../../../core/router/routes.dart'; import '../../../core/constants/app_colors.dart'; +import '../../../core/error/exceptions.dart'; import '../../providers/user_providers.dart'; class ForgotPasswordPage extends ConsumerStatefulWidget { @@ -22,6 +23,7 @@ class _ForgotPasswordPageState extends ConsumerState { bool _obscurePassword = true; bool _obscureConfirmPassword = true; int _countDown = 0; + bool _sendingSms = false; @override void dispose() { @@ -41,21 +43,27 @@ class _ForgotPasswordPageState extends ConsumerState { return; } + setState(() => _sendingSms = true); try { await ref.read(userNotifierProvider.notifier).sendSmsCode(phone, 'RESET_PASSWORD'); - setState(() => _countDown = 60); - _startCountDown(); if (mounted) { + setState(() => _countDown = 60); + _startCountDown(); ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('验证码已发送')), ); } } catch (e) { if (mounted) { + final msg = e is ServerException || e is NetworkException + ? (e as dynamic).message as String + : e.toString().replaceFirst(RegExp(r'^[A-Za-z]+Exception:\s*'), ''); ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('发送失败: $e')), + SnackBar(content: Text(msg)), ); } + } finally { + if (mounted) setState(() => _sendingSms = false); } } @@ -86,8 +94,11 @@ class _ForgotPasswordPageState extends ConsumerState { } } catch (e) { if (mounted) { + final msg = e is ServerException || e is NetworkException + ? (e as dynamic).message as String + : e.toString().replaceFirst(RegExp(r'^[A-Za-z]+Exception:\s*'), ''); ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('重置失败: $e')), + SnackBar(content: Text(msg)), ); } } @@ -191,7 +202,7 @@ class _ForgotPasswordPageState extends ConsumerState { width: 120, height: 56, child: ElevatedButton( - onPressed: _countDown > 0 ? null : _sendSmsCode, + onPressed: (_countDown > 0 || _sendingSms) ? null : _sendSmsCode, style: ElevatedButton.styleFrom( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12),