mirror of
				https://github.com/immich-app/immich.git
				synced 2025-10-31 02:27:08 -04:00 
			
		
		
		
	* rename api tags to follow plural nomenclature of endpoints * chore: open api * fix mobile
		
			
				
	
	
		
			244 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
			
		
		
	
	
			244 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
| import 'dart:io';
 | |
| 
 | |
| import 'package:device_info_plus/device_info_plus.dart';
 | |
| import 'package:flutter/material.dart';
 | |
| import 'package:flutter_udid/flutter_udid.dart';
 | |
| import 'package:hooks_riverpod/hooks_riverpod.dart';
 | |
| import 'package:immich_mobile/providers/album/album.provider.dart';
 | |
| import 'package:immich_mobile/providers/album/shared_album.provider.dart';
 | |
| import 'package:immich_mobile/entities/store.entity.dart';
 | |
| import 'package:immich_mobile/models/authentication/authentication_state.model.dart';
 | |
| import 'package:immich_mobile/entities/user.entity.dart';
 | |
| import 'package:immich_mobile/providers/api.provider.dart';
 | |
| import 'package:immich_mobile/providers/db.provider.dart';
 | |
| import 'package:immich_mobile/services/api.service.dart';
 | |
| import 'package:immich_mobile/utils/db.dart';
 | |
| import 'package:immich_mobile/utils/hash.dart';
 | |
| import 'package:isar/isar.dart';
 | |
| import 'package:logging/logging.dart';
 | |
| import 'package:openapi/api.dart';
 | |
| 
 | |
| class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
 | |
|   AuthenticationNotifier(
 | |
|     this._apiService,
 | |
|     this._db,
 | |
|     this._ref,
 | |
|   ) : super(
 | |
|           AuthenticationState(
 | |
|             deviceId: "",
 | |
|             userId: "",
 | |
|             userEmail: "",
 | |
|             name: '',
 | |
|             profileImagePath: '',
 | |
|             isAdmin: false,
 | |
|             shouldChangePassword: false,
 | |
|             isAuthenticated: false,
 | |
|           ),
 | |
|         );
 | |
| 
 | |
|   final ApiService _apiService;
 | |
|   final Isar _db;
 | |
|   final StateNotifierProviderRef<AuthenticationNotifier, AuthenticationState>
 | |
|       _ref;
 | |
|   final _log = Logger("AuthenticationNotifier");
 | |
| 
 | |
|   Future<bool> login(
 | |
|     String email,
 | |
|     String password,
 | |
|     String serverUrl,
 | |
|   ) async {
 | |
|     try {
 | |
|       // Resolve API server endpoint from user provided serverUrl
 | |
|       await _apiService.resolveAndSetEndpoint(serverUrl);
 | |
|       await _apiService.serverInfoApi.pingServer();
 | |
|     } catch (e) {
 | |
|       debugPrint('Invalid Server Endpoint Url $e');
 | |
|       return false;
 | |
|     }
 | |
| 
 | |
|     // Make sign-in request
 | |
|     DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin();
 | |
| 
 | |
|     if (Platform.isIOS) {
 | |
|       var iosInfo = await deviceInfoPlugin.iosInfo;
 | |
|       _apiService.authenticationApi.apiClient
 | |
|           .addDefaultHeader('deviceModel', iosInfo.utsname.machine);
 | |
|       _apiService.authenticationApi.apiClient
 | |
|           .addDefaultHeader('deviceType', 'iOS');
 | |
|     } else {
 | |
|       var androidInfo = await deviceInfoPlugin.androidInfo;
 | |
|       _apiService.authenticationApi.apiClient
 | |
|           .addDefaultHeader('deviceModel', androidInfo.model);
 | |
|       _apiService.authenticationApi.apiClient
 | |
|           .addDefaultHeader('deviceType', 'Android');
 | |
|     }
 | |
| 
 | |
|     try {
 | |
|       var loginResponse = await _apiService.authenticationApi.login(
 | |
|         LoginCredentialDto(
 | |
|           email: email,
 | |
|           password: password,
 | |
|         ),
 | |
|       );
 | |
| 
 | |
|       if (loginResponse == null) {
 | |
|         debugPrint('Login Response is null');
 | |
|         return false;
 | |
|       }
 | |
| 
 | |
|       return setSuccessLoginInfo(
 | |
|         accessToken: loginResponse.accessToken,
 | |
|         serverUrl: serverUrl,
 | |
|       );
 | |
|     } catch (e) {
 | |
|       debugPrint("Error logging in $e");
 | |
|       return false;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   Future<void> logout() async {
 | |
|     var log = Logger('AuthenticationNotifier');
 | |
|     try {
 | |
|       String? userEmail = Store.tryGet(StoreKey.currentUser)?.email;
 | |
| 
 | |
|       _apiService.authenticationApi
 | |
|           .logout()
 | |
|           .then((_) => log.info("Logout was successful for $userEmail"))
 | |
|           .onError(
 | |
|             (error, stackTrace) =>
 | |
|                 log.severe("Logout failed for $userEmail", error, stackTrace),
 | |
|           );
 | |
| 
 | |
|       await Future.wait([
 | |
|         clearAssetsAndAlbums(_db),
 | |
|         Store.delete(StoreKey.currentUser),
 | |
|         Store.delete(StoreKey.accessToken),
 | |
|       ]);
 | |
|       _ref.invalidate(albumProvider);
 | |
|       _ref.invalidate(sharedAlbumProvider);
 | |
| 
 | |
|       state = state.copyWith(
 | |
|         deviceId: "",
 | |
|         userId: "",
 | |
|         userEmail: "",
 | |
|         name: '',
 | |
|         profileImagePath: '',
 | |
|         isAdmin: false,
 | |
|         shouldChangePassword: false,
 | |
|         isAuthenticated: false,
 | |
|       );
 | |
|     } catch (e, stack) {
 | |
|       log.severe('Logout failed', e, stack);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   updateUserProfileImagePath(String path) {
 | |
|     state = state.copyWith(profileImagePath: path);
 | |
|   }
 | |
| 
 | |
|   Future<bool> changePassword(String newPassword) async {
 | |
|     try {
 | |
|       await _apiService.usersApi.updateMyUser(
 | |
|         UserUpdateMeDto(
 | |
|           password: newPassword,
 | |
|         ),
 | |
|       );
 | |
| 
 | |
|       state = state.copyWith(shouldChangePassword: false);
 | |
| 
 | |
|       return true;
 | |
|     } catch (e) {
 | |
|       debugPrint("Error changing password $e");
 | |
|       return false;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   Future<bool> setSuccessLoginInfo({
 | |
|     required String accessToken,
 | |
|     required String serverUrl,
 | |
|     bool offlineLogin = false,
 | |
|   }) 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;
 | |
| 
 | |
|     bool shouldChangePassword = false;
 | |
|     User? user;
 | |
| 
 | |
|     bool retResult = false;
 | |
|     User? offlineUser = Store.tryGet(StoreKey.currentUser);
 | |
| 
 | |
|     // If the user is offline and there is a user saved on the device,
 | |
|     // if not try an online login
 | |
|     if (offlineLogin && offlineUser != null) {
 | |
|       user = offlineUser;
 | |
|       retResult = false;
 | |
|     } else {
 | |
|       UserAdminResponseDto? userResponseDto;
 | |
|       UserPreferencesResponseDto? userPreferences;
 | |
|       try {
 | |
|         userResponseDto = await _apiService.usersApi.getMyUser();
 | |
|         userPreferences = await _apiService.usersApi.getMyPreferences();
 | |
|       } on ApiException catch (error, stackTrace) {
 | |
|         _log.severe(
 | |
|           "Error getting user information from the server [API EXCEPTION]",
 | |
|           error,
 | |
|           stackTrace,
 | |
|         );
 | |
|         if (error.innerException is SocketException) {
 | |
|           state = state.copyWith(isAuthenticated: true);
 | |
|         }
 | |
|       } catch (error, stackTrace) {
 | |
|         _log.severe(
 | |
|           "Error getting user information from the server [CATCH ALL]",
 | |
|           error,
 | |
|           stackTrace,
 | |
|         );
 | |
|       }
 | |
| 
 | |
|       if (userResponseDto != null) {
 | |
|         Store.put(StoreKey.deviceId, deviceId);
 | |
|         Store.put(StoreKey.deviceIdHash, fastHash(deviceId));
 | |
|         Store.put(
 | |
|           StoreKey.currentUser,
 | |
|           User.fromUserDto(userResponseDto, userPreferences),
 | |
|         );
 | |
|         Store.put(StoreKey.serverUrl, serverUrl);
 | |
|         Store.put(StoreKey.accessToken, accessToken);
 | |
| 
 | |
|         shouldChangePassword = userResponseDto.shouldChangePassword;
 | |
|         user = User.fromUserDto(userResponseDto, userPreferences);
 | |
| 
 | |
|         retResult = true;
 | |
|       } else {
 | |
|         _log.severe("Unable to get user information from the server.");
 | |
|         return false;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     state = state.copyWith(
 | |
|       isAuthenticated: true,
 | |
|       userId: user.id,
 | |
|       userEmail: user.email,
 | |
|       name: user.name,
 | |
|       profileImagePath: user.profileImagePath,
 | |
|       isAdmin: user.isAdmin,
 | |
|       shouldChangePassword: shouldChangePassword,
 | |
|       deviceId: deviceId,
 | |
|     );
 | |
| 
 | |
|     return retResult;
 | |
|   }
 | |
| }
 | |
| 
 | |
| final authenticationProvider =
 | |
|     StateNotifierProvider<AuthenticationNotifier, AuthenticationState>((ref) {
 | |
|   return AuthenticationNotifier(
 | |
|     ref.watch(apiServiceProvider),
 | |
|     ref.watch(dbProvider),
 | |
|     ref,
 | |
|   );
 | |
| });
 |