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

View File

@@ -5,6 +5,7 @@ import 'package:logging/logging.dart';
import 'package:userfront/i18n.dart';
import '../../../../core/notifiers/auth_notifier.dart';
import '../../../../core/i18n/locale_utils.dart';
import '../../../../core/services/auth_proxy_service.dart';
import '../../../../core/services/auth_token_store.dart';
import '../../../../core/ui/layout_breakpoints.dart';
import '../../../../core/ui/toast_service.dart';
@@ -54,10 +55,80 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
bool _showCurrentPassword = false;
bool _showNewPassword = false;
bool _showConfirmPassword = false;
Map<String, dynamic>? _passwordPolicy;
bool _isPasswordPolicyLoading = false;
@override
void 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(
@@ -267,6 +338,62 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
);
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) {
setState(
() => _passwordError = tr('msg.userfront.profile.password.mismatch'),
@@ -853,6 +980,11 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
tr('msg.userfront.profile.password.subtitle'),
style: const TextStyle(color: Color(0xFF6B7280)),
),
const SizedBox(height: 8),
Text(
_buildPasswordPolicyDescription(),
style: const TextStyle(color: Color(0xFF6B7280), fontSize: 12),
),
const SizedBox(height: 16),
TextField(
controller: _currentPasswordController,