forked from baron/baron-sso
라우터 구조 개선 및 리다이렉트 시 파라미터 유실 수정
This commit is contained in:
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user