1
0
forked from baron/baron-sso

라우터 구조 개선 및 리다이렉트 시 파라미터 유실 수정

This commit is contained in:
2026-02-19 13:48:30 +09:00
parent b675159510
commit 95136cd5df
2 changed files with 77 additions and 118 deletions

View File

@@ -75,14 +75,16 @@ String buildLocalizedPath(String localeCode, Uri uri) {
restSegments = segments.skip(1); restSegments = segments.skip(1);
} }
} }
final newSegments = [localeCode, ...restSegments]; final newPath = '/${[localeCode, ...restSegments].join('/')}';
final path = '/${newSegments.join('/')}';
final queryPart = uri.hasQuery ? '?${uri.query}' : ''; // Return only the path and query part to avoid GoRouter confusion with full URLs
final fragmentPart = uri.fragment.isNotEmpty ? '#${uri.fragment}' : ''; final newUri = uri.replace(path: newPath);
return '$path$queryPart$fragmentPart'; String result = newUri.path;
} if (newUri.hasQuery) {
result += '?${newUri.query}';
String buildSigninRedirectPath(String localeCode, Uri uri) { }
final queryPart = uri.hasQuery ? '?${uri.query}' : ''; if (newUri.hasFragment) {
return '/$localeCode/signin$queryPart'; result += '#${newUri.fragment}';
}
return result;
} }

View File

@@ -101,8 +101,6 @@ void main() async {
} }
// Router Configuration // Router Configuration
final _routerLogger = Logger('Router');
final _router = GoRouter( final _router = GoRouter(
initialLocation: '/', initialLocation: '/',
debugLogDiagnostics: !kReleaseMode, debugLogDiagnostics: !kReleaseMode,
@@ -117,11 +115,15 @@ final _router = GoRouter(
routes: [ routes: [
GoRoute( GoRoute(
path: '/:locale', path: '/:locale',
builder: (context, state) { // Note: Removed direct builder here to prevent interference with sub-routes
_routerLogger.info("Navigating to root (DashboardScreen)");
return const DashboardScreen();
},
routes: [ routes: [
GoRoute(
path: '', // Matches /:locale
builder: (context, state) {
print("[Router] Building Dashboard (Root)");
return const DashboardScreen();
},
),
GoRoute( GoRoute(
path: 'profile', path: 'profile',
builder: (context, state) => const ProfilePage(), builder: (context, state) => const ProfilePage(),
@@ -129,14 +131,10 @@ final _router = GoRouter(
GoRoute( GoRoute(
path: 'signin', path: 'signin',
builder: (context, state) { builder: (context, state) {
final loginChallenge = final loginChallenge = state.uri.queryParameters['login_challenge'];
state.uri.queryParameters['login_challenge']; final redirectUrl = state.uri.queryParameters['redirect_uri'] ??
final redirectUrl = state.uri.queryParameters['redirect_url'];
state.uri.queryParameters['redirect_uri'] ?? print("[Router] Building /signin. Challenge: $loginChallenge");
state.uri.queryParameters['redirect_url'];
_routerLogger.info(
"Navigating to /signin with login_challenge: $loginChallenge, redirect: $redirectUrl",
);
return LoginScreen( return LoginScreen(
key: state.pageKey, key: state.pageKey,
loginChallenge: loginChallenge, loginChallenge: loginChallenge,
@@ -147,14 +145,11 @@ final _router = GoRouter(
GoRoute( GoRoute(
path: 'login', path: 'login',
builder: (context, state) { builder: (context, state) {
final loginChallenge = // IMPORTANT: Match signin logic to handle OIDC challenges
state.uri.queryParameters['login_challenge']; final loginChallenge = state.uri.queryParameters['login_challenge'];
final redirectUrl = final redirectUrl = state.uri.queryParameters['redirect_uri'] ??
state.uri.queryParameters['redirect_uri'] ?? state.uri.queryParameters['redirect_url'];
state.uri.queryParameters['redirect_url']; print("[Router] Building /login (as signin). Challenge: $loginChallenge");
_routerLogger.info(
"Navigating to /login with login_challenge: $loginChallenge, redirect: $redirectUrl",
);
return LoginScreen( return LoginScreen(
key: state.pageKey, key: state.pageKey,
loginChallenge: loginChallenge, loginChallenge: loginChallenge,
@@ -165,48 +160,33 @@ final _router = GoRouter(
GoRoute( GoRoute(
path: 'consent', path: 'consent',
builder: (BuildContext context, GoRouterState state) { builder: (BuildContext context, GoRouterState state) {
final consentChallenge = final consentChallenge = state.uri.queryParameters['consent_challenge'];
state.uri.queryParameters['consent_challenge'];
if (consentChallenge == null) { if (consentChallenge == null) {
_routerLogger.warning( print("[Router] WARNING: Consent screen without challenge.");
"Consent screen loaded without a challenge.",
);
return const Scaffold( return const Scaffold(
body: Center( body: Center(child: Text('Error: Consent challenge is missing.')),
child: Text('Error: Consent challenge is missing.'),
),
); );
} }
_routerLogger.info("Navigating to /consent with challenge."); print("[Router] Building /consent. Challenge: $consentChallenge");
return ConsentScreen(consentChallenge: consentChallenge); return ConsentScreen(consentChallenge: consentChallenge);
}, },
), ),
GoRoute( GoRoute(
path: 'signup', path: 'signup',
builder: (context, state) { builder: (context, state) => const SignupScreen(),
_routerLogger.info("Navigating to /signup");
return const SignupScreen();
},
), ),
GoRoute( GoRoute(
path: 'registration', path: 'registration',
builder: (context, state) { builder: (context, state) => const SignupScreen(),
_routerLogger.info("Navigating to /registration");
return const SignupScreen();
},
), ),
GoRoute( GoRoute(
path: 'verify', path: 'verify',
builder: (context, state) { builder: (context, state) => LoginScreen(key: state.pageKey),
_routerLogger.info("Navigating to /verify (query)");
return LoginScreen(key: state.pageKey);
},
), ),
GoRoute( GoRoute(
path: 'verify/:token', path: 'verify/:token',
builder: (context, state) { builder: (context, state) {
final token = state.pathParameters['token']; final token = state.pathParameters['token'];
_routerLogger.info("Navigating to /verify with token: $token");
return LoginScreen( return LoginScreen(
key: state.pageKey, key: state.pageKey,
verificationToken: token, verificationToken: token,
@@ -215,45 +195,30 @@ final _router = GoRouter(
), ),
GoRoute( GoRoute(
path: 'verification', path: 'verification',
builder: (context, state) { builder: (context, state) => LoginScreen(key: state.pageKey),
_routerLogger.info("Navigating to /verification");
return LoginScreen(key: state.pageKey);
},
), ),
GoRoute( GoRoute(
path: 'l/:shortCode', path: 'l/:shortCode',
builder: (context, state) { builder: (context, state) {
final shortCode = state.pathParameters['shortCode']; final shortCode = state.pathParameters['shortCode'];
_routerLogger.info("Navigating to /l with code: $shortCode");
return LoginScreen(key: state.pageKey); return LoginScreen(key: state.pageKey);
}, },
), ),
GoRoute( GoRoute(
path: 'forgot-password', path: 'forgot-password',
builder: (context, state) { builder: (context, state) => const ForgotPasswordScreen(),
_routerLogger.info("Navigating to /forgot-password");
return const ForgotPasswordScreen();
},
), ),
GoRoute( GoRoute(
path: 'recovery', path: 'recovery',
builder: (context, state) { builder: (context, state) => const ForgotPasswordScreen(),
_routerLogger.info("Navigating to /recovery");
return const ForgotPasswordScreen();
},
), ),
GoRoute( GoRoute(
// Supports both /reset-password and /reset-password?token=...
path: 'reset-password', path: 'reset-password',
builder: (context, state) { builder: (context, state) => const ResetPasswordScreen(),
_routerLogger.info("Navigating to /reset-password");
return const ResetPasswordScreen();
},
), ),
GoRoute( GoRoute(
path: 'error', path: 'error',
builder: (context, state) { builder: (context, state) {
_routerLogger.info("Navigating to /error");
final params = state.uri.queryParameters; final params = state.uri.queryParameters;
return ErrorScreen( return ErrorScreen(
errorId: params['id'], errorId: params['id'],
@@ -264,43 +229,30 @@ final _router = GoRouter(
), ),
GoRoute( GoRoute(
path: 'settings', path: 'settings',
builder: (context, state) { builder: (context, state) => ErrorScreen(
_routerLogger.info("Navigating to /settings (disabled)"); errorCode: 'settings_disabled',
return ErrorScreen( description: tr('msg.userfront.settings.disabled'),
errorCode: 'settings_disabled', ),
description: tr('msg.userfront.settings.disabled'),
);
},
), ),
GoRoute( GoRoute(
path: 'approve', path: 'approve',
builder: (context, state) { builder: (context, state) => ApproveQrScreen(
final ref = state.uri.queryParameters['ref']; pendingRef: state.uri.queryParameters['ref'],
_routerLogger.info("Navigating to /approve with ref: $ref"); ),
return ApproveQrScreen(pendingRef: ref);
},
), ),
GoRoute( GoRoute(
path: 'ql/:ref', path: 'ql/:ref',
builder: (context, state) { builder: (context, state) => ApproveQrScreen(
final ref = state.pathParameters['ref']; pendingRef: state.pathParameters['ref'],
_routerLogger.info("Navigating to /ql with ref: $ref"); ),
return ApproveQrScreen(pendingRef: ref);
},
), ),
GoRoute( GoRoute(
path: 'scan', path: 'scan',
builder: (context, state) { builder: (context, state) => const QRScanScreen(),
_routerLogger.info("Navigating to /scan");
return const QRScanScreen();
},
), ),
GoRoute( GoRoute(
path: 'admin/users', path: 'admin/users',
builder: (context, state) { builder: (context, state) => const UserManagementScreen(),
_routerLogger.info("Navigating to /admin/users");
return const UserManagementScreen();
},
), ),
], ],
), ),
@@ -308,18 +260,23 @@ final _router = GoRouter(
), ),
], ],
redirect: (context, state) { redirect: (context, state) {
final requestedLocale = extractLocaleFromPath(state.uri); final uri = state.uri;
final requestedLocale = extractLocaleFromPath(uri);
final preferredLocale = resolvePreferredLocaleCode(); final preferredLocale = resolvePreferredLocaleCode();
print("[Router] Redirect check for: $uri");
if (requestedLocale == null) { 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 token = AuthTokenStore.getToken();
final hasCookieSession = AuthTokenStore.usesCookie(); final isLoggedIn = (token != null && token.isNotEmpty) || AuthTokenStore.usesCookie();
final isLoggedIn = hasStoredToken || hasCookieSession; final path = stripLocalePath(uri);
final path = stripLocalePath(state.uri);
// Public paths that don't require login // Precise public path detection
final isPublicPath = final isPublicPath =
path == '/signin' || path == '/signin' ||
path == '/signup' || path == '/signup' ||
@@ -335,28 +292,28 @@ final _router = GoRouter(
path == '/reset-password' || path == '/reset-password' ||
path == '/error' || path == '/error' ||
path == '/settings' || 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) { if (isPublicPath) {
return null; return null;
} }
// If not logged in and trying to access a protected page, redirect to /signin
if (!isLoggedIn) { if (!isLoggedIn) {
_routerLogger.info("Not logged in, redirecting to /signin"); print("[Router] ACCESS DENIED. Redirecting to /signin");
return buildSigninRedirectPath(requestedLocale, state.uri); 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; return null;
}, },
); );