fix(auth): 修复找回密码流程的三个 bug
Backend: - password.service.ts: resetPassword 补齐验证码暴力破解防护, 输错时累计 attempts,超过 5 次拒绝,与 loginBySms 逻辑一致 Frontend (forgot_password_page.dart): - 发送验证码错误消息改为提取真实 message,不再显示原始异常类名 - 重置密码错误消息同上处理 - 新增 _sendingSms 标志,发送请求期间禁用按钮,防止重复发短信 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
761dcb1115
commit
149cf7ea77
|
|
@ -45,8 +45,15 @@ export class PasswordService {
|
||||||
SmsVerificationType.RESET_PASSWORD,
|
SmsVerificationType.RESET_PASSWORD,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!verification || verification.code !== dto.smsCode) {
|
if (!verification) {
|
||||||
throw new BadRequestException('验证码错误或已过期');
|
throw new BadRequestException('验证码已过期或不存在');
|
||||||
|
}
|
||||||
|
if (verification.attempts >= 5) {
|
||||||
|
throw new BadRequestException('验证码尝试次数过多,请重新获取');
|
||||||
|
}
|
||||||
|
if (verification.code !== dto.smsCode) {
|
||||||
|
await this.smsVerificationRepository.incrementAttempts(verification.id);
|
||||||
|
throw new BadRequestException('验证码错误');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 标记验证码已使用
|
// 标记验证码已使用
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import '../../../core/router/routes.dart';
|
import '../../../core/router/routes.dart';
|
||||||
import '../../../core/constants/app_colors.dart';
|
import '../../../core/constants/app_colors.dart';
|
||||||
|
import '../../../core/error/exceptions.dart';
|
||||||
import '../../providers/user_providers.dart';
|
import '../../providers/user_providers.dart';
|
||||||
|
|
||||||
class ForgotPasswordPage extends ConsumerStatefulWidget {
|
class ForgotPasswordPage extends ConsumerStatefulWidget {
|
||||||
|
|
@ -22,6 +23,7 @@ class _ForgotPasswordPageState extends ConsumerState<ForgotPasswordPage> {
|
||||||
bool _obscurePassword = true;
|
bool _obscurePassword = true;
|
||||||
bool _obscureConfirmPassword = true;
|
bool _obscureConfirmPassword = true;
|
||||||
int _countDown = 0;
|
int _countDown = 0;
|
||||||
|
bool _sendingSms = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
|
@ -41,21 +43,27 @@ class _ForgotPasswordPageState extends ConsumerState<ForgotPasswordPage> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setState(() => _sendingSms = true);
|
||||||
try {
|
try {
|
||||||
await ref.read(userNotifierProvider.notifier).sendSmsCode(phone, 'RESET_PASSWORD');
|
await ref.read(userNotifierProvider.notifier).sendSmsCode(phone, 'RESET_PASSWORD');
|
||||||
setState(() => _countDown = 60);
|
|
||||||
_startCountDown();
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
|
setState(() => _countDown = 60);
|
||||||
|
_startCountDown();
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(content: Text('验证码已发送')),
|
const SnackBar(content: Text('验证码已发送')),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (mounted) {
|
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(
|
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<ForgotPasswordPage> {
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (mounted) {
|
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(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(content: Text('重置失败: $e')),
|
SnackBar(content: Text(msg)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -191,7 +202,7 @@ class _ForgotPasswordPageState extends ConsumerState<ForgotPasswordPage> {
|
||||||
width: 120,
|
width: 120,
|
||||||
height: 56,
|
height: 56,
|
||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
onPressed: _countDown > 0 ? null : _sendSmsCode,
|
onPressed: (_countDown > 0 || _sendingSms) ? null : _sendSmsCode,
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue