1
0
forked from baron/baron-sso

리다이렉트 후속 로직 업데이트

This commit is contained in:
Lectom C Han
2026-02-19 12:40:56 +09:00
parent 1a5b04d688
commit 6fd0e5c800
6 changed files with 228 additions and 200 deletions

View File

@@ -3,13 +3,13 @@ import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:url_launcher/url_launcher_string.dart';
import 'package:qr_flutter/qr_flutter.dart';
import 'package:userfront/i18n.dart';
import '../../../core/widgets/language_selector.dart';
import '../../../core/services/web_auth_integration.dart';
import '../../../core/services/auth_proxy_service.dart';
import '../../../core/services/auth_token_store.dart';
import '../../../core/services/oidc_redirect_guard.dart';
import '../../../core/notifiers/auth_notifier.dart';
import '../../profile/domain/notifiers/profile_notifier.dart';
import '../../../core/services/web_window.dart';
@@ -65,9 +65,7 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
bool _verificationApproved = false;
bool _dismissedOverlays = false;
String _verificationMessage = '';
String _verificationTitle = tr(
'ui.userfront.login.verification.title',
);
String _verificationTitle = tr('ui.userfront.login.verification.title');
String _verificationPageTitle = tr(
'ui.userfront.login.verification.page_title',
);
@@ -127,11 +125,7 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
if (!_noticeHandled && notice == 'qr_login_required') {
_noticeHandled = true;
_showInfo(
tr(
'msg.userfront.login.qr_login_required',
),
);
_showInfo(tr('msg.userfront.login.qr_login_required'));
}
if (!_verificationOnly) {
@@ -234,9 +228,7 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
);
final redirectTo = res['redirectTo'] as String?;
if (redirectTo != null && redirectTo.isNotEmpty) {
debugPrint("[Auth] OIDC login accepted. Redirecting to: $redirectTo");
webWindow.redirectTo(redirectTo);
return true;
return _redirectToOidcTarget(redirectTo, source: 'accept_oidc_login');
}
} catch (e) {
debugPrint("[Auth] OIDC login auto-accept failed: $e");
@@ -244,6 +236,23 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
return false;
}
bool _redirectToOidcTarget(String redirectTo, {required String source}) {
final checked = validateOidcRedirectTarget(redirectTo);
debugPrint(
"[Auth] OIDC redirect check ($source): valid=${checked.isValid}, reason=${checked.reason}, len=${checked.length}, host=${checked.host}, path=${checked.path}, has_login_verifier=${checked.hasLoginVerifier}",
);
if (!checked.isValid || checked.uri == null) {
if (mounted) {
_showError(tr('msg.userfront.login.oidc_failed'));
}
return false;
}
webWindow.redirectTo(checked.uri.toString());
return true;
}
void _resetLinkLoginState() {
_linkPendingRef = null;
_lastLinkLoginId = null;
@@ -307,9 +316,7 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
setState(() {
_linkExpired = true;
});
_showInfo(
tr('msg.userfront.login.link_timeout'),
);
_showInfo(tr('msg.userfront.login.link_timeout'));
}
});
}
@@ -387,9 +394,7 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
setState(() {
_qrExpired = true;
});
_showInfo(
tr('msg.userfront.login.qr_expired'),
);
_showInfo(tr('msg.userfront.login.qr_expired'));
}
}
return;
@@ -440,9 +445,7 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
_qrExpired = true;
});
}
_showError(
tr('msg.userfront.login.qr_expired'),
);
_showError(tr('msg.userfront.login.qr_expired'));
return;
}
@@ -454,11 +457,7 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
if (token is String && token.isNotEmpty) {
_completeLoginFromToken(token);
} else {
_showError(
tr(
'msg.userfront.login.token_missing',
),
);
_showError(tr('msg.userfront.login.token_missing'));
}
}
} catch (e) {
@@ -533,14 +532,11 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
Duration redirectDelay = const Duration(seconds: 2),
}) {
if (!mounted) return;
final resolvedTitle =
title ?? tr('ui.userfront.login.verification.title');
final resolvedTitle = title ?? tr('ui.userfront.login.verification.title');
final resolvedPageTitle =
pageTitle ??
tr('ui.userfront.login.verification.page_title');
pageTitle ?? tr('ui.userfront.login.verification.page_title');
final resolvedActionLabel =
actionLabel ??
tr('ui.userfront.login.verification.action_label');
actionLabel ?? tr('ui.userfront.login.verification.action_label');
setState(() {
_verificationApproved = true;
_verificationMessage = message;
@@ -581,9 +577,7 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
const SizedBox(height: 12),
Text(
_verificationMessage.isEmpty
? tr(
'msg.userfront.login.verification.success',
)
? tr('msg.userfront.login.verification.success')
: _verificationMessage,
textAlign: TextAlign.center,
style: const TextStyle(color: Colors.black54),
@@ -613,9 +607,7 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
Future<void> _verifyToken(String token) async {
debugPrint("[Auth] Starting verification for token: $token");
final approvedMessage = tr(
'msg.userfront.login.verification.approved',
);
final approvedMessage = tr('msg.userfront.login.verification.approved');
final localSessionMessage = tr(
'msg.userfront.login.verification.approved_local',
);
@@ -675,15 +667,11 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
debugPrint(
"[Auth] Starting code verification for loginId: $sanitizedLoginId",
);
final approvedMessage = tr(
'msg.userfront.login.verification.approved',
);
final approvedMessage = tr('msg.userfront.login.verification.approved');
final localSessionMessage = tr(
'msg.userfront.login.verification.approved_local',
);
final linkLoginMessage = tr(
'msg.userfront.login.link.approved',
);
final linkLoginMessage = tr('msg.userfront.login.link.approved');
try {
final res = await AuthProxyService.verifyLoginCode(
sanitizedLoginId,
@@ -721,12 +709,8 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
_markVerificationApproved(
linkLoginMessage,
title: tr('ui.userfront.login.link.title'),
pageTitle: tr(
'ui.userfront.login.link.page_title',
),
actionLabel: tr(
'ui.userfront.login.link.action_label',
),
pageTitle: tr('ui.userfront.login.link.page_title'),
actionLabel: tr('ui.userfront.login.link.action_label'),
actionPath: '/signin',
autoRedirect: true,
);
@@ -755,9 +739,7 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
final sanitized = shortCode.trim().toUpperCase();
if (sanitized.isEmpty) return;
debugPrint("[Auth] Starting short code verification for code: $sanitized");
final approvedMessage = tr(
'msg.userfront.login.verification.approved',
);
final approvedMessage = tr('msg.userfront.login.verification.approved');
final localSessionMessage = tr(
'msg.userfront.login.verification.approved_local',
);
@@ -829,11 +811,7 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
final input = _passwordLoginIdController.text.trim();
final password = _passwordController.text.trim();
if (input.isEmpty || password.isEmpty) {
_showError(
tr(
'msg.userfront.login.password.missing_credentials',
),
);
_showError(tr('msg.userfront.login.password.missing_credentials'));
return;
}
@@ -856,7 +834,7 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
final redirectTo = res['redirectTo'] as String?;
if (redirectTo != null && redirectTo.isNotEmpty) {
webWindow.redirectTo(redirectTo);
_redirectToOidcTarget(redirectTo, source: 'password_login');
return;
}
@@ -938,12 +916,8 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
_dismissOverlays();
_showInfo(
isEmail
? tr(
'msg.userfront.login.link_sent_email',
)
: tr(
'msg.userfront.login.link_sent_phone',
),
? tr('msg.userfront.login.link_sent_email')
: tr('msg.userfront.login.link_sent_phone'),
);
final initialInterval = (interval is int && interval > 0)
@@ -1009,11 +983,7 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
if (result['error'] == 'expired_token') {
if (mounted) {
Navigator.of(context).pop();
_showError(
tr(
'msg.userfront.login.link_timeout',
),
);
_showError(tr('msg.userfront.login.link_timeout'));
}
return;
}
@@ -1034,11 +1004,7 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
if (mounted && Navigator.canPop(context)) {
Navigator.of(context).pop();
}
_showError(
tr(
'msg.userfront.login.token_missing',
),
);
_showError(tr('msg.userfront.login.token_missing'));
return;
}
} catch (e) {
@@ -1049,9 +1015,7 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
if (mounted) {
debugPrint("[Auth] Polling timed out for ref: $pendingRef");
Navigator.of(context).pop();
_showError(
tr('msg.userfront.login.link_timeout'),
);
_showError(tr('msg.userfront.login.link_timeout'));
}
}
@@ -1124,25 +1088,23 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
);
final redirectTo = res['redirectTo'] as String?;
if (redirectTo != null && redirectTo.isNotEmpty) {
debugPrint("[Auth] OIDC login accepted. Redirecting to: $redirectTo");
webWindow.redirectTo(redirectTo);
_redirectToOidcTarget(
redirectTo,
source: 'on_login_success_accept_oidc',
);
return;
}
} catch (e) {
_showError(
tr(
'msg.userfront.login.oidc_failed',
),
);
_showError(tr('msg.userfront.login.oidc_failed'));
return;
}
}
final uri = Uri.base;
final redirectParam =
uri.queryParameters['redirect_uri'] ?? uri.queryParameters['redirect_url'];
final hasRedirectParam =
redirectParam != null && redirectParam.isNotEmpty;
uri.queryParameters['redirect_uri'] ??
uri.queryParameters['redirect_url'];
final hasRedirectParam = redirectParam != null && redirectParam.isNotEmpty;
if (WebAuthIntegration.isPopup() || hasRedirectParam) {
debugPrint(
@@ -1163,14 +1125,8 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text(
tr('ui.userfront.login.unregistered.title'),
),
content: Text(
tr(
'msg.userfront.login.unregistered.body',
),
),
title: Text(tr('ui.userfront.login.unregistered.title')),
content: Text(tr('msg.userfront.login.unregistered.body')),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
@@ -1182,9 +1138,7 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
_resetLinkLoginState();
context.push('/signup');
},
child: Text(
tr('ui.userfront.login.unregistered.action'),
),
child: Text(tr('ui.userfront.login.unregistered.action')),
),
],
),
@@ -1249,9 +1203,7 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
const SizedBox(width: 8),
Expanded(
child: Text(
tr(
'msg.userfront.login.dry_send',
),
tr('msg.userfront.login.dry_send'),
style: const TextStyle(
color: Color(0xFF8A6D3B),
fontSize: 12,
@@ -1267,21 +1219,9 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
TabBar(
controller: _tabController,
tabs: [
Tab(
text: tr(
'ui.userfront.login.tabs.password',
),
),
Tab(
text: tr(
'ui.userfront.login.tabs.link',
),
),
Tab(
text: tr(
'ui.userfront.login.tabs.qr',
),
),
Tab(text: tr('ui.userfront.login.tabs.password')),
Tab(text: tr('ui.userfront.login.tabs.link')),
Tab(text: tr('ui.userfront.login.tabs.qr')),
],
),
const SizedBox(height: 24),
@@ -1330,9 +1270,7 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
minimumSize: const Size.fromHeight(50),
),
child: Text(
tr(
'ui.userfront.login.action.submit',
),
tr('ui.userfront.login.action.submit'),
),
),
],
@@ -1365,16 +1303,12 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
minimumSize: const Size.fromHeight(50),
),
child: Text(
tr(
'ui.userfront.login.link.send',
),
tr('ui.userfront.login.link.send'),
),
),
const SizedBox(height: 24),
Text(
tr(
'msg.userfront.login.link.helper',
),
tr('msg.userfront.login.link.helper'),
style: const TextStyle(
color: Colors.grey,
fontSize: 12,
@@ -1398,8 +1332,9 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
setState(_resetLinkLoginState);
},
style: FilledButton.styleFrom(
minimumSize:
const Size.fromHeight(45),
minimumSize: const Size.fromHeight(
45,
),
),
child: Text(tr('ui.common.refresh')),
),
@@ -1458,15 +1393,15 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
),
suffixText:
_linkExpireSeconds > 0
? tr(
'ui.userfront.login.short_code.expire_time',
params: {
'time': _formatTime(
_linkExpireSeconds,
),
},
)
: null,
? tr(
'ui.userfront.login.short_code.expire_time',
params: {
'time': _formatTime(
_linkExpireSeconds,
),
},
)
: null,
),
maxLength: 6,
),
@@ -1495,8 +1430,9 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
_verifyShortCode(prefix + digits);
},
style: FilledButton.styleFrom(
minimumSize:
const Size.fromHeight(45),
minimumSize: const Size.fromHeight(
45,
),
),
child: Text(
tr(
@@ -1549,9 +1485,7 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
),
},
)
: tr(
'ui.common.resend',
),
: tr('ui.common.resend'),
),
),
if (!_lastLinkIsEmail) ...[
@@ -1627,8 +1561,9 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
FilledButton(
onPressed: _startQrFlow,
style: FilledButton.styleFrom(
minimumSize:
const Size.fromHeight(45),
minimumSize: const Size.fromHeight(
45,
),
),
child: Text(tr('ui.common.refresh')),
),
@@ -1679,9 +1614,7 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
),
const SizedBox(height: 8),
Text(
tr(
'msg.userfront.login.qr.scan_hint',
),
tr('msg.userfront.login.qr.scan_hint'),
textAlign: TextAlign.center,
style: const TextStyle(
color: Colors.grey,
@@ -1691,18 +1624,14 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
TextButton(
onPressed: _startQrFlow,
child: Text(
tr(
'ui.userfront.login.qr.refresh',
),
tr('ui.userfront.login.qr.refresh'),
),
),
],
)
else
Text(
tr(
'msg.userfront.login.qr.load_failed',
),
tr('msg.userfront.login.qr.load_failed'),
textAlign: TextAlign.center,
),
],
@@ -1716,18 +1645,14 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
TextButton(
onPressed: () => context.push('/forgot-password'),
child: Text(
tr(
'ui.userfront.login.forgot_password',
),
tr('ui.userfront.login.forgot_password'),
),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
tr(
'msg.userfront.login.no_account',
),
tr('msg.userfront.login.no_account'),
style: const TextStyle(
color: Colors.grey,
fontSize: 14,
@@ -1735,11 +1660,7 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
),
TextButton(
onPressed: () => context.push('/signup'),
child: Text(
tr(
'ui.userfront.login.signup',
),
),
child: Text(tr('ui.userfront.login.signup')),
),
],
),