mirror of
https://github.com/immich-app/immich.git
synced 2026-05-18 03:10:24 +03:00
fix(mobile): don't block app open on slow validateAccessToken (#28405)
* fix(mobile): don't block app open on slow validateAccessToken AuthGuard.onNavigation was async so auto_route awaited the body through validateAccessToken's OS timeout. now it's sync and the validate runs in bg. kicks to login on 401. * fix(mobile): handle re-login race in AuthGuard validate if user logs out + logs back in during a slow validate, the old 401 was logging them out again. now we check the token hasn't changed before redirecting, and dedupe in-flight calls. --------- Co-authored-by: Alex <alex.tran1502@gmail.com>
This commit is contained in:
@@ -15,37 +15,62 @@ class AuthGuard extends AutoRouteGuard {
|
|||||||
final ApiService _apiService;
|
final ApiService _apiService;
|
||||||
final AuthService _authService;
|
final AuthService _authService;
|
||||||
final _log = Logger("AuthGuard");
|
final _log = Logger("AuthGuard");
|
||||||
|
bool _validateInFlight = false;
|
||||||
AuthGuard(this._apiService, this._authService);
|
AuthGuard(this._apiService, this._authService);
|
||||||
@override
|
@override
|
||||||
void onNavigation(NavigationResolver resolver, StackRouter router) async {
|
void onNavigation(NavigationResolver resolver, StackRouter router) {
|
||||||
resolver.next(true);
|
// Synchronously check for the access token. auto_route awaits async
|
||||||
|
// guards, so we keep this function fully sync and validate the token in
|
||||||
|
// the background — otherwise a slow validateAccessToken() request would
|
||||||
|
// block the route transition for as long as the OS-level HTTP timeout.
|
||||||
try {
|
try {
|
||||||
// Look in the store for an access token
|
|
||||||
Store.get(StoreKey.accessToken);
|
Store.get(StoreKey.accessToken);
|
||||||
|
|
||||||
// Validate the access token with the server
|
|
||||||
final res = await _apiService.authenticationApi.validateAccessToken();
|
|
||||||
if (res == null || res.authStatus != true) {
|
|
||||||
// If the access token is invalid, take user back to login
|
|
||||||
_log.fine('User token is invalid. Redirecting to login');
|
|
||||||
unawaited(router.replaceAll([const LoginRoute()]).then((_) => _authService.clearLocalData()));
|
|
||||||
}
|
|
||||||
} on StoreKeyNotFoundException catch (_) {
|
} on StoreKeyNotFoundException catch (_) {
|
||||||
// If there is no access token, take us to the login page
|
|
||||||
_log.warning('No access token in the store.');
|
_log.warning('No access token in the store.');
|
||||||
|
resolver.next(false);
|
||||||
unawaited(router.replaceAll([const LoginRoute()]));
|
unawaited(router.replaceAll([const LoginRoute()]));
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolver.next(true);
|
||||||
|
unawaited(_validateAccessTokenInBackground(router));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _validateAccessTokenInBackground(StackRouter router) async {
|
||||||
|
if (_validateInFlight) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final token = Store.tryGet(StoreKey.accessToken);
|
||||||
|
if (token == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_validateInFlight = true;
|
||||||
|
try {
|
||||||
|
final res = await _apiService.authenticationApi.validateAccessToken();
|
||||||
|
if (res == null || res.authStatus != true) {
|
||||||
|
// Token may have changed during validation (user logged out + logged in
|
||||||
|
// again); only act if it still applies to the current session.
|
||||||
|
if (Store.tryGet(StoreKey.accessToken) != token) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_log.fine('User token is invalid. Redirecting to login');
|
||||||
|
await router.replaceAll([const LoginRoute()]);
|
||||||
|
await _authService.clearLocalData();
|
||||||
|
}
|
||||||
} on ApiException catch (e) {
|
} on ApiException catch (e) {
|
||||||
// On an unauthorized request, take us to the login page
|
if (e.code != HttpStatus.unauthorized) {
|
||||||
if (e.code == HttpStatus.unauthorized) {
|
|
||||||
_log.warning("Unauthorized access token.");
|
|
||||||
unawaited(router.replaceAll([const LoginRoute()]).then((_) => _authService.clearLocalData()));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (Store.tryGet(StoreKey.accessToken) != token) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_log.warning("Unauthorized access token.");
|
||||||
|
await router.replaceAll([const LoginRoute()]);
|
||||||
|
await _authService.clearLocalData();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Otherwise, this is not fatal, but we still log the warning
|
|
||||||
_log.warning('Error validating access token from server: $e');
|
_log.warning('Error validating access token from server: $e');
|
||||||
|
} finally {
|
||||||
|
_validateInFlight = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user