@@ -28,14 +28,24 @@ final routerProvider = Provider<GoRouter>((ref) {
2828 redirect: (context, state) {
2929 switch (session.status) {
3030 case AppSessionStatus .loading:
31+ // Stay on /unlock during an unlock attempt so the `from` query
32+ // parameter is preserved across the loading → ready transition.
33+ if (state.matchedLocation == '/unlock' ) return null ;
3134 return state.matchedLocation == '/loading' ? null : '/loading' ;
3235 case AppSessionStatus .needsSetup:
3336 return state.matchedLocation == '/setup' ? null : '/setup' ;
3437 case AppSessionStatus .locked:
35- return state.matchedLocation == '/unlock' ||
36- state.matchedLocation == '/recover'
37- ? null
38- : '/unlock' ;
38+ if (state.matchedLocation == '/unlock' ||
39+ state.matchedLocation == '/recover' ) {
40+ return null ;
41+ }
42+ // Capture the current location so it can be restored after unlock.
43+ // Strip fragments (client-side only, not meaningful across auth).
44+ final currentUri = state.uri.removeFragment ();
45+ return Uri (
46+ path: '/unlock' ,
47+ queryParameters: {'from' : currentUri.toString ()},
48+ ).toString ();
3949 case AppSessionStatus .ready:
4050 if (session.hasPendingRecoveryKey) {
4151 return state.matchedLocation == '/setup/recovery'
@@ -48,6 +58,22 @@ final routerProvider = Provider<GoRouter>((ref) {
4858 state.matchedLocation == '/unlock' ||
4959 state.matchedLocation == '/recover' ||
5060 state.matchedLocation == '/' ) {
61+ // Restore the location the user was at before the app locked.
62+ final from = state.uri.queryParameters['from' ];
63+ if (from != null && from.isNotEmpty) {
64+ final fromUri = Uri .tryParse (from);
65+ // Only allow relative paths (no scheme or host) to prevent open
66+ // redirect attacks. Explicitly reject protocol-relative URLs
67+ // (e.g. //evil.com) as a defense-in-depth measure before
68+ // parsing, since Uri.parse may handle them unexpectedly.
69+ if (! from.startsWith ('//' ) &&
70+ fromUri != null &&
71+ fromUri.scheme.isEmpty &&
72+ fromUri.host.isEmpty &&
73+ fromUri.path.startsWith ('/' )) {
74+ return from;
75+ }
76+ }
5177 return '/groups' ;
5278 }
5379 return null ;
0 commit comments