diff --git a/backend/internal/handler/auth_handler.go b/backend/internal/handler/auth_handler.go index 78e21a21..2b1e3494 100644 --- a/backend/internal/handler/auth_handler.go +++ b/backend/internal/handler/auth_handler.go @@ -3015,11 +3015,15 @@ func (h *AuthHandler) GetAuthTimeline(c *fiber.Ctx) error { if subject != "" && h.Hydra != nil { if sessions, err := h.Hydra.ListConsentSessions(c.Context(), subject, ""); err == nil { for _, session := range sessions { - clientID := strings.TrimSpace(session.Client.ClientID) + client := session.Client + if client.ClientID == "" && session.ConsentRequest != nil { + client = session.ConsentRequest.Client + } + clientID := strings.TrimSpace(client.ClientID) if clientID == "" { continue } - name := strings.TrimSpace(session.Client.ClientName) + name := strings.TrimSpace(client.ClientName) if name == "" { name = clientID } @@ -3028,6 +3032,8 @@ func (h *AuthHandler) GetAuthTimeline(c *fiber.Ctx) error { consentAt = *session.AuthenticatedAt } else if session.RequestedAt != nil { consentAt = *session.RequestedAt + } else if session.HandledAt != nil { + consentAt = *session.HandledAt } if existing, ok := consentMap[clientID]; ok { if !consentAt.IsZero() && (existing.ConsentAt.IsZero() || consentAt.Before(existing.ConsentAt)) { diff --git a/userfront/lib/features/auth/presentation/login_screen.dart b/userfront/lib/features/auth/presentation/login_screen.dart index 7b20ad69..d2dcbb47 100644 --- a/userfront/lib/features/auth/presentation/login_screen.dart +++ b/userfront/lib/features/auth/presentation/login_screen.dart @@ -58,6 +58,7 @@ class _LoginScreenState extends ConsumerState Timer? _verificationRedirectTimer; bool _noticeHandled = false; bool _drySendEnabled = false; + bool _oidcAutoAcceptTried = false; @override void initState() { @@ -66,7 +67,7 @@ class _LoginScreenState extends ConsumerState _tabController.addListener(_handleTabSelection); _drySendEnabled = _parseBoolParam(Uri.base.queryParameters['drySend']) && !AuthProxyService.isProdEnv; - WidgetsBinding.instance.addPostFrameCallback((_) { + WidgetsBinding.instance.addPostFrameCallback((_) async { final uri = Uri.base; _loginChallenge = widget.loginChallenge ?? uri.queryParameters['login_challenge']; final loginIdParam = uri.queryParameters['loginId']; @@ -95,7 +96,9 @@ class _LoginScreenState extends ConsumerState } if (!_verificationOnly) { - _tryCookieSession(); + await _attemptOidcAutoAccept(); + if (!mounted) return; + await _tryCookieSession(); } if (uri.queryParameters.containsKey('redirect_url')) { @@ -105,7 +108,8 @@ class _LoginScreenState extends ConsumerState } Future _tryCookieSession({bool silent = true}) async { - if (AuthTokenStore.getToken() != null) { + if (AuthTokenStore.getToken() != null && + (_loginChallenge == null || _loginChallenge!.isEmpty)) { return; } final pendingProvider = AuthTokenStore.getPendingProvider(); @@ -117,7 +121,7 @@ class _LoginScreenState extends ConsumerState AuthTokenStore.clearPendingProvider(); if (mounted) { await ref.read(profileProvider.notifier).loadProfile(); - _onCookieLoginSuccess(provider); + await _onCookieLoginSuccess(provider); } } catch (e) { if (!silent) { @@ -126,14 +130,65 @@ class _LoginScreenState extends ConsumerState } } - void _onCookieLoginSuccess(String provider) { + Future _onCookieLoginSuccess(String provider) async { debugPrint("[Auth] Cookie-based login success. Provider: $provider"); AuthNotifier.instance.notify(); + if (_loginChallenge != null && _loginChallenge!.isNotEmpty) { + final accepted = await _acceptOidcLoginAndRedirect(); + if (accepted) { + return; + } + } if (mounted) { context.go('/'); } } + Future _attemptOidcAutoAccept() async { + if (_oidcAutoAcceptTried) return; + _oidcAutoAcceptTried = true; + if (_loginChallenge == null || _loginChallenge!.isEmpty) { + return; + } + + final token = AuthTokenStore.getToken(); + if (token != null && token.isNotEmpty) { + final accepted = await _acceptOidcLoginAndRedirect(token: token); + if (accepted) { + return; + } + } + + try { + await AuthProxyService.checkCookieSession(); + AuthTokenStore.setCookieMode(provider: AuthTokenStore.getProvider() ?? 'ory'); + await _acceptOidcLoginAndRedirect(); + } catch (e) { + debugPrint("[Auth] OIDC auto-accept cookie check failed: $e"); + } + } + + Future _acceptOidcLoginAndRedirect({String? token}) async { + if (_loginChallenge == null || _loginChallenge!.isEmpty) { + return false; + } + try { + final res = await AuthProxyService.acceptOidcLogin( + _loginChallenge!, + token: token, + ); + final redirectTo = res['redirectTo'] as String?; + if (redirectTo != null && redirectTo.isNotEmpty) { + debugPrint("[Auth] OIDC login accepted. Redirecting to: $redirectTo"); + webWindow.redirectTo(redirectTo); + return true; + } + } catch (e) { + debugPrint("[Auth] OIDC login auto-accept failed: $e"); + } + return false; + } + void _resetLinkLoginState() { _linkPendingRef = null; _lastLinkLoginId = null;