import 'package:flutter/foundation.dart'; import 'package:flutter_udid/flutter_udid.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/models/auth/login_response.model.dart'; import 'package:immich_mobile/models/auth/auth_state.model.dart'; import 'package:immich_mobile/entities/user.entity.dart'; import 'package:immich_mobile/providers/api.provider.dart'; import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/services/auth.service.dart'; import 'package:immich_mobile/utils/hash.dart'; import 'package:logging/logging.dart'; import 'package:openapi/api.dart'; final authProvider = StateNotifierProvider((ref) { return AuthNotifier( ref.watch(authServiceProvider), ref.watch(apiServiceProvider), ); }); class AuthNotifier extends StateNotifier { final AuthService _authService; final ApiService _apiService; final _log = Logger("AuthenticationNotifier"); static const Duration _timeoutDuration = Duration(seconds: 7); AuthNotifier( this._authService, this._apiService, ) : super( AuthState( deviceId: "", userId: "", userEmail: "", name: '', profileImagePath: '', isAdmin: false, isAuthenticated: false, ), ); Future validateServerUrl(String url) { return _authService.validateServerUrl(url); } /// Validating the url is the alternative connecting server url without /// saving the infomation to the local database Future validateAuxilaryServerUrl(String url) async { try { final validEndpoint = await _apiService.resolveEndpoint(url); return await _authService.validateAuxilaryServerUrl(validEndpoint); } catch (_) { return false; } } Future login(String email, String password) async { final response = await _authService.login(email, password); await saveAuthInfo(accessToken: response.accessToken); return response; } Future logout() async { try { await _authService.logout(); } finally { await _cleanUp(); } } Future _cleanUp() async { state = AuthState( deviceId: "", userId: "", userEmail: "", name: '', profileImagePath: '', isAdmin: false, isAuthenticated: false, ); } void updateUserProfileImagePath(String path) { state = state.copyWith(profileImagePath: path); } Future changePassword(String newPassword) async { try { await _authService.changePassword(newPassword); return true; } catch (_) { return false; } } Future saveAuthInfo({ required String accessToken, }) async { _apiService.setAccessToken(accessToken); // Get the deviceid from the store if it exists, otherwise generate a new one String deviceId = Store.tryGet(StoreKey.deviceId) ?? await FlutterUdid.consistentUdid; User? user = Store.tryGet(StoreKey.currentUser); UserAdminResponseDto? userResponse; UserPreferencesResponseDto? userPreferences; try { final responses = await Future.wait([ _apiService.usersApi.getMyUser().timeout(_timeoutDuration), _apiService.usersApi.getMyPreferences().timeout(_timeoutDuration), ]); userResponse = responses[0] as UserAdminResponseDto; userPreferences = responses[1] as UserPreferencesResponseDto; } on ApiException catch (error, stackTrace) { if (error.code == 401) { _log.severe("Unauthorized access, token likely expired. Logging out."); return false; } _log.severe( "Error getting user information from the server [API EXCEPTION]", stackTrace, ); } catch (error, stackTrace) { _log.severe( "Error getting user information from the server [CATCH ALL]", error, stackTrace, ); if (kDebugMode) { debugPrint( "Error getting user information from the server [CATCH ALL] $error $stackTrace", ); } } // If the user information is successfully retrieved, update the store // Due to the flow of the code, this will always happen on first login if (userResponse != null) { Store.put(StoreKey.deviceId, deviceId); Store.put(StoreKey.deviceIdHash, fastHash(deviceId)); Store.put( StoreKey.currentUser, User.fromUserDto(userResponse, userPreferences), ); Store.put(StoreKey.accessToken, accessToken); user = User.fromUserDto(userResponse, userPreferences); } else { _log.severe("Unable to get user information from the server."); } // If the user is null, the login was not successful // and we don't have a local copy of the user from a prior successful login if (user == null) { return false; } state = state.copyWith( isAuthenticated: true, userId: user.id, userEmail: user.email, name: user.name, profileImagePath: user.profileImagePath, isAdmin: user.isAdmin, deviceId: deviceId, ); return true; } Future saveWifiName(String wifiName) { return Store.put(StoreKey.preferredWifiName, wifiName); } Future saveLocalEndpoint(String url) { return Store.put(StoreKey.localEndpoint, url); } String? getSavedWifiName() { return Store.tryGet(StoreKey.preferredWifiName); } String? getSavedLocalEndpoint() { return Store.tryGet(StoreKey.localEndpoint); } /// Returns the current server endpoint (with /api) URL from the store String? getServerEndpoint() { return Store.tryGet(StoreKey.serverEndpoint); } /// Returns the current server URL (input by the user) from the store String? getServerUrl() { return Store.tryGet(StoreKey.serverUrl); } Future setOpenApiServiceEndpoint() { return _authService.setOpenApiServiceEndpoint(); } }