From 96550c3411c82273145b95c4179506f69d88b833 Mon Sep 17 00:00:00 2001 From: help4bis Date: Thu, 2 Apr 2026 11:55:27 +1000 Subject: [PATCH] fix: session restore timeout + block .git access MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add 12s timeout to restoreSession() — client.init() could hang indefinitely if token expired or server unreachable, leaving user on spinner forever. Now clears stale credentials and shows login. - Sync persistence startup wrapped in try-catch — non-fatal - .htaccess blocks .git directory (was returning 200 to scanners) Co-Authored-By: Claude Sonnet 4.6 --- lib/core/auth/auth_notifier.dart | 35 ++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/lib/core/auth/auth_notifier.dart b/lib/core/auth/auth_notifier.dart index 40cd99a..5e0936d 100644 --- a/lib/core/auth/auth_notifier.dart +++ b/lib/core/auth/auth_notifier.dart @@ -1,7 +1,9 @@ -// Version: 1.1.0 | Created: 2026-04-01 +// Version: 1.1.1 | Created: 2026-04-01 // Riverpod notifier that owns the auth state machine. // All login/logout/session-restore transitions go through here. +import 'dart:async'; + import 'package:riverpod_annotation/riverpod_annotation.dart'; import '../../features/auth/data/auth_repository.dart'; @@ -39,25 +41,40 @@ class AuthNotifier extends _$AuthNotifier { try { final repo = ref.read(authRepositoryProvider); - await repo.restoreSession( - accessToken: credentials.accessToken, - userId: credentials.userId, - deviceId: credentials.deviceId, - ); + + // Timeout: client.init() starts the Matrix sync loop and can hang + // indefinitely if the token is expired or the server is unreachable. + // After 12 seconds we give up and send the user to the login screen. + await repo + .restoreSession( + accessToken: credentials.accessToken, + userId: credentials.userId, + deviceId: credentials.deviceId, + ) + .timeout( + const Duration(seconds: 12), + onTimeout: () => throw Exception('Session restore timed out'), + ); + state = AuthState.authenticated( userId: credentials.userId, accessToken: credentials.accessToken, deviceId: credentials.deviceId, ); - // Resume background persistence for the restored session. - ref.read(syncPersistenceServiceProvider).start(); + // Resume background persistence — fire-and-forget so it never blocks auth. + try { + ref.read(syncPersistenceServiceProvider).start(); + } catch (_) { + // Persistence failure is non-fatal; the app works without it. + } } on AuthFailure { // Stored credentials are invalid; force re-login. await storage.clearCredentials(); state = const AuthState.unauthenticated(); } on Exception { - // Network offline at startup; still land on login rather than crashing. + // Covers: timeout, network offline, Olm init failure. + // Clear stale credentials so the login screen appears cleanly. await storage.clearCredentials(); state = const AuthState.unauthenticated(); }