feat: rewrite authentication repository

This commit is contained in:
Hugo Pointcheval 2022-11-09 01:46:28 -05:00
parent 0bc2aa3512
commit 3972c56e55
Signed by: hugo
GPG Key ID: A9E8E9615379254F
5 changed files with 209 additions and 449 deletions

View File

@ -1,338 +0,0 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'dart:async';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter_facebook_auth/flutter_facebook_auth.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'package:sign_in_with_apple/sign_in_with_apple.dart';
import 'package:twitter_login/twitter_login.dart';
import 'package:wyatt_authentication_bloc/src/core/enum/auth_cubit_status.dart';
import 'package:wyatt_authentication_bloc/src/core/exceptions/exceptions_firebase.dart';
import 'package:wyatt_authentication_bloc/src/core/extensions/firebase_auth_user_x.dart';
import 'package:wyatt_authentication_bloc/src/core/utils/cryptography.dart';
import 'package:wyatt_authentication_bloc/src/data/models/user_firebase.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/user.dart'
as wyatt;
import 'package:wyatt_authentication_bloc/src/domain/repositories/authentication_repository.dart';
class AuthenticationRepositoryFirebase implements AuthenticationRepository {
final _controller = StreamController<AuthCubitStatus>();
final FirebaseAuth _firebaseAuth;
final TwitterLogin? _twitterLogin;
UserFirebase _userCache = const UserFirebase.empty();
AuthenticationRepositoryFirebase({
FirebaseAuth? firebaseAuth,
TwitterLogin? twitterLogin,
}) : _firebaseAuth = firebaseAuth ?? FirebaseAuth.instance,
_twitterLogin = twitterLogin {
_controller.sink.add(AuthCubitStatus.stoped);
}
@override
Stream<AuthCubitStatus> get cubitStatus =>
_controller.stream.asBroadcastStream();
@override
void changeCubitStatus(AuthCubitStatus status) =>
_controller.sink.add(status);
@override
Stream<wyatt.User> get user =>
_firebaseAuth.userChanges().map((firebaseUser) {
final UserFirebase user = (firebaseUser == null)
? const UserFirebase.empty()
: firebaseUser.model;
_userCache = user;
return user;
});
@override
wyatt.User get currentUser => _userCache;
@override
Future<void> applyActionCode(String code) async {
try {
await _firebaseAuth.applyActionCode(code);
} on FirebaseAuthException catch (e) {
throw ApplyActionCodeFailureFirebase.fromCode(e.code);
} catch (_) {
throw ApplyActionCodeFailureFirebase();
}
}
@override
Future<String?> signUp({
required String email,
required String password,
}) async {
try {
final creds = await _firebaseAuth.createUserWithEmailAndPassword(
email: email,
password: password,
);
return creds.user?.uid;
} on FirebaseAuthException catch (e) {
throw SignUpWithEmailAndPasswordFailureFirebase.fromCode(e.code);
} catch (_) {
throw SignUpWithEmailAndPasswordFailureFirebase();
}
}
@override
Future<List<String>> fetchSignInMethodsForEmail({
required String email,
}) async {
try {
return await _firebaseAuth.fetchSignInMethodsForEmail(email);
} on FirebaseAuthException catch (e) {
throw FetchSignInMethodsForEmailFailureFirebase.fromCode(e.code);
} catch (_) {
throw FetchSignInMethodsForEmailFailureFirebase();
}
}
@override
Future<void> signInAnonymously() async {
try {
await _firebaseAuth.signInAnonymously();
} on FirebaseAuthException catch (e) {
throw SignInAnonymouslyFailureFirebase.fromCode(e.code);
} catch (_) {
throw SignInAnonymouslyFailureFirebase();
}
}
@override
Future<void> signInWithGoogle() async {
// Trigger the authentication flow
final GoogleSignInAccount? googleUser = await GoogleSignIn().signIn();
// Obtain the auth details from the request
final GoogleSignInAuthentication? googleAuth =
await googleUser?.authentication;
// Create a new credential
final credential = GoogleAuthProvider.credential(
accessToken: googleAuth?.accessToken,
idToken: googleAuth?.idToken,
);
try {
await _firebaseAuth.signInWithCredential(credential);
} on FirebaseAuthException catch (e) {
throw SignInWithGoogleFailureFirebase.fromCode(e.code);
} catch (_) {
throw SignInWithGoogleFailureFirebase();
}
}
@override
Future<void> signInWithFacebook() async {
// Trigger the sign-in flow
final LoginResult loginResult = await FacebookAuth.instance.login();
// Create a credential from the access token
final OAuthCredential credential =
FacebookAuthProvider.credential(loginResult.accessToken?.token ?? '');
try {
await _firebaseAuth.signInWithCredential(credential);
} on FirebaseAuthException catch (e) {
throw SignInWithFacebookFailureFirebase.fromCode(e.code);
} catch (_) {
throw SignInWithFacebookFailureFirebase();
}
}
@override
Future<void> signInWithApple() async {
// To prevent replay attacks with the credential returned from Apple, we
// include a nonce in the credential request. When signing in with
// Firebase, the nonce in the id token returned by Apple, is expected to
// match the sha256 hash of `rawNonce`.
final rawNonce = Cryptography.generateNonce();
final nonce = Cryptography.sha256ofString(rawNonce);
// Request credential for the currently signed in Apple account.
final appleCredential = await SignInWithApple.getAppleIDCredential(
scopes: [
AppleIDAuthorizationScopes.email,
AppleIDAuthorizationScopes.fullName,
],
nonce: nonce,
);
// Create an `OAuthCredential` from the credential returned by Apple.
final credential = OAuthProvider('apple.com').credential(
idToken: appleCredential.identityToken,
rawNonce: rawNonce,
);
// Sign in the user with Firebase. If the nonce we generated earlier does
// not match the nonce in `appleCredential.identityToken`,
// sign in will fail.
try {
await _firebaseAuth.signInWithCredential(credential);
} on FirebaseAuthException catch (e) {
throw SignInWithAppleFailureFirebase.fromCode(e.code);
} catch (_) {
throw SignInWithAppleFailureFirebase();
}
}
@override
Future<void> signInWithTwitter() async {
final twitterLogin = _twitterLogin;
if (twitterLogin == null) {
throw SignInWithTwitterFailureFirebase();
}
// Trigger the sign-in flow
final authResult = await twitterLogin.login();
// Create a credential from the access token
final credential = TwitterAuthProvider.credential(
accessToken: authResult.authToken!,
secret: authResult.authTokenSecret!,
);
try {
await _firebaseAuth.signInWithCredential(credential);
} on FirebaseAuthException catch (e) {
throw SignInWithCredentialFailureFirebase.fromCode(e.code);
} catch (_) {
throw SignInWithCredentialFailureFirebase();
}
}
@override
Future<void> signInWithEmailLink(String email, String emailLink) async {
try {
await _firebaseAuth.signInWithEmailLink(
email: email,
emailLink: emailLink,
);
} on FirebaseAuthException catch (e) {
throw SignInWithEmailLinkFailureFirebase.fromCode(e.code);
} catch (_) {
throw SignInWithEmailLinkFailureFirebase();
}
}
@override
Future<void> signInWithEmailAndPassword({
required String email,
required String password,
}) async {
try {
await _firebaseAuth.signInWithEmailAndPassword(
email: email,
password: password,
);
} on FirebaseAuthException catch (e) {
throw SignInWithEmailAndPasswordFailureFirebase.fromCode(e.code);
} catch (_) {
throw SignInWithEmailAndPasswordFailureFirebase();
}
}
@override
Future<void> sendEmailVerification() async {
try {
await _userCache.inner!.sendEmailVerification();
} catch (e) {
throw SendEmailVerificationFailureFirebase();
}
}
@override
Future<void> sendPasswordResetEmail({required String email}) async {
try {
await _firebaseAuth.sendPasswordResetEmail(email: email);
} on FirebaseAuthException catch (e) {
throw SendPasswordResetEmailFailureFirebase.fromCode(e.code);
} catch (_) {
throw SendPasswordResetEmailFailureFirebase();
}
}
@override
Future<void> sendSignInLinkEmail({required String email}) async {
try {
// TODO(hpcl): implement sendSignInLinkEmail
} on FirebaseAuthException catch (e) {
throw SendSignInLinkEmailFailureFirebase.fromCode(e.code);
} catch (_) {
throw SendSignInLinkEmailFailureFirebase();
}
}
@override
Future<void> confirmPasswordReset({
required String code,
required String newPassword,
}) async {
try {
await _firebaseAuth.confirmPasswordReset(
code: code,
newPassword: newPassword,
);
} on FirebaseAuthException catch (e) {
throw ConfirmPasswordResetFailureFirebase.fromCode(e.code);
} catch (_) {
throw ConfirmPasswordResetFailureFirebase();
}
throw UnimplementedError();
}
@override
Future<void> verifyPasswordResetCode({required String code}) async {
try {
await _firebaseAuth.verifyPasswordResetCode(code);
} on FirebaseAuthException catch (e) {
throw VerifyPasswordResetCodeFailureFirebase.fromCode(e.code);
} catch (_) {
throw VerifyPasswordResetCodeFailureFirebase();
}
}
@override
Future<void> signOut() async {
try {
await Future.wait([
_firebaseAuth.signOut(),
]);
_userCache = const UserFirebase.empty();
} catch (_) {
throw SignOutFailureFirebase();
}
}
@override
Future<void> refresh() async {
try {
await _userCache.inner!.reload();
} on FirebaseAuthException catch (e) {
throw RefreshFailureFirebase.fromCode(e.code);
} catch (_) {
throw RefreshFailureFirebase();
}
}
}

View File

@ -0,0 +1,156 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:wyatt_architecture/wyatt_architecture.dart';
import 'package:wyatt_authentication_bloc/src/data/models/account_wrapper_model.dart';
import 'package:wyatt_authentication_bloc/src/domain/data_sources/local/authentication_local_data_source.dart';
import 'package:wyatt_authentication_bloc/src/domain/data_sources/remote/authentication_remote_data_source.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/account.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/account_wrapper.dart';
import 'package:wyatt_authentication_bloc/src/domain/repositories/authentication_repository.dart';
import 'package:wyatt_type_utils/wyatt_type_utils.dart';
class AuthenticationRepositoryImpl<T extends Object>
extends AuthenticationRepository<T> {
final AuthenticationLocalDataSource<T> _authenticationLocalDataSource;
final AuthenticationRemoteDataSource _authenticationRemoteDataSource;
final FutureResult<T?> Function(Account? account)? _onSignUpSuccess;
final FutureResult<T?> Function(Account? account)? _onAccountChanges;
AuthenticationRepositoryImpl(
this._authenticationLocalDataSource,
this._authenticationRemoteDataSource,
this._onSignUpSuccess,
this._onAccountChanges,
);
@override
FutureResult<Account> signInWithEmailAndPassword({
required String email,
required String password,
}) =>
Result.tryCatchAsync<Account, AppException, AppException>(
() async {
final account =
await _authenticationRemoteDataSource.signInWithEmailAndPassword(
email: email,
password: password,
);
_authenticationLocalDataSource.storeAccount(account);
return account;
},
(error) => error,
);
@override
FutureResult<void> signOut() =>
Result.tryCatchAsync<void, AppException, AppException>(
() async {
await _authenticationRemoteDataSource.signOut();
_authenticationLocalDataSource.destroy();
},
(error) => error,
);
@override
FutureResult<Account> signUp({
required String email,
required String password,
}) =>
Result.tryCatchAsync<Account, AppException, AppException>(
() async {
final account = await _authenticationRemoteDataSource.signUp(
email: email,
password: password,
);
_authenticationLocalDataSource.storeAccount(account);
if (_onSignUpSuccess.isNotNull) {
final dataResult = await _onSignUpSuccess!.call(account);
dataResult.fold(
_authenticationLocalDataSource.storeData,
(error) => throw error,
);
}
return account;
},
(error) => error,
);
@override
Result<void, AppException> destroyCache() =>
Result.tryCatch<void, AppException, AppException>(
_authenticationLocalDataSource.destroy,
(error) => error,
);
@override
Result<Account, AppException> getAccount() =>
Result.tryCatch<Account, AppException, AppException>(
_authenticationLocalDataSource.loadAccount,
(error) => error,
);
@override
Result<Account, AppException> setAccount(
Account account,
) =>
Result.tryCatch<Account, AppException, AppException>(
() {
_authenticationLocalDataSource.storeAccount(account);
return account;
},
(error) => error,
);
@override
Result<T, AppException> getData() =>
Result.tryCatch<T, AppException, AppException>(
_authenticationLocalDataSource.loadData,
(error) => error,
);
@override
Result<T, AppException> setData(
T data,
) =>
Result.tryCatch<T, AppException, AppException>(
() {
_authenticationLocalDataSource.storeData(data);
return data;
},
(error) => error,
);
@override
FutureResult<String> getIdentityToken() =>
Result.tryCatchAsync<String, AppException, AppException>(
_authenticationRemoteDataSource.getIdentityToken,
(error) => error,
);
@override
Stream<FutureResult<AccountWrapper<T>>> streamAccount() =>
_authenticationRemoteDataSource.streamAccount().map((account) async {
if (_onAccountChanges.isNotNull) {
final dataResult = await _onAccountChanges!.call(account);
return dataResult.map((data) => AccountWrapperModel(account, data));
}
return Ok<AccountWrapperModel<T>, AppException>(
AccountWrapperModel<T>(account, null),
);
});
}

View File

@ -0,0 +1,17 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
export 'authentication_repository_impl.dart';

View File

@ -14,126 +14,34 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:wyatt_authentication_bloc/src/core/enum/auth_cubit_status.dart'; import 'package:wyatt_architecture/wyatt_architecture.dart';
import 'package:wyatt_authentication_bloc/src/core/exceptions/exceptions.dart'; import 'package:wyatt_authentication_bloc/src/domain/entities/account.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/user.dart'; import 'package:wyatt_authentication_bloc/src/domain/entities/account_wrapper.dart';
import 'package:wyatt_type_utils/wyatt_type_utils.dart';
/// {@template authentication_repository} abstract class AuthenticationRepository<T> extends BaseRepository {
/// Repository which manages user authentication. FutureResult<Account> signUp({
/// {@endtemplate} required String email,
abstract class AuthenticationRepository { required String password,
/// Stream of [AuthCubitStatus] wich will emit the current cubit status.
Stream<AuthCubitStatus> get cubitStatus;
/// Changes cubit status.(Useful to start or stop the engine.) });
void changeCubitStatus(AuthCubitStatus status);
/// Stream of [User] which will emit the current user when FutureResult<Account> signInWithEmailAndPassword({
/// the authentication state changes.
///
/// Emits [User.empty] if the user is not authenticated.
Stream<User> get user;
/// Returns the current cached account.
/// Defaults to [User.empty] if there is no cached user.
User get currentUser;
/// Applies action code
///
/// [code] - The action code sent to the user's email address.
/// Throw [ApplyActionCodeFailureInterface] if an exception occurs.
Future<void> applyActionCode(String code);
/// Creates a new user with the provided [email] and [password].
///
/// Returns the newly created user's unique identifier.
///
/// Throws a [SignUpWithEmailAndPasswordFailureInterface] if
/// an exception occurs.
Future<String?> signUp({required String email, required String password});
/// Fetches sign in methods for [email].
///
/// Throws a [FetchSignInMethodsForEmailFailureInterface] if
/// an exception occurs.
Future<List<String>> fetchSignInMethodsForEmail({required String email});
/// Sign in anonimously.
///
/// Throws a [SignInAnonymouslyFailureInterface] if an exception occurs.
Future<void> signInAnonymously();
/// Starts the Sign In with Google Flow.
///
/// Throws a [SignInWithGoogleFailureInterface] if an exception occurs.
Future<void> signInWithGoogle();
/// Starts the Sign In with Facebook Flow.
///
/// Throws a [SignInWithFacebookFailureInterface] if an exception occurs.
Future<void> signInWithFacebook();
/// Starts the Sign In with Apple Flow.
///
/// Throws a [SignInWithAppleFailureInterface] if an exception occurs.
Future<void> signInWithApple();
/// Starts the Sign In with Twitter Flow.
///
/// Throws a [SignInWithTwitterFailureInterface] if an exception occurs.
Future<void> signInWithTwitter();
/// Signs in using an email address and email sign-in link.
///
/// Throws a [SignInWithEmailLinkFailureInterface] if an exception occurs.
Future<void> signInWithEmailLink(
String email,
String emailLink,
);
/// Signs in with the provided [email] and [password].
///
/// Throws a [SignInWithEmailAndPasswordFailureInterface] if
/// an exception occurs.
Future<void> signInWithEmailAndPassword({
required String email, required String email,
required String password, required String password,
}); });
/// Sends verification email to the provided [user]. FutureResult<void> signOut();
///
/// Throws a [SendEmailVerificationFailureInterface] if an exception occurs.
Future<void> sendEmailVerification();
/// Sends a password reset email to the provided [email]. Stream<FutureResult<AccountWrapper<T>>> streamAccount();
///
/// Throws a [SendPasswordResetEmailFailureInterface] if an exception occurs.
Future<void> sendPasswordResetEmail({required String email});
/// Sends link to login. FutureResult<String> getIdentityToken();
///
/// Throws a [SendSignInLinkEmailFailureInterface] if an exception occurs.
Future<void> sendSignInLinkEmail({required String email});
/// Confirms the password reset with the provided [newPassword] and [code]. Result<Account, AppException> getAccount();
/// Result<Account, AppException> setAccount(Account account);
/// Throws a [ConfirmPasswordResetFailureInterface] if an exception occurs.
Future<void> confirmPasswordReset({
required String code,
required String newPassword,
});
/// Verify password reset code. Result<T, AppException> getData();
/// Result<T, AppException> setData(T data);
/// Throws a [VerifyPasswordResetCodeFailureInterface] if an exception occurs.
Future<void> verifyPasswordResetCode({required String code});
/// Signs out the current user which will emit Result<void, AppException> destroyCache();
/// [User.empty] from the [user] Stream.
Future<void> signOut();
/// Refreshes the current user.
///
/// Throws a [RefreshFailureInterface] if an exception occurs.
Future<void> refresh();
} }

View File

@ -0,0 +1,17 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
export 'authentication_repository.dart';