From 95136cd5df65c0ac8a7d48e1886d992a0a7fb2c4 Mon Sep 17 00:00:00 2001 From: kyy Date: Thu, 19 Feb 2026 13:48:30 +0900 Subject: [PATCH] =?UTF-8?q?=EB=9D=BC=EC=9A=B0=ED=84=B0=20=EA=B5=AC?= =?UTF-8?q?=EC=A1=B0=20=EA=B0=9C=EC=84=A0=20=EB=B0=8F=20=EB=A6=AC=EB=8B=A4?= =?UTF-8?q?=EC=9D=B4=EB=A0=89=ED=8A=B8=20=EC=8B=9C=20=ED=8C=8C=EB=9D=BC?= =?UTF-8?q?=EB=AF=B8=ED=84=B0=20=EC=9C=A0=EC=8B=A4=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- userfront/lib/core/i18n/locale_utils.dart | 22 +-- userfront/lib/main.dart | 173 ++++++++-------------- 2 files changed, 77 insertions(+), 118 deletions(-) diff --git a/userfront/lib/core/i18n/locale_utils.dart b/userfront/lib/core/i18n/locale_utils.dart index 7c89f051..9b942f7d 100644 --- a/userfront/lib/core/i18n/locale_utils.dart +++ b/userfront/lib/core/i18n/locale_utils.dart @@ -75,14 +75,16 @@ String buildLocalizedPath(String localeCode, Uri uri) { restSegments = segments.skip(1); } } - final newSegments = [localeCode, ...restSegments]; - final path = '/${newSegments.join('/')}'; - final queryPart = uri.hasQuery ? '?${uri.query}' : ''; - final fragmentPart = uri.fragment.isNotEmpty ? '#${uri.fragment}' : ''; - return '$path$queryPart$fragmentPart'; -} - -String buildSigninRedirectPath(String localeCode, Uri uri) { - final queryPart = uri.hasQuery ? '?${uri.query}' : ''; - return '/$localeCode/signin$queryPart'; + final newPath = '/${[localeCode, ...restSegments].join('/')}'; + + // Return only the path and query part to avoid GoRouter confusion with full URLs + final newUri = uri.replace(path: newPath); + String result = newUri.path; + if (newUri.hasQuery) { + result += '?${newUri.query}'; + } + if (newUri.hasFragment) { + result += '#${newUri.fragment}'; + } + return result; } diff --git a/userfront/lib/main.dart b/userfront/lib/main.dart index 4e6b3333..97d7e040 100644 --- a/userfront/lib/main.dart +++ b/userfront/lib/main.dart @@ -101,8 +101,6 @@ void main() async { } // Router Configuration -final _routerLogger = Logger('Router'); - final _router = GoRouter( initialLocation: '/', debugLogDiagnostics: !kReleaseMode, @@ -117,11 +115,15 @@ final _router = GoRouter( routes: [ GoRoute( path: '/:locale', - builder: (context, state) { - _routerLogger.info("Navigating to root (DashboardScreen)"); - return const DashboardScreen(); - }, + // Note: Removed direct builder here to prevent interference with sub-routes routes: [ + GoRoute( + path: '', // Matches /:locale + builder: (context, state) { + print("[Router] Building Dashboard (Root)"); + return const DashboardScreen(); + }, + ), GoRoute( path: 'profile', builder: (context, state) => const ProfilePage(), @@ -129,14 +131,10 @@ final _router = GoRouter( GoRoute( path: 'signin', builder: (context, state) { - final loginChallenge = - state.uri.queryParameters['login_challenge']; - final redirectUrl = - state.uri.queryParameters['redirect_uri'] ?? - state.uri.queryParameters['redirect_url']; - _routerLogger.info( - "Navigating to /signin with login_challenge: $loginChallenge, redirect: $redirectUrl", - ); + final loginChallenge = state.uri.queryParameters['login_challenge']; + final redirectUrl = state.uri.queryParameters['redirect_uri'] ?? + state.uri.queryParameters['redirect_url']; + print("[Router] Building /signin. Challenge: $loginChallenge"); return LoginScreen( key: state.pageKey, loginChallenge: loginChallenge, @@ -147,14 +145,11 @@ final _router = GoRouter( GoRoute( path: 'login', builder: (context, state) { - final loginChallenge = - state.uri.queryParameters['login_challenge']; - final redirectUrl = - state.uri.queryParameters['redirect_uri'] ?? - state.uri.queryParameters['redirect_url']; - _routerLogger.info( - "Navigating to /login with login_challenge: $loginChallenge, redirect: $redirectUrl", - ); + // IMPORTANT: Match signin logic to handle OIDC challenges + final loginChallenge = state.uri.queryParameters['login_challenge']; + final redirectUrl = state.uri.queryParameters['redirect_uri'] ?? + state.uri.queryParameters['redirect_url']; + print("[Router] Building /login (as signin). Challenge: $loginChallenge"); return LoginScreen( key: state.pageKey, loginChallenge: loginChallenge, @@ -165,48 +160,33 @@ final _router = GoRouter( GoRoute( path: 'consent', builder: (BuildContext context, GoRouterState state) { - final consentChallenge = - state.uri.queryParameters['consent_challenge']; + final consentChallenge = state.uri.queryParameters['consent_challenge']; if (consentChallenge == null) { - _routerLogger.warning( - "Consent screen loaded without a challenge.", - ); + print("[Router] WARNING: Consent screen without challenge."); return const Scaffold( - body: Center( - child: Text('Error: Consent challenge is missing.'), - ), + body: Center(child: Text('Error: Consent challenge is missing.')), ); } - _routerLogger.info("Navigating to /consent with challenge."); + print("[Router] Building /consent. Challenge: $consentChallenge"); return ConsentScreen(consentChallenge: consentChallenge); }, ), GoRoute( path: 'signup', - builder: (context, state) { - _routerLogger.info("Navigating to /signup"); - return const SignupScreen(); - }, + builder: (context, state) => const SignupScreen(), ), GoRoute( path: 'registration', - builder: (context, state) { - _routerLogger.info("Navigating to /registration"); - return const SignupScreen(); - }, + builder: (context, state) => const SignupScreen(), ), GoRoute( path: 'verify', - builder: (context, state) { - _routerLogger.info("Navigating to /verify (query)"); - return LoginScreen(key: state.pageKey); - }, + builder: (context, state) => LoginScreen(key: state.pageKey), ), GoRoute( path: 'verify/:token', builder: (context, state) { final token = state.pathParameters['token']; - _routerLogger.info("Navigating to /verify with token: $token"); return LoginScreen( key: state.pageKey, verificationToken: token, @@ -215,45 +195,30 @@ final _router = GoRouter( ), GoRoute( path: 'verification', - builder: (context, state) { - _routerLogger.info("Navigating to /verification"); - return LoginScreen(key: state.pageKey); - }, + builder: (context, state) => LoginScreen(key: state.pageKey), ), GoRoute( path: 'l/:shortCode', builder: (context, state) { final shortCode = state.pathParameters['shortCode']; - _routerLogger.info("Navigating to /l with code: $shortCode"); return LoginScreen(key: state.pageKey); }, ), GoRoute( path: 'forgot-password', - builder: (context, state) { - _routerLogger.info("Navigating to /forgot-password"); - return const ForgotPasswordScreen(); - }, + builder: (context, state) => const ForgotPasswordScreen(), ), GoRoute( path: 'recovery', - builder: (context, state) { - _routerLogger.info("Navigating to /recovery"); - return const ForgotPasswordScreen(); - }, + builder: (context, state) => const ForgotPasswordScreen(), ), GoRoute( - // Supports both /reset-password and /reset-password?token=... path: 'reset-password', - builder: (context, state) { - _routerLogger.info("Navigating to /reset-password"); - return const ResetPasswordScreen(); - }, + builder: (context, state) => const ResetPasswordScreen(), ), GoRoute( path: 'error', builder: (context, state) { - _routerLogger.info("Navigating to /error"); final params = state.uri.queryParameters; return ErrorScreen( errorId: params['id'], @@ -264,43 +229,30 @@ final _router = GoRouter( ), GoRoute( path: 'settings', - builder: (context, state) { - _routerLogger.info("Navigating to /settings (disabled)"); - return ErrorScreen( - errorCode: 'settings_disabled', - description: tr('msg.userfront.settings.disabled'), - ); - }, + builder: (context, state) => ErrorScreen( + errorCode: 'settings_disabled', + description: tr('msg.userfront.settings.disabled'), + ), ), GoRoute( path: 'approve', - builder: (context, state) { - final ref = state.uri.queryParameters['ref']; - _routerLogger.info("Navigating to /approve with ref: $ref"); - return ApproveQrScreen(pendingRef: ref); - }, + builder: (context, state) => ApproveQrScreen( + pendingRef: state.uri.queryParameters['ref'], + ), ), GoRoute( path: 'ql/:ref', - builder: (context, state) { - final ref = state.pathParameters['ref']; - _routerLogger.info("Navigating to /ql with ref: $ref"); - return ApproveQrScreen(pendingRef: ref); - }, + builder: (context, state) => ApproveQrScreen( + pendingRef: state.pathParameters['ref'], + ), ), GoRoute( path: 'scan', - builder: (context, state) { - _routerLogger.info("Navigating to /scan"); - return const QRScanScreen(); - }, + builder: (context, state) => const QRScanScreen(), ), GoRoute( path: 'admin/users', - builder: (context, state) { - _routerLogger.info("Navigating to /admin/users"); - return const UserManagementScreen(); - }, + builder: (context, state) => const UserManagementScreen(), ), ], ), @@ -308,18 +260,23 @@ final _router = GoRouter( ), ], redirect: (context, state) { - final requestedLocale = extractLocaleFromPath(state.uri); + final uri = state.uri; + final requestedLocale = extractLocaleFromPath(uri); final preferredLocale = resolvePreferredLocaleCode(); + + print("[Router] Redirect check for: $uri"); + if (requestedLocale == null) { - return buildLocalizedPath(preferredLocale, state.uri); + final localizedPath = buildLocalizedPath(preferredLocale, uri); + print("[Router] Locale missing. Redirecting to: $localizedPath"); + return localizedPath; } - final hasStoredToken = AuthTokenStore.getToken() != null; - final hasCookieSession = AuthTokenStore.usesCookie(); - final isLoggedIn = hasStoredToken || hasCookieSession; - final path = stripLocalePath(state.uri); + final token = AuthTokenStore.getToken(); + final isLoggedIn = (token != null && token.isNotEmpty) || AuthTokenStore.usesCookie(); + final path = stripLocalePath(uri); - // Public paths that don't require login + // Precise public path detection final isPublicPath = path == '/signin' || path == '/signup' || @@ -335,28 +292,28 @@ final _router = GoRouter( path == '/reset-password' || path == '/error' || path == '/settings' || - path == '/consent'; // Consent page is public + path == '/consent' || + path.startsWith('/consent/') || + uri.path.contains('/consent'); - _routerLogger.fine("Redirect check - Path: $path, IsLoggedIn: $isLoggedIn"); + print("[Router] Path: $path, IsLoggedIn: $isLoggedIn, IsPublic: $isPublicPath"); - // 0. ALWAYS allow public paths to proceed so they can function if (isPublicPath) { return null; } - // If not logged in and trying to access a protected page, redirect to /signin if (!isLoggedIn) { - _routerLogger.info("Not logged in, redirecting to /signin"); - return buildSigninRedirectPath(requestedLocale, state.uri); + print("[Router] ACCESS DENIED. Redirecting to /signin"); + final locale = requestedLocale; + final newPath = '/$locale/signin'; + + // Preserve ALL query parameters + final finalRedirect = uri.replace(path: newPath); + String result = finalRedirect.path; + if (finalRedirect.hasQuery) result += '?${finalRedirect.query}'; + return result; } - // If logged in and trying to access login page, redirect to root (dashboard) - // This is now implicitly handled by the isPublicPath check, but kept for clarity. - // if (isLoggedIn && path == '/signin') { - // _routerLogger.info("Logged in, redirecting to /"); - // return '/'; - // } - return null; }, );