master #76
| @ -256,3 +256,24 @@ abstract class GetIdTokenFailureInterface | |||||||
| 
 | 
 | ||||||
|   GetIdTokenFailureInterface.fromCode(super.code) : super.fromCode(); |   GetIdTokenFailureInterface.fromCode(super.code) : super.fromCode(); | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | abstract class ReauthenticateFailureInterface | ||||||
|  |     extends AuthenticationFailureInterface { | ||||||
|  |   ReauthenticateFailureInterface(super.code, super.msg); | ||||||
|  | 
 | ||||||
|  |   ReauthenticateFailureInterface.fromCode(super.code) : super.fromCode(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | abstract class UpdateEmailFailureInterface | ||||||
|  |     extends AuthenticationFailureInterface { | ||||||
|  |   UpdateEmailFailureInterface(super.code, super.msg); | ||||||
|  | 
 | ||||||
|  |   UpdateEmailFailureInterface.fromCode(super.code) : super.fromCode(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | abstract class UpdatePasswordFailureInterface | ||||||
|  |     extends AuthenticationFailureInterface { | ||||||
|  |   UpdatePasswordFailureInterface(super.code, super.msg); | ||||||
|  | 
 | ||||||
|  |   UpdatePasswordFailureInterface.fromCode(super.code) : super.fromCode(); | ||||||
|  | } | ||||||
|  | |||||||
| @ -277,3 +277,75 @@ class GetIdTokenFailureFirebase extends GetIdTokenFailureInterface { | |||||||
| 
 | 
 | ||||||
|   GetIdTokenFailureFirebase.fromCode(super.code) : super.fromCode(); |   GetIdTokenFailureFirebase.fromCode(super.code) : super.fromCode(); | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | class ReauthenticateFailureFirebase extends ReauthenticateFailureInterface { | ||||||
|  |   ReauthenticateFailureFirebase([String? code, String? msg]) | ||||||
|  |       : super(code ?? 'unknown', msg ?? 'An unknown error occurred.'); | ||||||
|  |   ReauthenticateFailureFirebase.fromCode(String code) : super.fromCode(code) { | ||||||
|  |     switch (code) { | ||||||
|  |       case 'user-mismatch': | ||||||
|  |         msg = 'Given credential does not correspond to the user.'; | ||||||
|  |         break; | ||||||
|  |       case 'user-not-found': | ||||||
|  |         msg = 'User is not found, please create an account.'; | ||||||
|  |         break; | ||||||
|  |       case 'invalid-credential': | ||||||
|  |         msg = 'The credential received is malformed or has expired.'; | ||||||
|  |         break; | ||||||
|  |       case 'invalid-email': | ||||||
|  |         msg = 'Email is not valid or badly formatted.'; | ||||||
|  |         break; | ||||||
|  |       case 'wrong-password': | ||||||
|  |         msg = 'Incorrect password, please try again.'; | ||||||
|  |         break; | ||||||
|  |       case 'invalid-verification-code': | ||||||
|  |         msg = 'The credential verification code received is invalid.'; | ||||||
|  |         break; | ||||||
|  |       case 'invalid-verification-id': | ||||||
|  |         msg = 'The credential verification ID received is invalid.'; | ||||||
|  |         break; | ||||||
|  |       default: | ||||||
|  |         this.code = 'unknown'; | ||||||
|  |         msg = 'An unknown error occurred.'; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | class UpdateEmailFailureFirebase extends UpdateEmailFailureInterface { | ||||||
|  |   UpdateEmailFailureFirebase([String? code, String? msg]) | ||||||
|  |       : super(code ?? 'unknown', msg ?? 'An unknown error occurred.'); | ||||||
|  |   UpdateEmailFailureFirebase.fromCode(String code) : super.fromCode(code) { | ||||||
|  |     switch (code) { | ||||||
|  |       case 'invalid-email': | ||||||
|  |         msg = 'Email is not valid or badly formatted.'; | ||||||
|  |         break; | ||||||
|  |       case 'email-already-in-use': | ||||||
|  |         msg = 'An account already exists for that email.'; | ||||||
|  |         break; | ||||||
|  |       case 'requires-recent-login': | ||||||
|  |         msg = "User's last sign-in time does not meet the security threshold."; | ||||||
|  |         break; | ||||||
|  |       default: | ||||||
|  |         this.code = 'unknown'; | ||||||
|  |         msg = 'An unknown error occurred.'; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | class UpdatePasswordFailureFirebase extends UpdatePasswordFailureInterface { | ||||||
|  |   UpdatePasswordFailureFirebase([String? code, String? msg]) | ||||||
|  |       : super(code ?? 'unknown', msg ?? 'An unknown error occurred.'); | ||||||
|  |   UpdatePasswordFailureFirebase.fromCode(String code) : super.fromCode(code) { | ||||||
|  |     switch (code) { | ||||||
|  |       case 'weak-password': | ||||||
|  |         msg = 'Please enter a stronger password.'; | ||||||
|  |         break; | ||||||
|  |       case 'requires-recent-login': | ||||||
|  |         msg = "User's last sign-in time does not meet the security threshold."; | ||||||
|  |         break; | ||||||
|  |       default: | ||||||
|  |         this.code = 'unknown'; | ||||||
|  |         msg = 'An unknown error occurred.'; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | |||||||
| @ -21,6 +21,7 @@ import 'package:wyatt_type_utils/wyatt_type_utils.dart'; | |||||||
| class AuthenticationFirebaseDataSourceImpl | class AuthenticationFirebaseDataSourceImpl | ||||||
|     extends AuthenticationRemoteDataSource { |     extends AuthenticationRemoteDataSource { | ||||||
|   final FirebaseAuth _firebaseAuth; |   final FirebaseAuth _firebaseAuth; | ||||||
|  |   UserCredential? _latestCreds; | ||||||
| 
 | 
 | ||||||
|   AuthenticationFirebaseDataSourceImpl({FirebaseAuth? firebaseAuth}) |   AuthenticationFirebaseDataSourceImpl({FirebaseAuth? firebaseAuth}) | ||||||
|       : _firebaseAuth = firebaseAuth ?? FirebaseAuth.instance; |       : _firebaseAuth = firebaseAuth ?? FirebaseAuth.instance; | ||||||
| @ -51,6 +52,7 @@ class AuthenticationFirebaseDataSourceImpl | |||||||
|         email: email, |         email: email, | ||||||
|         password: password, |         password: password, | ||||||
|       ); |       ); | ||||||
|  |       _latestCreds = userCredential; | ||||||
|       final user = userCredential.user; |       final user = userCredential.user; | ||||||
|       if (user.isNotNull) { |       if (user.isNotNull) { | ||||||
|         return _mapper(user!); |         return _mapper(user!); | ||||||
| @ -76,6 +78,7 @@ class AuthenticationFirebaseDataSourceImpl | |||||||
|         email: email, |         email: email, | ||||||
|         password: password, |         password: password, | ||||||
|       ); |       ); | ||||||
|  |       _latestCreds = userCredential; | ||||||
|       final user = userCredential.user; |       final user = userCredential.user; | ||||||
|       if (user.isNotNull) { |       if (user.isNotNull) { | ||||||
|         return _mapper(user!); |         return _mapper(user!); | ||||||
| @ -92,6 +95,7 @@ class AuthenticationFirebaseDataSourceImpl | |||||||
|   @override |   @override | ||||||
|   Future<void> signOut() async { |   Future<void> signOut() async { | ||||||
|     try { |     try { | ||||||
|  |       _latestCreds = null; | ||||||
|       await _firebaseAuth.signOut(); |       await _firebaseAuth.signOut(); | ||||||
|     } catch (_) { |     } catch (_) { | ||||||
|       throw SignOutFailureFirebase(); |       throw SignOutFailureFirebase(); | ||||||
| @ -164,6 +168,7 @@ class AuthenticationFirebaseDataSourceImpl | |||||||
|   Future<Account> signInAnonymously() async { |   Future<Account> signInAnonymously() async { | ||||||
|     try { |     try { | ||||||
|       final userCredential = await _firebaseAuth.signInAnonymously(); |       final userCredential = await _firebaseAuth.signInAnonymously(); | ||||||
|  |       _latestCreds = userCredential; | ||||||
|       final user = userCredential.user; |       final user = userCredential.user; | ||||||
|       if (user.isNotNull) { |       if (user.isNotNull) { | ||||||
|         return _mapper(user!); |         return _mapper(user!); | ||||||
| @ -199,4 +204,60 @@ class AuthenticationFirebaseDataSourceImpl | |||||||
|       throw RefreshFailureFirebase(); |       throw RefreshFailureFirebase(); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|  |   @override | ||||||
|  |   Future<Account> reauthenticateWithCredential() async { | ||||||
|  |     try { | ||||||
|  |       if (_latestCreds?.credential != null) { | ||||||
|  |         await _firebaseAuth.currentUser | ||||||
|  |             ?.reauthenticateWithCredential(_latestCreds!.credential!); | ||||||
|  |       } else { | ||||||
|  |         throw Exception(); // Get caught just after. | ||||||
|  |       } | ||||||
|  |       final user = _firebaseAuth.currentUser; | ||||||
|  |       if (user.isNotNull) { | ||||||
|  |         return _mapper(user!); | ||||||
|  |       } else { | ||||||
|  |         throw Exception(); // Get caught just after. | ||||||
|  |       } | ||||||
|  |     } on FirebaseAuthException catch (e) { | ||||||
|  |       throw ReauthenticateFailureFirebase.fromCode(e.code); | ||||||
|  |     } catch (_) { | ||||||
|  |       throw ReauthenticateFailureFirebase(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   @override | ||||||
|  |   Future<Account> updateEmail({required String email}) async { | ||||||
|  |     try { | ||||||
|  |       await _firebaseAuth.currentUser!.updateEmail(email); | ||||||
|  |       final user = _firebaseAuth.currentUser; | ||||||
|  |       if (user.isNotNull) { | ||||||
|  |         return _mapper(user!); | ||||||
|  |       } else { | ||||||
|  |         throw Exception(); // Get caught just after. | ||||||
|  |       } | ||||||
|  |     } on FirebaseAuthException catch (e) { | ||||||
|  |       throw UpdateEmailFailureFirebase.fromCode(e.code); | ||||||
|  |     } catch (_) { | ||||||
|  |       throw UpdateEmailFailureFirebase(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   @override | ||||||
|  |   Future<Account> updatePassword({required String password}) async { | ||||||
|  |     try { | ||||||
|  |       await _firebaseAuth.currentUser!.updatePassword(password); | ||||||
|  |       final user = _firebaseAuth.currentUser; | ||||||
|  |       if (user.isNotNull) { | ||||||
|  |         return _mapper(user!); | ||||||
|  |       } else { | ||||||
|  |         throw Exception(); // Get caught just after. | ||||||
|  |       } | ||||||
|  |     } on FirebaseAuthException catch (e) { | ||||||
|  |       throw UpdatePasswordFailureFirebase.fromCode(e.code); | ||||||
|  |     } catch (_) { | ||||||
|  |       throw UpdatePasswordFailureFirebase(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
| } | } | ||||||
|  | |||||||
| @ -23,6 +23,7 @@ import 'package:wyatt_type_utils/wyatt_type_utils.dart'; | |||||||
| class AuthenticationMockDataSourceImpl extends AuthenticationRemoteDataSource { | class AuthenticationMockDataSourceImpl extends AuthenticationRemoteDataSource { | ||||||
|   Pair<Account, String>? _connectedMock; |   Pair<Account, String>? _connectedMock; | ||||||
|   Pair<Account, String>? _registeredMock; |   Pair<Account, String>? _registeredMock; | ||||||
|  |   DateTime _lastSignInTime = DateTime.now(); | ||||||
|   final StreamController<Account?> _streamAccount = StreamController() |   final StreamController<Account?> _streamAccount = StreamController() | ||||||
|     ..add(null); |     ..add(null); | ||||||
| 
 | 
 | ||||||
| @ -118,6 +119,7 @@ class AuthenticationMockDataSourceImpl extends AuthenticationRemoteDataSource { | |||||||
|     ); |     ); | ||||||
|     _streamAccount.add(mock); |     _streamAccount.add(mock); | ||||||
|     _connectedMock = _connectedMock?.copyWith(left: mock); |     _connectedMock = _connectedMock?.copyWith(left: mock); | ||||||
|  |     _lastSignInTime = DateTime.now(); | ||||||
|     return Future.value(mock); |     return Future.value(mock); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
| @ -149,6 +151,7 @@ class AuthenticationMockDataSourceImpl extends AuthenticationRemoteDataSource { | |||||||
|       } |       } | ||||||
|       _streamAccount.add(_registeredMock!.left); |       _streamAccount.add(_registeredMock!.left); | ||||||
|       _connectedMock = _registeredMock!.copyWith(); |       _connectedMock = _registeredMock!.copyWith(); | ||||||
|  |       _lastSignInTime = DateTime.now(); | ||||||
|       return _registeredMock!.left!; |       return _registeredMock!.left!; | ||||||
|     } |     } | ||||||
|     throw SignInWithCredentialFailureFirebase(); |     throw SignInWithCredentialFailureFirebase(); | ||||||
| @ -193,6 +196,7 @@ class AuthenticationMockDataSourceImpl extends AuthenticationRemoteDataSource { | |||||||
|     ); |     ); | ||||||
|     _streamAccount.add(mock); |     _streamAccount.add(mock); | ||||||
|     _registeredMock = Pair(mock, password); |     _registeredMock = Pair(mock, password); | ||||||
|  |     _lastSignInTime = DateTime.now(); | ||||||
|     return Future.value(mock); |     return Future.value(mock); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
| @ -204,4 +208,45 @@ class AuthenticationMockDataSourceImpl extends AuthenticationRemoteDataSource { | |||||||
|     await _randomDelay(); |     await _randomDelay(); | ||||||
|     return true; |     return true; | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|  |   @override | ||||||
|  |   Future<Account> reauthenticateWithCredential() async { | ||||||
|  |     await _randomDelay(); | ||||||
|  |     if (_connectedMock.isNull) { | ||||||
|  |       throw ReauthenticateFailureFirebase(); | ||||||
|  |     } | ||||||
|  |     await refresh(); | ||||||
|  |     _lastSignInTime = DateTime.now(); | ||||||
|  |     return Future.value(_connectedMock?.left); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   @override | ||||||
|  |   Future<Account> updateEmail({required String email}) { | ||||||
|  |     final before = DateTime.now().subtract(const Duration(seconds: 10)); | ||||||
|  |     if (_lastSignInTime.isBefore(before)) { | ||||||
|  |       throw UpdateEmailFailureFirebase('requires-recent-login'); | ||||||
|  |     } | ||||||
|  |     final refresh = DateTime.now(); | ||||||
|  |     final mock = (_connectedMock?.left as AccountModel?) | ||||||
|  |         ?.copyWith(lastSignInTime: refresh, email: email); | ||||||
|  |     _connectedMock = _connectedMock?.copyWith(left: mock); | ||||||
|  |     _registeredMock = _registeredMock?.copyWith(left: mock); | ||||||
|  |     _streamAccount.add(mock); | ||||||
|  |     return Future.value(_connectedMock?.left); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   @override | ||||||
|  |   Future<Account> updatePassword({required String password}) { | ||||||
|  |     final before = DateTime.now().subtract(const Duration(seconds: 10)); | ||||||
|  |     if (_lastSignInTime.isBefore(before)) { | ||||||
|  |       throw UpdatePasswordFailureFirebase('requires-recent-login'); | ||||||
|  |     } | ||||||
|  |     final refresh = DateTime.now(); | ||||||
|  |     final mock = (_connectedMock?.left as AccountModel?) | ||||||
|  |         ?.copyWith(lastSignInTime: refresh); | ||||||
|  |     _connectedMock = _connectedMock?.copyWith(left: mock, right: password); | ||||||
|  |     _registeredMock = _registeredMock?.copyWith(left: mock, right: password); | ||||||
|  |     _streamAccount.add(mock); | ||||||
|  |     return Future.value(_connectedMock?.left); | ||||||
|  |   } | ||||||
| } | } | ||||||
|  | |||||||
| @ -314,4 +314,38 @@ class AuthenticationRepositoryImpl<T extends Object> | |||||||
|         }, |         }, | ||||||
|         (error) => error, |         (error) => error, | ||||||
|       ); |       ); | ||||||
|  | 
 | ||||||
|  |   @override | ||||||
|  |   FutureOrResult<Account> reauthenticateWithCredential() => | ||||||
|  |       Result.tryCatchAsync<Account, AppException, AppException>( | ||||||
|  |         () async { | ||||||
|  |           final account = await _authenticationRemoteDataSource | ||||||
|  |               .reauthenticateWithCredential(); | ||||||
|  |           return account; | ||||||
|  |         }, | ||||||
|  |         (error) => error, | ||||||
|  |       ); | ||||||
|  | 
 | ||||||
|  |   @override | ||||||
|  |   FutureOrResult<Account> updateEmail({required String email}) => | ||||||
|  |       Result.tryCatchAsync<Account, AppException, AppException>( | ||||||
|  |         () async { | ||||||
|  |           final account = | ||||||
|  |               await _authenticationRemoteDataSource.updateEmail(email: email); | ||||||
|  |           return account; | ||||||
|  |         }, | ||||||
|  |         (error) => error, | ||||||
|  |       ); | ||||||
|  | 
 | ||||||
|  |   @override | ||||||
|  |   FutureOrResult<Account> updatePassword({required String password}) => | ||||||
|  |       Result.tryCatchAsync<Account, AppException, AppException>( | ||||||
|  |         () async { | ||||||
|  |           final account = await _authenticationRemoteDataSource.updatePassword( | ||||||
|  |             password: password, | ||||||
|  |           ); | ||||||
|  |           return account; | ||||||
|  |         }, | ||||||
|  |         (error) => error, | ||||||
|  |       ); | ||||||
| } | } | ||||||
|  | |||||||
| @ -48,4 +48,10 @@ abstract class AuthenticationRemoteDataSource extends BaseRemoteDataSource { | |||||||
|   Future<bool> verifyPasswordResetCode({required String code}); |   Future<bool> verifyPasswordResetCode({required String code}); | ||||||
| 
 | 
 | ||||||
|   Future<Account> signInAnonymously(); |   Future<Account> signInAnonymously(); | ||||||
|  | 
 | ||||||
|  |   Future<Account> updateEmail({required String email}); | ||||||
|  | 
 | ||||||
|  |   Future<Account> updatePassword({required String password}); | ||||||
|  | 
 | ||||||
|  |   Future<Account> reauthenticateWithCredential(); | ||||||
| } | } | ||||||
|  | |||||||
| @ -84,12 +84,45 @@ abstract class AuthenticationRepository<T> extends BaseRepository { | |||||||
|     required String password, |     required String password, | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|  |   /// {@template update_email} | ||||||
|  |   /// Update or add [email]. | ||||||
|  |   /// | ||||||
|  |   /// Throws a UpdateEmailFailureInterface if | ||||||
|  |   /// an exception occurs. | ||||||
|  |   /// {@endtemplate} | ||||||
|  |   FutureOrResult<Account> updateEmail({ | ||||||
|  |     required String email, | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   /// {@template update_password} | ||||||
|  |   /// Update or add [password]. | ||||||
|  |   /// | ||||||
|  |   /// Throws a UpdatePasswordFailureInterface if | ||||||
|  |   /// an exception occurs. | ||||||
|  |   /// {@endtemplate} | ||||||
|  |   FutureOrResult<Account> updatePassword({ | ||||||
|  |     required String password, | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   /// {@template reauthenticate} | ||||||
|  |   /// Some security-sensitive actions—such as deleting an account,  | ||||||
|  |   /// setting a primary email address, and changing a password—require that  | ||||||
|  |   /// the user has recently signed in. | ||||||
|  |   /// | ||||||
|  |   /// Throws a ReauthenticateFailureInterface if | ||||||
|  |   /// an exception occurs. | ||||||
|  |   /// {@endtemplate} | ||||||
|  |   FutureOrResult<Account> reauthenticateWithCredential(); | ||||||
|  | 
 | ||||||
|   /// {@template signout} |   /// {@template signout} | ||||||
|   /// Signs out the current user. |   /// Signs out the current user. | ||||||
|   /// It also clears the cache and the associated data. |   /// It also clears the cache and the associated data. | ||||||
|   /// {@endtemplate} |   /// {@endtemplate} | ||||||
|   FutureOrResult<void> signOut(); |   FutureOrResult<void> signOut(); | ||||||
| 
 | 
 | ||||||
|  |   /// {@template refresh} | ||||||
|  |   /// Refreshes the current user, if signed in. | ||||||
|  |   /// {@endtemplate} | ||||||
|   FutureOrResult<void> refresh(); |   FutureOrResult<void> refresh(); | ||||||
| 
 | 
 | ||||||
|   /// {@template stream_account} |   /// {@template stream_account} | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user