forked from baron/baron-sso
리다이렉트 책임 단일화 및 인증 흐름 추적 로그 추가
This commit is contained in:
@@ -1629,6 +1629,9 @@ func (h *AuthHandler) PasswordLogin(c *fiber.Ctx) error {
|
|||||||
logOidcRedirectSummary("password_login", acceptResp.RedirectTo)
|
logOidcRedirectSummary("password_login", acceptResp.RedirectTo)
|
||||||
return c.JSON(fiber.Map{
|
return c.JSON(fiber.Map{
|
||||||
"redirectTo": acceptResp.RedirectTo,
|
"redirectTo": acceptResp.RedirectTo,
|
||||||
|
"sessionJwt": authInfo.SessionToken.JWT,
|
||||||
|
"status": "ok",
|
||||||
|
"provider": h.IdpProvider.Name(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
// --- OIDC 로그인 흐름 처리 끝 ---
|
// --- OIDC 로그인 흐름 처리 끝 ---
|
||||||
|
|||||||
@@ -287,7 +287,9 @@ class AuthProxyService {
|
|||||||
final url = Uri.parse(
|
final url = Uri.parse(
|
||||||
'$_baseUrl/api/v1/auth/consent',
|
'$_baseUrl/api/v1/auth/consent',
|
||||||
).replace(queryParameters: {'consent_challenge': consentChallenge});
|
).replace(queryParameters: {'consent_challenge': consentChallenge});
|
||||||
final response = await http.get(
|
final client = createHttpClient(withCredentials: true);
|
||||||
|
try {
|
||||||
|
final response = await client.get(
|
||||||
url,
|
url,
|
||||||
headers: {'Content-Type': 'application/json'},
|
headers: {'Content-Type': 'application/json'},
|
||||||
);
|
);
|
||||||
@@ -297,9 +299,15 @@ class AuthProxyService {
|
|||||||
} else {
|
} else {
|
||||||
final errorBody = jsonDecode(response.body);
|
final errorBody = jsonDecode(response.body);
|
||||||
throw Exception(
|
throw Exception(
|
||||||
errorBody['error'] ?? tr('err.userfront.auth_proxy.consent_fetch'),
|
errorBody['error'] ??
|
||||||
|
tr(
|
||||||
|
'err.userfront.auth_proxy.consent_fetch',
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
client.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<Map<String, dynamic>> acceptConsent(
|
static Future<Map<String, dynamic>> acceptConsent(
|
||||||
@@ -312,7 +320,9 @@ class AuthProxyService {
|
|||||||
body['grant_scope'] = grantScope;
|
body['grant_scope'] = grantScope;
|
||||||
}
|
}
|
||||||
|
|
||||||
final response = await http.post(
|
final client = createHttpClient(withCredentials: true);
|
||||||
|
try {
|
||||||
|
final response = await client.post(
|
||||||
url,
|
url,
|
||||||
headers: {'Content-Type': 'application/json'},
|
headers: {'Content-Type': 'application/json'},
|
||||||
body: jsonEncode(body),
|
body: jsonEncode(body),
|
||||||
@@ -323,9 +333,15 @@ class AuthProxyService {
|
|||||||
} else {
|
} else {
|
||||||
final errorBody = jsonDecode(response.body);
|
final errorBody = jsonDecode(response.body);
|
||||||
throw Exception(
|
throw Exception(
|
||||||
errorBody['error'] ?? tr('err.userfront.auth_proxy.consent_accept'),
|
errorBody['error'] ??
|
||||||
|
tr(
|
||||||
|
'err.userfront.auth_proxy.consent_accept',
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
client.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<Map<String, dynamic>> rejectConsent(
|
static Future<Map<String, dynamic>> rejectConsent(
|
||||||
@@ -334,7 +350,9 @@ class AuthProxyService {
|
|||||||
final url = Uri.parse('$_baseUrl/api/v1/auth/consent/reject');
|
final url = Uri.parse('$_baseUrl/api/v1/auth/consent/reject');
|
||||||
final body = <String, dynamic>{'consent_challenge': consentChallenge};
|
final body = <String, dynamic>{'consent_challenge': consentChallenge};
|
||||||
|
|
||||||
final response = await http.post(
|
final client = createHttpClient(withCredentials: true);
|
||||||
|
try {
|
||||||
|
final response = await client.post(
|
||||||
url,
|
url,
|
||||||
headers: {'Content-Type': 'application/json'},
|
headers: {'Content-Type': 'application/json'},
|
||||||
body: jsonEncode(body),
|
body: jsonEncode(body),
|
||||||
@@ -345,9 +363,15 @@ class AuthProxyService {
|
|||||||
} else {
|
} else {
|
||||||
final errorBody = jsonDecode(response.body);
|
final errorBody = jsonDecode(response.body);
|
||||||
throw Exception(
|
throw Exception(
|
||||||
errorBody['error'] ?? tr('err.userfront.auth_proxy.consent_reject'),
|
errorBody['error'] ??
|
||||||
|
tr(
|
||||||
|
'err.userfront.auth_proxy.consent_reject',
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
client.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<Map<String, dynamic>> acceptOidcLogin(
|
static Future<Map<String, dynamic>> acceptOidcLogin(
|
||||||
|
|||||||
@@ -709,7 +709,7 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
|
|||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_markVerificationApproved(approvedMessage, actionPath: actionPath);
|
_onLoginSuccess(jwt, provider: res['provider'] as String?);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -777,14 +777,7 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
|
|||||||
_markVerificationApproved(approvedMessage, actionPath: actionPath);
|
_markVerificationApproved(approvedMessage, actionPath: actionPath);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_markVerificationApproved(
|
_onLoginSuccess(jwt, provider: res['provider'] as String?);
|
||||||
linkLoginMessage,
|
|
||||||
title: tr('ui.userfront.login.link.title'),
|
|
||||||
pageTitle: tr('ui.userfront.login.link.page_title'),
|
|
||||||
actionLabel: tr('ui.userfront.login.link.action_label'),
|
|
||||||
actionPath: '/signin',
|
|
||||||
autoRedirect: true,
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -844,7 +837,7 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
|
|||||||
_markVerificationApproved(approvedMessage, actionPath: actionPath);
|
_markVerificationApproved(approvedMessage, actionPath: actionPath);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_completeLoginFromToken(jwt, provider: res['provider'] as String?);
|
_onLoginSuccess(jwt, provider: res['provider'] as String?);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -879,6 +872,7 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _handlePasswordLogin() async {
|
Future<void> _handlePasswordLogin() async {
|
||||||
|
print("[Auth] _handlePasswordLogin START");
|
||||||
final input = _passwordLoginIdController.text.trim();
|
final input = _passwordLoginIdController.text.trim();
|
||||||
final password = _passwordController.text.trim();
|
final password = _passwordController.text.trim();
|
||||||
if (input.isEmpty || password.isEmpty) {
|
if (input.isEmpty || password.isEmpty) {
|
||||||
@@ -895,65 +889,29 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final challengeResolution = _resolveLoginChallenge(Uri.base);
|
print("[Auth] Calling AuthProxyService.loginWithPassword...");
|
||||||
if (!_hasLoginChallenge && challengeResolution.value != null) {
|
|
||||||
_loginChallenge = challengeResolution.value;
|
|
||||||
}
|
|
||||||
_logLoginChallengeDiagnostics(
|
|
||||||
phase: 'password_submit',
|
|
||||||
resolution: challengeResolution,
|
|
||||||
);
|
|
||||||
|
|
||||||
final res = await AuthProxyService.loginWithPassword(
|
final res = await AuthProxyService.loginWithPassword(
|
||||||
loginId,
|
loginId,
|
||||||
password,
|
password,
|
||||||
loginChallenge: _loginChallenge,
|
loginChallenge: _loginChallenge,
|
||||||
);
|
);
|
||||||
final jwtRaw = res['sessionJwt'] ?? res['sessionToken'] ?? res['token'];
|
print("[Auth] loginWithPassword response: $res");
|
||||||
final jwt = jwtRaw?.toString();
|
|
||||||
|
final jwt = res['sessionJwt'] ?? res['sessionToken'] ?? res['token'];
|
||||||
final provider = res['provider'] as String?;
|
final provider = res['provider'] as String?;
|
||||||
final redirectTo = res['redirectTo'] as String?;
|
final redirectTo = res['redirectTo'] as String?;
|
||||||
final hasJwt = jwt != null && jwt.isNotEmpty;
|
|
||||||
final nextAction = decidePasswordLoginNextAction(
|
|
||||||
hasLoginChallenge: _hasLoginChallenge,
|
|
||||||
redirectTo: redirectTo,
|
|
||||||
jwt: jwt,
|
|
||||||
);
|
|
||||||
|
|
||||||
debugPrint(
|
if (jwt != null) {
|
||||||
"[Auth] Password login outcome: has_login_challenge=$_hasLoginChallenge, next_action=$nextAction, has_jwt=$hasJwt",
|
print("[Auth] JWT found, calling _onLoginSuccess. RedirectTo: $redirectTo");
|
||||||
);
|
_onLoginSuccess(jwt, provider: provider, redirectTo: redirectTo);
|
||||||
if (!_hasLoginChallenge) {
|
} else if (redirectTo != null && redirectTo.isNotEmpty) {
|
||||||
debugPrint(
|
print("[Auth] Only redirectTo found. Redirecting...");
|
||||||
"[Auth] WARNING: password login proceeded without login_challenge; treated as local login flow",
|
webWindow.redirectTo(redirectTo);
|
||||||
);
|
} else {
|
||||||
}
|
print("[Auth] No JWT and no redirectTo found.");
|
||||||
|
|
||||||
switch (nextAction) {
|
|
||||||
case PasswordLoginNextAction.redirectToOidc:
|
|
||||||
_redirectToOidcTarget(redirectTo!, source: 'password_login');
|
|
||||||
return;
|
|
||||||
case PasswordLoginNextAction.acceptOidc:
|
|
||||||
final accepted = await _acceptOidcLoginAndRedirect(
|
|
||||||
token: hasJwt ? jwt : null,
|
|
||||||
);
|
|
||||||
if (accepted) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (mounted) {
|
|
||||||
_showError(tr('msg.userfront.login.oidc_failed'));
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
case PasswordLoginNextAction.localLogin:
|
|
||||||
_onLoginSuccess(jwt!, provider: provider);
|
|
||||||
return;
|
|
||||||
case PasswordLoginNextAction.invalid:
|
|
||||||
if (mounted) {
|
|
||||||
_showError(tr('msg.userfront.login.password.failed'));
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
print("[Auth] _handlePasswordLogin Error: $e");
|
||||||
if (e.toString().contains("User not registered")) {
|
if (e.toString().contains("User not registered")) {
|
||||||
_showUnregisteredDialog();
|
_showUnregisteredDialog();
|
||||||
} else {
|
} else {
|
||||||
@@ -1175,9 +1133,67 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onLoginSuccess(String token, {String? provider}) async {
|
Future<void> _onLoginSuccess(String token, {String? provider, String? redirectTo}) async {
|
||||||
if (!mounted) return;
|
print("[Auth] _onLoginSuccess ENTRY. RedirectTo: $redirectTo, Token len: ${token.length}");
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!mounted) {
|
||||||
|
print("[Auth] _onLoginSuccess: Not mounted, returning.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// [Priority 1] Immediate External Redirection
|
||||||
|
if (redirectTo != null && redirectTo.isNotEmpty) {
|
||||||
|
print("[Auth] _onLoginSuccess: Has redirectTo. Saving token and redirecting...");
|
||||||
|
try {
|
||||||
|
final providerName = provider ?? AuthTokenStore.getProvider();
|
||||||
|
print("[Auth] _onLoginSuccess: Provider resolved: $providerName");
|
||||||
|
AuthTokenStore.setToken(token, provider: providerName);
|
||||||
|
print("[Auth] _onLoginSuccess: Token saved to store.");
|
||||||
|
} catch (stErr) {
|
||||||
|
print("[Auth] _onLoginSuccess: FAILED to save token: $stErr");
|
||||||
|
}
|
||||||
|
|
||||||
|
print("[Auth] Calling webWindow.redirectTo: $redirectTo");
|
||||||
|
webWindow.redirectTo(redirectTo); // Removed await as it's void
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// [Priority 2] OIDC Challenge Handling
|
||||||
|
if (_loginChallenge != null && _loginChallenge!.isNotEmpty) {
|
||||||
|
print("[Auth] _onLoginSuccess: Has loginChallenge. Attempting auto-accept...");
|
||||||
|
try {
|
||||||
|
// Save token first, it's needed for acceptance
|
||||||
|
final providerName = provider ?? AuthTokenStore.getProvider();
|
||||||
|
AuthTokenStore.setToken(token, provider: providerName);
|
||||||
|
print("[Auth] _onLoginSuccess: Token saved for auto-accept.");
|
||||||
|
|
||||||
|
final res = await AuthProxyService.acceptOidcLogin(
|
||||||
|
_loginChallenge!,
|
||||||
|
token: token,
|
||||||
|
);
|
||||||
|
final nextRedirectTo = res['redirectTo'] as String?;
|
||||||
|
print("[Auth] Auto-accept response: $res");
|
||||||
|
|
||||||
|
if (nextRedirectTo != null && nextRedirectTo.isNotEmpty) {
|
||||||
|
print("[Auth] OIDC login accepted. Redirecting to: $nextRedirectTo");
|
||||||
|
webWindow.redirectTo(nextRedirectTo); // Removed await
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
print("[Auth] Auto-accept successful but no redirectTo provided.");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print("[Auth] Auto-accept failed: $e");
|
||||||
|
_showError(
|
||||||
|
tr(
|
||||||
|
'msg.userfront.login.oidc_failed',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
print("[Auth] _onLoginSuccess: Standard Login Flow");
|
||||||
_logTokenDetails(token);
|
_logTokenDetails(token);
|
||||||
|
|
||||||
final providerName = provider ?? AuthTokenStore.getProvider();
|
final providerName = provider ?? AuthTokenStore.getProvider();
|
||||||
@@ -1189,44 +1205,32 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
|
|||||||
try {
|
try {
|
||||||
await ref.read(profileProvider.notifier).loadProfile();
|
await ref.read(profileProvider.notifier).loadProfile();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint("[Auth] Failed to pre-fetch profile: $e");
|
print("[Auth] Failed to pre-fetch profile: $e");
|
||||||
}
|
|
||||||
|
|
||||||
if (_hasLoginChallenge) {
|
|
||||||
try {
|
|
||||||
final accepted = await _acceptOidcLoginAndRedirect(token: token);
|
|
||||||
if (accepted) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (mounted) {
|
|
||||||
_showError(tr('msg.userfront.login.oidc_failed'));
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
} catch (e) {
|
|
||||||
_showError(tr('msg.userfront.login.oidc_failed'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final uri = Uri.base;
|
final uri = Uri.base;
|
||||||
final redirectParam =
|
final redirectParam =
|
||||||
uri.queryParameters['redirect_uri'] ??
|
uri.queryParameters['redirect_uri'] ?? uri.queryParameters['redirect_url'];
|
||||||
uri.queryParameters['redirect_url'];
|
final hasRedirectParam =
|
||||||
final hasRedirectParam = redirectParam != null && redirectParam.isNotEmpty;
|
redirectParam != null && redirectParam.isNotEmpty;
|
||||||
|
|
||||||
if (WebAuthIntegration.isPopup() || hasRedirectParam) {
|
if (WebAuthIntegration.isPopup() || hasRedirectParam) {
|
||||||
debugPrint(
|
print(
|
||||||
"[Auth] External integration detected (popup or redirect). Notifying...",
|
"[Auth] External integration detected (popup or redirect). Notifying...",
|
||||||
);
|
);
|
||||||
WebAuthIntegration.sendLoginSuccess(token);
|
WebAuthIntegration.sendLoginSuccess(token);
|
||||||
|
AuthNotifier.instance.notify();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
debugPrint("[Auth] Login success. Navigating to root.");
|
print("[Auth] Login success. Navigating to root.");
|
||||||
AuthNotifier.instance.notify();
|
AuthNotifier.instance.notify();
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
context.go('/');
|
context.go('/');
|
||||||
}
|
}
|
||||||
|
} catch (globalErr) {
|
||||||
|
print("[Auth] CRITICAL ERROR in _onLoginSuccess: $globalErr");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _showUnregisteredDialog() {
|
void _showUnregisteredDialog() {
|
||||||
|
|||||||
Reference in New Issue
Block a user