diff --git a/packages/wyatt_authentication_bloc/lib/src/data/repositories/authentication_repository_firebase.dart b/packages/wyatt_authentication_bloc/lib/src/data/repositories/authentication_repository_firebase.dart deleted file mode 100644 index 0cf6d5dc..00000000 --- a/packages/wyatt_authentication_bloc/lib/src/data/repositories/authentication_repository_firebase.dart +++ /dev/null @@ -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 . - -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(); - 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 get cubitStatus => - _controller.stream.asBroadcastStream(); - - @override - void changeCubitStatus(AuthCubitStatus status) => - _controller.sink.add(status); - - @override - Stream 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 applyActionCode(String code) async { - try { - await _firebaseAuth.applyActionCode(code); - } on FirebaseAuthException catch (e) { - throw ApplyActionCodeFailureFirebase.fromCode(e.code); - } catch (_) { - throw ApplyActionCodeFailureFirebase(); - } - } - - @override - Future 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> 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 signInAnonymously() async { - try { - await _firebaseAuth.signInAnonymously(); - } on FirebaseAuthException catch (e) { - throw SignInAnonymouslyFailureFirebase.fromCode(e.code); - } catch (_) { - throw SignInAnonymouslyFailureFirebase(); - } - } - - @override - Future 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 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 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 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 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 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 sendEmailVerification() async { - try { - await _userCache.inner!.sendEmailVerification(); - } catch (e) { - throw SendEmailVerificationFailureFirebase(); - } - } - - @override - Future 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 sendSignInLinkEmail({required String email}) async { - try { - // TODO(hpcl): implement sendSignInLinkEmail - } on FirebaseAuthException catch (e) { - throw SendSignInLinkEmailFailureFirebase.fromCode(e.code); - } catch (_) { - throw SendSignInLinkEmailFailureFirebase(); - } - } - - @override - Future 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 verifyPasswordResetCode({required String code}) async { - try { - await _firebaseAuth.verifyPasswordResetCode(code); - } on FirebaseAuthException catch (e) { - throw VerifyPasswordResetCodeFailureFirebase.fromCode(e.code); - } catch (_) { - throw VerifyPasswordResetCodeFailureFirebase(); - } - } - - @override - Future signOut() async { - try { - await Future.wait([ - _firebaseAuth.signOut(), - ]); - _userCache = const UserFirebase.empty(); - } catch (_) { - throw SignOutFailureFirebase(); - } - } - - @override - Future refresh() async { - try { - await _userCache.inner!.reload(); - } on FirebaseAuthException catch (e) { - throw RefreshFailureFirebase.fromCode(e.code); - } catch (_) { - throw RefreshFailureFirebase(); - } - } -} diff --git a/packages/wyatt_authentication_bloc/lib/src/data/repositories/authentication_repository_impl.dart b/packages/wyatt_authentication_bloc/lib/src/data/repositories/authentication_repository_impl.dart new file mode 100644 index 00000000..e5526ea8 --- /dev/null +++ b/packages/wyatt_authentication_bloc/lib/src/data/repositories/authentication_repository_impl.dart @@ -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 . + +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 + extends AuthenticationRepository { + final AuthenticationLocalDataSource _authenticationLocalDataSource; + final AuthenticationRemoteDataSource _authenticationRemoteDataSource; + + final FutureResult Function(Account? account)? _onSignUpSuccess; + final FutureResult Function(Account? account)? _onAccountChanges; + + AuthenticationRepositoryImpl( + this._authenticationLocalDataSource, + this._authenticationRemoteDataSource, + this._onSignUpSuccess, + this._onAccountChanges, + ); + + @override + FutureResult signInWithEmailAndPassword({ + required String email, + required String password, + }) => + Result.tryCatchAsync( + () async { + final account = + await _authenticationRemoteDataSource.signInWithEmailAndPassword( + email: email, + password: password, + ); + _authenticationLocalDataSource.storeAccount(account); + return account; + }, + (error) => error, + ); + + @override + FutureResult signOut() => + Result.tryCatchAsync( + () async { + await _authenticationRemoteDataSource.signOut(); + _authenticationLocalDataSource.destroy(); + }, + (error) => error, + ); + + @override + FutureResult signUp({ + required String email, + required String password, + }) => + Result.tryCatchAsync( + () 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 destroyCache() => + Result.tryCatch( + _authenticationLocalDataSource.destroy, + (error) => error, + ); + + @override + Result getAccount() => + Result.tryCatch( + _authenticationLocalDataSource.loadAccount, + (error) => error, + ); + + @override + Result setAccount( + Account account, + ) => + Result.tryCatch( + () { + _authenticationLocalDataSource.storeAccount(account); + return account; + }, + (error) => error, + ); + + @override + Result getData() => + Result.tryCatch( + _authenticationLocalDataSource.loadData, + (error) => error, + ); + + @override + Result setData( + T data, + ) => + Result.tryCatch( + () { + _authenticationLocalDataSource.storeData(data); + return data; + }, + (error) => error, + ); + + @override + FutureResult getIdentityToken() => + Result.tryCatchAsync( + _authenticationRemoteDataSource.getIdentityToken, + (error) => error, + ); + + @override + Stream>> streamAccount() => + _authenticationRemoteDataSource.streamAccount().map((account) async { + if (_onAccountChanges.isNotNull) { + final dataResult = await _onAccountChanges!.call(account); + return dataResult.map((data) => AccountWrapperModel(account, data)); + } + return Ok, AppException>( + AccountWrapperModel(account, null), + ); + }); +} diff --git a/packages/wyatt_authentication_bloc/lib/src/data/repositories/repositories.dart b/packages/wyatt_authentication_bloc/lib/src/data/repositories/repositories.dart new file mode 100644 index 00000000..7858c9e7 --- /dev/null +++ b/packages/wyatt_authentication_bloc/lib/src/data/repositories/repositories.dart @@ -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 . + +export 'authentication_repository_impl.dart'; diff --git a/packages/wyatt_authentication_bloc/lib/src/domain/repositories/authentication_repository.dart b/packages/wyatt_authentication_bloc/lib/src/domain/repositories/authentication_repository.dart index 35c80a72..432b1de6 100644 --- a/packages/wyatt_authentication_bloc/lib/src/domain/repositories/authentication_repository.dart +++ b/packages/wyatt_authentication_bloc/lib/src/domain/repositories/authentication_repository.dart @@ -14,126 +14,34 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -import 'package:wyatt_authentication_bloc/src/core/enum/auth_cubit_status.dart'; -import 'package:wyatt_authentication_bloc/src/core/exceptions/exceptions.dart'; -import 'package:wyatt_authentication_bloc/src/domain/entities/user.dart'; +import 'package:wyatt_architecture/wyatt_architecture.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_type_utils/wyatt_type_utils.dart'; -/// {@template authentication_repository} -/// Repository which manages user authentication. -/// {@endtemplate} -abstract class AuthenticationRepository { - /// Stream of [AuthCubitStatus] wich will emit the current cubit status. - Stream get cubitStatus; +abstract class AuthenticationRepository extends BaseRepository { + FutureResult signUp({ + required String email, + required String password, + + }); - /// Changes cubit status.(Useful to start or stop the engine.) - void changeCubitStatus(AuthCubitStatus status); - - /// Stream of [User] which will emit the current user when - /// the authentication state changes. - /// - /// Emits [User.empty] if the user is not authenticated. - Stream 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 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 signUp({required String email, required String password}); - - /// Fetches sign in methods for [email]. - /// - /// Throws a [FetchSignInMethodsForEmailFailureInterface] if - /// an exception occurs. - Future> fetchSignInMethodsForEmail({required String email}); - - /// Sign in anonimously. - /// - /// Throws a [SignInAnonymouslyFailureInterface] if an exception occurs. - Future signInAnonymously(); - - /// Starts the Sign In with Google Flow. - /// - /// Throws a [SignInWithGoogleFailureInterface] if an exception occurs. - Future signInWithGoogle(); - - /// Starts the Sign In with Facebook Flow. - /// - /// Throws a [SignInWithFacebookFailureInterface] if an exception occurs. - Future signInWithFacebook(); - - /// Starts the Sign In with Apple Flow. - /// - /// Throws a [SignInWithAppleFailureInterface] if an exception occurs. - Future signInWithApple(); - - /// Starts the Sign In with Twitter Flow. - /// - /// Throws a [SignInWithTwitterFailureInterface] if an exception occurs. - Future signInWithTwitter(); - - /// Signs in using an email address and email sign-in link. - /// - /// Throws a [SignInWithEmailLinkFailureInterface] if an exception occurs. - Future signInWithEmailLink( - String email, - String emailLink, - ); - - /// Signs in with the provided [email] and [password]. - /// - /// Throws a [SignInWithEmailAndPasswordFailureInterface] if - /// an exception occurs. - Future signInWithEmailAndPassword({ + FutureResult signInWithEmailAndPassword({ required String email, required String password, }); - /// Sends verification email to the provided [user]. - /// - /// Throws a [SendEmailVerificationFailureInterface] if an exception occurs. - Future sendEmailVerification(); + FutureResult signOut(); - /// Sends a password reset email to the provided [email]. - /// - /// Throws a [SendPasswordResetEmailFailureInterface] if an exception occurs. - Future sendPasswordResetEmail({required String email}); + Stream>> streamAccount(); - /// Sends link to login. - /// - /// Throws a [SendSignInLinkEmailFailureInterface] if an exception occurs. - Future sendSignInLinkEmail({required String email}); + FutureResult getIdentityToken(); - /// Confirms the password reset with the provided [newPassword] and [code]. - /// - /// Throws a [ConfirmPasswordResetFailureInterface] if an exception occurs. - Future confirmPasswordReset({ - required String code, - required String newPassword, - }); + Result getAccount(); + Result setAccount(Account account); - /// Verify password reset code. - /// - /// Throws a [VerifyPasswordResetCodeFailureInterface] if an exception occurs. - Future verifyPasswordResetCode({required String code}); + Result getData(); + Result setData(T data); - /// Signs out the current user which will emit - /// [User.empty] from the [user] Stream. - Future signOut(); - - /// Refreshes the current user. - /// - /// Throws a [RefreshFailureInterface] if an exception occurs. - Future refresh(); + Result destroyCache(); } diff --git a/packages/wyatt_authentication_bloc/lib/src/domain/repositories/repositories.dart b/packages/wyatt_authentication_bloc/lib/src/domain/repositories/repositories.dart new file mode 100644 index 00000000..85f8ae88 --- /dev/null +++ b/packages/wyatt_authentication_bloc/lib/src/domain/repositories/repositories.dart @@ -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 . + +export 'authentication_repository.dart';