at main 4.4 kB view raw
1import 'dart:convert'; 2 3import 'package:flutter_secure_storage/flutter_secure_storage.dart'; 4import 'package:flutter_web_auth_2/flutter_web_auth_2.dart'; 5import 'package:grain/api.dart'; 6import 'package:grain/app_logger.dart'; 7import 'package:grain/main.dart'; 8import 'package:grain/models/session.dart'; 9 10class Auth { 11 static const _storage = FlutterSecureStorage(); 12 Auth(); 13 14 Future<bool> hasToken() async { 15 final session = await _loadSession(); 16 return session != null && session.token.isNotEmpty && !isSessionExpired(session); 17 } 18 19 Future<void> login(String handle) async { 20 final apiUrl = AppConfig.apiUrl; 21 String? token; 22 String? refreshToken; 23 String? expiresAtStr; 24 String? did; 25 26 try { 27 final redirectUrl = await FlutterWebAuth2.authenticate( 28 url: '$apiUrl/oauth/login?client=native&handle=${Uri.encodeComponent(handle)}', 29 callbackUrlScheme: 'grainflutter', 30 ); 31 32 appLogger.i('Redirected URL: $redirectUrl'); 33 34 final uri = Uri.parse(redirectUrl); 35 token = uri.queryParameters['token']; 36 refreshToken = uri.queryParameters['refreshToken']; 37 expiresAtStr = uri.queryParameters['expiresAt']; 38 did = uri.queryParameters['did']; 39 } catch (e) { 40 appLogger.e('Error during authentication: $e'); 41 throw Exception('Authentication failed'); 42 } 43 44 appLogger.i('User signed in with handle: $handle'); 45 46 if (token == null || token.isEmpty) { 47 throw Exception('No token found in redirect URL'); 48 } 49 if (refreshToken == null || refreshToken.isEmpty) { 50 throw Exception('No refreshToken found in redirect URL'); 51 } 52 if (expiresAtStr == null || expiresAtStr.isEmpty) { 53 throw Exception('No expiresAt found in redirect URL'); 54 } 55 if (did == null || did.isEmpty) { 56 throw Exception('No did found in redirect URL'); 57 } 58 59 DateTime expiresAt; 60 try { 61 expiresAt = DateTime.parse(expiresAtStr); 62 } catch (e) { 63 throw Exception('Invalid expiresAt format'); 64 } 65 66 final session = Session( 67 token: token, 68 refreshToken: refreshToken, 69 expiresAt: expiresAt, 70 did: did, 71 ); 72 await _saveSession(session); 73 } 74 75 Future<void> _saveSession(Session session) async { 76 final sessionJson = jsonEncode(session.toJson()); 77 await _storage.write(key: 'session', value: sessionJson); 78 } 79 80 Future<Session?> _loadSession() async { 81 final sessionJsonString = await _storage.read(key: 'session'); 82 if (sessionJsonString == null) return null; 83 84 try { 85 final sessionJson = jsonDecode(sessionJsonString); 86 return Session.fromJson(sessionJson); 87 } catch (e) { 88 // Optionally log or clear storage if corrupted 89 return null; 90 } 91 } 92 93 bool isSessionExpired(Session session, {Duration tolerance = const Duration(seconds: 30)}) { 94 final now = DateTime.now().toUtc(); 95 return session.expiresAt.subtract(tolerance).isBefore(now); 96 } 97 98 Future<Session?> getValidSession() async { 99 final session = await _loadSession(); 100 if (session == null) { 101 // No session at all, do not attempt refresh 102 return null; 103 } 104 if (isSessionExpired(session)) { 105 appLogger.w('Session is expired, attempting refresh'); 106 try { 107 final refreshed = await apiService.refreshSession(session); 108 if (refreshed != null && !isSessionExpired(refreshed)) { 109 await _saveSession(refreshed); 110 appLogger.i('Session refreshed and saved'); 111 return refreshed; 112 } else { 113 appLogger.w('Session refresh failed or still expired, clearing session'); 114 await clearSession(); 115 return null; 116 } 117 } catch (e) { 118 appLogger.e('Error refreshing session: $e'); 119 await clearSession(); 120 return null; 121 } 122 } 123 return session; 124 } 125 126 Future<void> clearSession() async { 127 // Remove session from secure storage 128 await _storage.delete(key: 'session'); 129 } 130 131 Future<void> logout() async { 132 final session = await _loadSession(); 133 134 appLogger.i('Logging out user with session: $session'); 135 136 // Clear any in-memory session/user data 137 apiService.currentUser = null; 138 139 if (session == null) { 140 appLogger.w('No session to revoke'); 141 return; 142 } 143 144 await apiService.revokeSession(session); 145 146 await clearSession(); 147 148 appLogger.i('User logged out and session cleared'); 149 } 150} 151 152final auth = Auth();