1
0
forked from baron/baron-sso

프로필 비밀번호 변경 정책 안내 추가

This commit is contained in:
2026-03-31 10:17:27 +09:00
parent 468ca475ed
commit 4d8b9d9f87
2 changed files with 136 additions and 1 deletions

View File

@@ -68,6 +68,7 @@ class _ResetPasswordScreenState extends State<ResetPasswordScreen> {
} }
Future<void> _handlePasswordReset() async { Future<void> _handlePasswordReset() async {
if (_isLoading) return;
if (_formKey.currentState?.validate() != true) return; if (_formKey.currentState?.validate() != true) return;
if ((_loginId == null || _loginId!.isEmpty) && if ((_loginId == null || _loginId!.isEmpty) &&
(_token == null || _token!.isEmpty)) { (_token == null || _token!.isEmpty)) {
@@ -76,6 +77,7 @@ class _ResetPasswordScreenState extends State<ResetPasswordScreen> {
} }
setState(() => _isLoading = true); setState(() => _isLoading = true);
bool isSuccess = false;
try { try {
await AuthProxyService.completePasswordReset( await AuthProxyService.completePasswordReset(
@@ -84,6 +86,7 @@ class _ResetPasswordScreenState extends State<ResetPasswordScreen> {
newPassword: _passwordController.text, newPassword: _passwordController.text,
); );
isSuccess = true;
if (mounted) { if (mounted) {
ToastService.success(tr('msg.userfront.reset.success')); ToastService.success(tr('msg.userfront.reset.success'));
context.go(buildLocalizedSigninPath(Uri.base)); context.go(buildLocalizedSigninPath(Uri.base));
@@ -98,7 +101,7 @@ class _ResetPasswordScreenState extends State<ResetPasswordScreen> {
); );
} }
} finally { } finally {
if (mounted) { if (mounted && !isSuccess) {
setState(() => _isLoading = false); setState(() => _isLoading = false);
} }
} }

View File

@@ -5,6 +5,7 @@ import 'package:logging/logging.dart';
import 'package:userfront/i18n.dart'; import 'package:userfront/i18n.dart';
import '../../../../core/notifiers/auth_notifier.dart'; import '../../../../core/notifiers/auth_notifier.dart';
import '../../../../core/i18n/locale_utils.dart'; import '../../../../core/i18n/locale_utils.dart';
import '../../../../core/services/auth_proxy_service.dart';
import '../../../../core/services/auth_token_store.dart'; import '../../../../core/services/auth_token_store.dart';
import '../../../../core/ui/layout_breakpoints.dart'; import '../../../../core/ui/layout_breakpoints.dart';
import '../../../../core/ui/toast_service.dart'; import '../../../../core/ui/toast_service.dart';
@@ -54,10 +55,80 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
bool _showCurrentPassword = false; bool _showCurrentPassword = false;
bool _showNewPassword = false; bool _showNewPassword = false;
bool _showConfirmPassword = false; bool _showConfirmPassword = false;
Map<String, dynamic>? _passwordPolicy;
bool _isPasswordPolicyLoading = false;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_loadPasswordPolicy();
}
Future<void> _loadPasswordPolicy() async {
setState(() {
_isPasswordPolicyLoading = true;
});
try {
final policy = await AuthProxyService.fetchPasswordPolicy();
if (mounted) {
setState(() {
_passwordPolicy = policy;
});
}
} catch (_) {
// 정책 조회 실패 시 기본 검증 규칙 사용
} finally {
if (mounted) {
setState(() {
_isPasswordPolicyLoading = false;
});
}
}
}
String _buildPasswordPolicyDescription() {
if (_isPasswordPolicyLoading) {
return tr('msg.userfront.signup.policy.loading');
}
final minLength = (_passwordPolicy?['minLength'] as int?) ?? 12;
final minTypes = (_passwordPolicy?['minCharacterTypes'] as int?) ?? 0;
final requiresLower = _passwordPolicy?['lowercase'] ?? true;
final requiresUpper = _passwordPolicy?['uppercase'] ?? false;
final requiresNumber = _passwordPolicy?['number'] ?? true;
final requiresSymbol = _passwordPolicy?['nonAlphanumeric'] ?? true;
final parts = <String>[
tr(
'msg.userfront.signup.policy.min_length',
params: {'count': '$minLength'},
),
];
if (minTypes > 0) {
parts.add(
tr(
'msg.userfront.signup.policy.min_types',
params: {'count': '$minTypes'},
),
);
}
if (requiresLower) {
parts.add(tr('msg.userfront.signup.policy.lowercase'));
}
if (requiresUpper) {
parts.add(tr('msg.userfront.signup.policy.uppercase'));
}
if (requiresNumber) {
parts.add(tr('msg.userfront.signup.policy.number'));
}
if (requiresSymbol) {
parts.add(tr('msg.userfront.signup.policy.symbol'));
}
return tr(
'msg.userfront.signup.policy.summary',
params: {'rules': parts.join(", ")},
);
} }
void _debugLog( void _debugLog(
@@ -267,6 +338,62 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
); );
return; return;
} }
final minLength = (_passwordPolicy?['minLength'] as int?) ?? 12;
final minTypes = (_passwordPolicy?['minCharacterTypes'] as int?) ?? 0;
final hasLower = RegExp(r'[a-z]').hasMatch(newPassword);
final hasUpper = RegExp(r'[A-Z]').hasMatch(newPassword);
final hasNumber = RegExp(r'[0-9]').hasMatch(newPassword);
final hasSymbol = RegExp(r'[\W_]').hasMatch(newPassword);
int typeCount = 0;
if (hasLower) typeCount++;
if (hasUpper) typeCount++;
if (hasNumber) typeCount++;
if (hasSymbol) typeCount++;
if (newPassword.length < minLength) {
setState(
() => _passwordError = tr(
'msg.userfront.reset.error.min_length',
params: {'count': '$minLength'},
),
);
return;
}
if (minTypes > 0 && typeCount < minTypes) {
setState(
() => _passwordError = tr(
'msg.userfront.reset.error.min_types',
params: {'count': '$minTypes'},
),
);
return;
}
if ((_passwordPolicy?['lowercase'] ?? true) && !hasLower) {
setState(
() => _passwordError = tr('msg.userfront.reset.error.lowercase'),
);
return;
}
if ((_passwordPolicy?['uppercase'] ?? false) && !hasUpper) {
setState(
() => _passwordError = tr('msg.userfront.reset.error.uppercase'),
);
return;
}
if ((_passwordPolicy?['number'] ?? true) && !hasNumber) {
setState(
() => _passwordError = tr('msg.userfront.reset.error.number'),
);
return;
}
if ((_passwordPolicy?['nonAlphanumeric'] ?? true) && !hasSymbol) {
setState(
() => _passwordError = tr('msg.userfront.reset.error.symbol'),
);
return;
}
if (newPassword != confirmPassword) { if (newPassword != confirmPassword) {
setState( setState(
() => _passwordError = tr('msg.userfront.profile.password.mismatch'), () => _passwordError = tr('msg.userfront.profile.password.mismatch'),
@@ -853,6 +980,11 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
tr('msg.userfront.profile.password.subtitle'), tr('msg.userfront.profile.password.subtitle'),
style: const TextStyle(color: Color(0xFF6B7280)), style: const TextStyle(color: Color(0xFF6B7280)),
), ),
const SizedBox(height: 8),
Text(
_buildPasswordPolicyDescription(),
style: const TextStyle(color: Color(0xFF6B7280), fontSize: 12),
),
const SizedBox(height: 16), const SizedBox(height: 16),
TextField( TextField(
controller: _currentPasswordController, controller: _currentPasswordController,