From 51831e94e4163a37aa1daceb882cd758509613ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Horv=C3=A1th=20Gergely?= Date: Thu, 5 Feb 2026 22:12:43 +0100 Subject: [PATCH] Enhance reauthentication process with token recovery from Apple Watch --- .../Views/ReauthRequiredView.swift | 6 +-- .../lib/helpers/api/client/kreta_client.dart | 51 ++++++++++++++++--- 2 files changed, 48 insertions(+), 9 deletions(-) diff --git a/firka/ios/FirkaWatch Watch App/Views/ReauthRequiredView.swift b/firka/ios/FirkaWatch Watch App/Views/ReauthRequiredView.swift index a442f14e..4a88f1ca 100644 --- a/firka/ios/FirkaWatch Watch App/Views/ReauthRequiredView.swift +++ b/firka/ios/FirkaWatch Watch App/Views/ReauthRequiredView.swift @@ -182,16 +182,16 @@ struct ReauthRequiredView: View { } if TokenManager.shared.isTokenExpired() { - print("[Watch] Watch token is expired - attempting to refresh...") + print("[Watch] Watch token is expired - attempting to refresh with retries...") Task { do { - _ = try await TokenManager.shared.refreshToken() + _ = try await KretaAPIClient.shared.getValidToken() print("[Watch] Token refresh succeeded! Now sending to iPhone...") await MainActor.run { self.sendRefreshedTokenToiPhone() } } catch { - print("[Watch] Token refresh failed: \(error) - both devices need reauth") + print("[Watch] Token refresh failed after all retries: \(error)") await MainActor.run { self.syncStatus = .failed } diff --git a/firka/lib/helpers/api/client/kreta_client.dart b/firka/lib/helpers/api/client/kreta_client.dart index 96448bfe..1e8fa324 100644 --- a/firka/lib/helpers/api/client/kreta_client.dart +++ b/firka/lib/helpers/api/client/kreta_client.dart @@ -16,6 +16,7 @@ import '../../../main.dart'; import '../../db/models/token_model.dart'; import '../../db/util.dart'; import '../../debug_helper.dart'; +import '../../watch_sync_helper.dart'; import '../consts.dart'; import '../exceptions/token.dart'; import '../model/grade.dart'; @@ -74,11 +75,49 @@ class KretaClient { debugPrint('[KretaClient] Reauth flag cleared'); } - static void _setReauthFlag() { + static Future _setReauthFlag() async { + if (Platform.isIOS && !needsReauth) { + debugPrint('[KretaClient] Token expired, trying to recover from Watch first...'); + final recovered = await _tryRecoverFromWatch(); + if (recovered) { + debugPrint('[KretaClient] Successfully recovered token from Watch, reauth not needed'); + return; + } + debugPrint('[KretaClient] Could not recover from Watch, setting reauth flag'); + } + needsReauth = true; reauthStateNotifier.value = true; } + static Future _tryRecoverFromWatch() async { + if (!Platform.isIOS || !initDone) return false; + + try { + await WatchSyncHelper.syncTokenFromWatch( + isar: initData.isar, + tokens: initData.tokens, + client: initData.client, + ); + + final tokens = await initData.isar.tokenModels.where().findAll(); + if (tokens.isEmpty) return false; + + final token = tokens.first; + if (token.expiryDate == null) return false; + + if (token.expiryDate!.isAfter(DateTime.now().add(const Duration(minutes: 1)))) { + debugPrint('[KretaClient] Watch provided fresh token, expiry: ${token.expiryDate}'); + return true; + } + + return false; + } catch (e) { + debugPrint('[KretaClient] Failed to recover from Watch: $e'); + return false; + } + } + KretaClient(this.model, this.isar); @@ -120,8 +159,8 @@ class KretaClient { } catch (e) { logger.warning("[Proactive] Token refresh failed: $e"); if (_isTokenExpired(e)) { - _setReauthFlag(); - if (Platform.isIOS) { + await _setReauthFlag(); + if (Platform.isIOS && needsReauth) { try { _watchChannel.invokeMethod('notifyReauthRequired'); } catch (e) { @@ -281,10 +320,10 @@ class KretaClient { } } catch (ex) { if (_isTokenExpired(ex)) { - _setReauthFlag(); + await _setReauthFlag(); logger.warning("Token expired, setting needsReauth flag"); - if (Platform.isIOS) { + if (Platform.isIOS && needsReauth) { try { _watchChannel.invokeMethod('notifyReauthRequired'); } catch (e) { @@ -573,7 +612,7 @@ class KretaClient { } } catch (ex) { if (_isTokenExpired(ex)) { - _setReauthFlag(); + await _setReauthFlag(); logger.warning("Token expired in timed request, setting needsReauth flag"); }