From 3f1c93c35ffb0c69affe420077a7de7044bc37a7 Mon Sep 17 00:00:00 2001 From: Hugo Pointcheval Date: Fri, 10 Mar 2023 15:07:49 +0100 Subject: [PATCH] feat(authentication): add mock + local storage --- .../lib/src/core/constants/storage.dart | 22 + .../lib/src/core/exceptions/exceptions.dart | 2 + .../src/core/exceptions/exceptions_base.dart | 382 +++++++++++++++++ .../src/core/exceptions/exceptions_mock.dart | 390 ++++++++++++++++++ ...secure_storage_cache_data_source_impl.dart | 75 ++++ .../src/data/data_sources/local/local.dart | 1 + .../authentication_base_data_source.dart | 106 +++++ .../authentication_mock_data_source_impl.dart | 201 +++++++++ .../src/data/data_sources/remote/remote.dart | 2 + .../lib/src/data/models/account_model.dart | 24 ++ .../authentication_repository_impl.dart | 24 +- .../wyatt_authentication_bloc/pubspec.yaml | 2 + 12 files changed, 1228 insertions(+), 3 deletions(-) create mode 100644 packages/wyatt_authentication_bloc/lib/src/core/constants/storage.dart create mode 100644 packages/wyatt_authentication_bloc/lib/src/core/exceptions/exceptions_base.dart create mode 100644 packages/wyatt_authentication_bloc/lib/src/core/exceptions/exceptions_mock.dart create mode 100644 packages/wyatt_authentication_bloc/lib/src/data/data_sources/local/authentication_secure_storage_cache_data_source_impl.dart create mode 100644 packages/wyatt_authentication_bloc/lib/src/data/data_sources/remote/authentication_base_data_source.dart create mode 100644 packages/wyatt_authentication_bloc/lib/src/data/data_sources/remote/authentication_mock_data_source_impl.dart diff --git a/packages/wyatt_authentication_bloc/lib/src/core/constants/storage.dart b/packages/wyatt_authentication_bloc/lib/src/core/constants/storage.dart new file mode 100644 index 00000000..e3875066 --- /dev/null +++ b/packages/wyatt_authentication_bloc/lib/src/core/constants/storage.dart @@ -0,0 +1,22 @@ +// Copyright (C) 2023 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 . + +abstract class AuthStorage { + static const String refreshToken = 'wyattRefreshToken'; + static const String accessToken = 'wyattAccessToken'; + static const String email = 'wyattEmail'; + static const String id = 'wyattId'; +} diff --git a/packages/wyatt_authentication_bloc/lib/src/core/exceptions/exceptions.dart b/packages/wyatt_authentication_bloc/lib/src/core/exceptions/exceptions.dart index e10faebe..0fb036f7 100644 --- a/packages/wyatt_authentication_bloc/lib/src/core/exceptions/exceptions.dart +++ b/packages/wyatt_authentication_bloc/lib/src/core/exceptions/exceptions.dart @@ -16,7 +16,9 @@ import 'package:wyatt_architecture/wyatt_architecture.dart'; +part 'exceptions_base.dart'; part 'exceptions_firebase.dart'; +part 'exceptions_mock.dart'; /// Base exception used in Wyatt Authentication abstract class AuthenticationFailureInterface extends AppException diff --git a/packages/wyatt_authentication_bloc/lib/src/core/exceptions/exceptions_base.dart b/packages/wyatt_authentication_bloc/lib/src/core/exceptions/exceptions_base.dart new file mode 100644 index 00000000..cd994be5 --- /dev/null +++ b/packages/wyatt_authentication_bloc/lib/src/core/exceptions/exceptions_base.dart @@ -0,0 +1,382 @@ +// Copyright (C) 2023 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 . + +part of 'exceptions.dart'; + +/// {@macro apply_action_code_failure} +class ApplyActionCodeFailureBase extends ApplyActionCodeFailureInterface { + ApplyActionCodeFailureBase([String? code, String? msg]) + : super(code ?? 'unknown', msg ?? 'An unknown error occurred.'); + ApplyActionCodeFailureBase.fromCode(String code) : super.fromCode(code) { + switch (code) { + case 'expired-action-code': + msg = 'Action code has expired.'; + break; + case 'invalid-action-code': + msg = 'Action code is invalid.'; + break; + case 'user-disabled': + msg = 'This user has been disabled. Please contact support for help.'; + break; + case 'user-not-found': + msg = 'Email is not found, please create an account.'; + break; + + default: + this.code = 'unknown'; + msg = 'An unknown error occurred.'; + } + } +} + +/// {@macro sign_up_with_email_and_password_failure} +class SignUpWithEmailAndPasswordFailureBase + extends SignUpWithEmailAndPasswordFailureInterface { + SignUpWithEmailAndPasswordFailureBase([String? code, String? msg]) + : super(code ?? 'unknown', msg ?? 'An unknown error occurred.'); + SignUpWithEmailAndPasswordFailureBase.fromCode(String code) + : super.fromCode(code) { + switch (code) { + case 'invalid-email': + msg = 'The email address is badly formatted.'; + break; + case 'user-disabled': + msg = 'This user has been disabled. Please contact support for help.'; + break; + case 'email-already-in-use': + msg = 'An account already exists for that email.'; + break; + case 'operation-not-allowed': + msg = 'Operation is not allowed. Please contact support.'; + break; + case 'weak-password': + msg = 'Please enter a stronger password.'; + break; + default: + this.code = 'unknown'; + msg = 'An unknown error occurred.'; + } + } +} + +/// {@macro fetch_sign_in_methods_failure} +class FetchSignInMethodsForEmailFailureBase + extends FetchSignInMethodsForEmailFailureInterface { + FetchSignInMethodsForEmailFailureBase([String? code, String? msg]) + : super(code ?? 'unknown', msg ?? 'An unknown error occurred.'); + FetchSignInMethodsForEmailFailureBase.fromCode(String code) + : super.fromCode(code) { + switch (code) { + case 'invalid-email': + msg = 'The email address is badly formatted.'; + break; + default: + this.code = 'unknown'; + msg = 'An unknown error occurred.'; + } + } +} + +/// {@macro sign_in_anonymously_failure} +class SignInAnonymouslyFailureBase extends SignInAnonymouslyFailureInterface { + SignInAnonymouslyFailureBase([String? code, String? msg]) + : super(code ?? 'unknown', msg ?? 'An unknown error occurred.'); + SignInAnonymouslyFailureBase.fromCode(String code) : super.fromCode(code) { + switch (code) { + case 'operation-not-allowed': + msg = 'Operation is not allowed. Please contact support.'; + break; + default: + this.code = 'unknown'; + msg = 'An unknown error occurred.'; + } + } +} + +/// {@macro sign_in_with_credential_failure} +class SignInWithCredentialFailureBase + extends SignInWithCredentialFailureInterface { + SignInWithCredentialFailureBase([String? code, String? msg]) + : super(code ?? 'unknown', msg ?? 'An unknown error occurred.'); + SignInWithCredentialFailureBase.fromCode(String code) : super.fromCode(code) { + switch (code) { + case 'account-exists-with-different-credential': + msg = 'Account exists with different credentials.'; + break; + case 'invalid-credential': + msg = 'The credential received is malformed or has expired.'; + break; + case 'operation-not-allowed': + msg = 'Operation is not allowed. Please contact support.'; + break; + case 'user-disabled': + msg = 'This user has been disabled. Please contact support for help.'; + break; + case 'user-not-found': + msg = 'Email is not found, please create an account.'; + 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.'; + } + } +} + +/// {@macro sign_in_with_google_failure} +class SignInWithGoogleFailureBase extends SignInWithCredentialFailureBase + implements SignInWithGoogleFailureInterface { + SignInWithGoogleFailureBase([super.code, super.msg]); + SignInWithGoogleFailureBase.fromCode(super.code) : super.fromCode(); +} + +/// {@macro sign_in_with_facebook_failure} +class SignInWithFacebookFailureBase extends SignInWithCredentialFailureBase + implements SignInWithFacebookFailureInterface { + SignInWithFacebookFailureBase([super.code, super.msg]); + SignInWithFacebookFailureBase.fromCode(super.code) : super.fromCode(); +} + +/// {@macro sign_in_with_apple_failure} +class SignInWithAppleFailureBase extends SignInWithCredentialFailureBase + implements SignInWithAppleFailureInterface { + SignInWithAppleFailureBase([super.code, super.msg]); + SignInWithAppleFailureBase.fromCode(super.code) : super.fromCode(); +} + +/// {@macro sign_in_with_twitter_failure} +class SignInWithTwitterFailureBase extends SignInWithCredentialFailureBase + implements SignInWithAppleFailureInterface { + SignInWithTwitterFailureBase([super.code, super.msg]); + SignInWithTwitterFailureBase.fromCode(super.code) : super.fromCode(); +} + +/// {@macro sign_in_with_email_link_failure} +class SignInWithEmailLinkFailureBase + extends SignInWithEmailLinkFailureInterface { + SignInWithEmailLinkFailureBase([String? code, String? msg]) + : super(code ?? 'unknown', msg ?? 'An unknown error occurred.'); + SignInWithEmailLinkFailureBase.fromCode(String code) : super.fromCode(code) { + switch (code) { + case 'expired-action-code': + msg = 'Action code has expired.'; + break; + case 'invalid-email': + msg = 'Email is not valid or badly formatted.'; + break; + case 'user-disabled': + msg = 'This user has been disabled. Please contact support for help.'; + break; + default: + this.code = 'unknown'; + msg = 'An unknown error occurred.'; + } + } +} + +/// {@macro sign_in_with_email_and_password_failure} +class SignInWithEmailAndPasswordFailureBase + extends SignInWithEmailAndPasswordFailureInterface { + SignInWithEmailAndPasswordFailureBase([String? code, String? msg]) + : super(code ?? 'unknown', msg ?? 'An unknown error occurred.'); + SignInWithEmailAndPasswordFailureBase.fromCode(String code) + : super.fromCode(code) { + switch (code) { + case 'invalid-email': + msg = 'Email is not valid or badly formatted.'; + break; + case 'user-disabled': + msg = 'This user has been disabled. Please contact support for help.'; + break; + case 'user-not-found': + msg = 'Email is not found, please create an account.'; + break; + case 'wrong-password': + msg = 'Incorrect password, please try again.'; + break; + default: + this.code = 'unknown'; + msg = 'An unknown error occurred.'; + } + } +} + +/// {@macro send_email_verification_failure} +class SendEmailVerificationFailureBase + extends SendEmailVerificationFailureInterface { + SendEmailVerificationFailureBase([String? code, String? msg]) + : super(code ?? 'unknown', msg ?? 'An unknown error occurred.'); + + SendEmailVerificationFailureBase.fromCode(super.code) : super.fromCode(); +} + +/// {@macro send_password_reset_email_failure} +class SendPasswordResetEmailFailureBase + extends SendPasswordResetEmailFailureInterface { + SendPasswordResetEmailFailureBase([String? code, String? msg]) + : super(code ?? 'unknown', msg ?? 'An unknown error occurred.'); + SendPasswordResetEmailFailureBase.fromCode(super.code) : super.fromCode(); +} + +/// {@macro send_sign_in_link_email_failure} +class SendSignInLinkEmailFailureBase + extends SendSignInLinkEmailFailureInterface { + SendSignInLinkEmailFailureBase([String? code, String? msg]) + : super(code ?? 'unknown', msg ?? 'An unknown error occurred.'); + + SendSignInLinkEmailFailureBase.fromCode(super.code) : super.fromCode(); +} + +/// {@macro confirm_password_reset_failure} +class ConfirmPasswordResetFailureBase + extends ConfirmPasswordResetFailureInterface { + ConfirmPasswordResetFailureBase([String? code, String? msg]) + : super(code ?? 'unknown', msg ?? 'An unknown error occurred.'); + + ConfirmPasswordResetFailureBase.fromCode(super.code) : super.fromCode(); +} + +/// {@macro verify_password_reset_code_failure} +class VerifyPasswordResetCodeFailureBase + extends VerifyPasswordResetCodeFailureInterface { + VerifyPasswordResetCodeFailureBase([String? code, String? msg]) + : super(code ?? 'unknown', msg ?? 'An unknown error occurred.'); + + VerifyPasswordResetCodeFailureBase.fromCode(super.code) : super.fromCode(); +} + +/// {@macro refresh_failure} +class RefreshFailureBase extends RefreshFailureInterface { + RefreshFailureBase([String? code, String? msg]) + : super(code ?? 'unknown', msg ?? 'An unknown error occurred.'); + RefreshFailureBase.fromCode(super.code) : super.fromCode(); +} + +/// {@macro sign_out_failure} +class SignOutFailureBase extends SignOutFailureInterface { + SignOutFailureBase([String? code, String? msg]) + : super(code ?? 'unknown', msg ?? 'An unknown error occurred.'); + + SignOutFailureBase.fromCode(super.code) : super.fromCode(); +} + +/// {@macro reauthenticate_failure} +class ReauthenticateFailureBase extends ReauthenticateFailureInterface { + ReauthenticateFailureBase([String? code, String? msg]) + : super(code ?? 'unknown', msg ?? 'An unknown error occurred.'); + ReauthenticateFailureBase.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.'; + } + } +} + +/// {@macro update_email_failure} +class UpdateEmailFailureBase extends UpdateEmailFailureInterface { + UpdateEmailFailureBase([String? code, String? msg]) + : super(code ?? 'unknown', msg ?? 'An unknown error occurred.'); + UpdateEmailFailureBase.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.'; + } + } +} + +/// {@macro update_password_failure} +class UpdatePasswordFailureBase extends UpdatePasswordFailureInterface { + UpdatePasswordFailureBase([String? code, String? msg]) + : super(code ?? 'unknown', msg ?? 'An unknown error occurred.'); + UpdatePasswordFailureBase.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.'; + } + } +} + +/// {@macro model_parsing_failure} +class ModelParsingFailureBase extends ModelParsingFailureInterface { + ModelParsingFailureBase([String? code, String? msg]) + : super(code ?? 'unknown', msg ?? 'An unknown error occurred.'); + + ModelParsingFailureBase.fromCode(super.code) : super.fromCode(); +} + +/// {@macro delete_account_failure} +class DeleteAccountFailureBase extends DeleteAccountFailureInterface { + DeleteAccountFailureBase([String? code, String? msg]) + : super(code ?? 'unknown', msg ?? 'An unknown error occurred.'); + + DeleteAccountFailureBase.fromCode(String code) : super.fromCode(code) { + switch (code) { + 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.'; + } + } +} diff --git a/packages/wyatt_authentication_bloc/lib/src/core/exceptions/exceptions_mock.dart b/packages/wyatt_authentication_bloc/lib/src/core/exceptions/exceptions_mock.dart new file mode 100644 index 00000000..ad8b6826 --- /dev/null +++ b/packages/wyatt_authentication_bloc/lib/src/core/exceptions/exceptions_mock.dart @@ -0,0 +1,390 @@ +// Copyright (C) 2023 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 . + +part of 'exceptions.dart'; + +/// {@macro apply_action_code_failure} +class ApplyActionCodeFailureMock extends ApplyActionCodeFailureInterface { + ApplyActionCodeFailureMock([String? code, String? msg]) + : super(code ?? 'unknown', msg ?? 'An unknown error occurred.'); + ApplyActionCodeFailureMock.fromCode(String code) : super.fromCode(code) { + switch (code) { + case 'expired-action-code': + msg = 'Action code has expired.'; + break; + case 'invalid-action-code': + msg = 'Action code is invalid.'; + break; + case 'user-disabled': + msg = 'This user has been disabled. Please contact support for help.'; + break; + case 'user-not-found': + msg = 'Email is not found, please create an account.'; + break; + + default: + this.code = 'unknown'; + msg = 'An unknown error occurred.'; + } + } +} + +/// {@macro sign_up_with_email_and_password_failure} +class SignUpWithEmailAndPasswordFailureMock + extends SignUpWithEmailAndPasswordFailureInterface { + SignUpWithEmailAndPasswordFailureMock([String? code, String? msg]) + : super(code ?? 'unknown', msg ?? 'An unknown error occurred.'); + SignUpWithEmailAndPasswordFailureMock.fromCode(String code) + : super.fromCode(code) { + switch (code) { + case 'invalid-email': + msg = 'The email address is badly formatted.'; + break; + case 'user-disabled': + msg = 'This user has been disabled. Please contact support for help.'; + break; + case 'email-already-in-use': + msg = 'An account already exists for that email.'; + break; + case 'operation-not-allowed': + msg = 'Operation is not allowed. Please contact support.'; + break; + case 'weak-password': + msg = 'Please enter a stronger password.'; + break; + default: + this.code = 'unknown'; + msg = 'An unknown error occurred.'; + } + } +} + +/// {@macro fetch_sign_in_methods_failure} +class FetchSignInMethodsForEmailFailureMock + extends FetchSignInMethodsForEmailFailureInterface { + FetchSignInMethodsForEmailFailureMock([String? code, String? msg]) + : super(code ?? 'unknown', msg ?? 'An unknown error occurred.'); + FetchSignInMethodsForEmailFailureMock.fromCode(String code) + : super.fromCode(code) { + switch (code) { + case 'invalid-email': + msg = 'The email address is badly formatted.'; + break; + default: + this.code = 'unknown'; + msg = 'An unknown error occurred.'; + } + } +} + +/// {@macro sign_in_anonymously_failure} +class SignInAnonymouslyFailureMock + extends SignInAnonymouslyFailureInterface { + SignInAnonymouslyFailureMock([String? code, String? msg]) + : super(code ?? 'unknown', msg ?? 'An unknown error occurred.'); + SignInAnonymouslyFailureMock.fromCode(String code) + : super.fromCode(code) { + switch (code) { + case 'operation-not-allowed': + msg = 'Operation is not allowed. Please contact support.'; + break; + default: + this.code = 'unknown'; + msg = 'An unknown error occurred.'; + } + } +} + +/// {@macro sign_in_with_credential_failure} +class SignInWithCredentialFailureMock + extends SignInWithCredentialFailureInterface { + SignInWithCredentialFailureMock([String? code, String? msg]) + : super(code ?? 'unknown', msg ?? 'An unknown error occurred.'); + SignInWithCredentialFailureMock.fromCode(String code) + : super.fromCode(code) { + switch (code) { + case 'account-exists-with-different-credential': + msg = 'Account exists with different credentials.'; + break; + case 'invalid-credential': + msg = 'The credential received is malformed or has expired.'; + break; + case 'operation-not-allowed': + msg = 'Operation is not allowed. Please contact support.'; + break; + case 'user-disabled': + msg = 'This user has been disabled. Please contact support for help.'; + break; + case 'user-not-found': + msg = 'Email is not found, please create an account.'; + 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.'; + } + } +} + +/// {@macro sign_in_with_google_failure} +class SignInWithGoogleFailureMock + extends SignInWithCredentialFailureMock + implements SignInWithGoogleFailureInterface { + SignInWithGoogleFailureMock([super.code, super.msg]); + SignInWithGoogleFailureMock.fromCode(super.code) : super.fromCode(); +} + +/// {@macro sign_in_with_facebook_failure} +class SignInWithFacebookFailureMock + extends SignInWithCredentialFailureMock + implements SignInWithFacebookFailureInterface { + SignInWithFacebookFailureMock([super.code, super.msg]); + SignInWithFacebookFailureMock.fromCode(super.code) : super.fromCode(); +} + +/// {@macro sign_in_with_apple_failure} +class SignInWithAppleFailureMock extends SignInWithCredentialFailureMock + implements SignInWithAppleFailureInterface { + SignInWithAppleFailureMock([super.code, super.msg]); + SignInWithAppleFailureMock.fromCode(super.code) : super.fromCode(); +} + +/// {@macro sign_in_with_twitter_failure} +class SignInWithTwitterFailureMock + extends SignInWithCredentialFailureMock + implements SignInWithAppleFailureInterface { + SignInWithTwitterFailureMock([super.code, super.msg]); + SignInWithTwitterFailureMock.fromCode(super.code) : super.fromCode(); +} + +/// {@macro sign_in_with_email_link_failure} +class SignInWithEmailLinkFailureMock + extends SignInWithEmailLinkFailureInterface { + SignInWithEmailLinkFailureMock([String? code, String? msg]) + : super(code ?? 'unknown', msg ?? 'An unknown error occurred.'); + SignInWithEmailLinkFailureMock.fromCode(String code) + : super.fromCode(code) { + switch (code) { + case 'expired-action-code': + msg = 'Action code has expired.'; + break; + case 'invalid-email': + msg = 'Email is not valid or badly formatted.'; + break; + case 'user-disabled': + msg = 'This user has been disabled. Please contact support for help.'; + break; + default: + this.code = 'unknown'; + msg = 'An unknown error occurred.'; + } + } +} + +/// {@macro sign_in_with_email_and_password_failure} +class SignInWithEmailAndPasswordFailureMock + extends SignInWithEmailAndPasswordFailureInterface { + SignInWithEmailAndPasswordFailureMock([String? code, String? msg]) + : super(code ?? 'unknown', msg ?? 'An unknown error occurred.'); + SignInWithEmailAndPasswordFailureMock.fromCode(String code) + : super.fromCode(code) { + switch (code) { + case 'invalid-email': + msg = 'Email is not valid or badly formatted.'; + break; + case 'user-disabled': + msg = 'This user has been disabled. Please contact support for help.'; + break; + case 'user-not-found': + msg = 'Email is not found, please create an account.'; + break; + case 'wrong-password': + msg = 'Incorrect password, please try again.'; + break; + default: + this.code = 'unknown'; + msg = 'An unknown error occurred.'; + } + } +} + +/// {@macro send_email_verification_failure} +class SendEmailVerificationFailureMock + extends SendEmailVerificationFailureInterface { + SendEmailVerificationFailureMock([String? code, String? msg]) + : super(code ?? 'unknown', msg ?? 'An unknown error occurred.'); + + SendEmailVerificationFailureMock.fromCode(super.code) : super.fromCode(); +} + +/// {@macro send_password_reset_email_failure} +class SendPasswordResetEmailFailureMock + extends SendPasswordResetEmailFailureInterface { + SendPasswordResetEmailFailureMock([String? code, String? msg]) + : super(code ?? 'unknown', msg ?? 'An unknown error occurred.'); + SendPasswordResetEmailFailureMock.fromCode(super.code) : super.fromCode(); +} + +/// {@macro send_sign_in_link_email_failure} +class SendSignInLinkEmailFailureMock + extends SendSignInLinkEmailFailureInterface { + SendSignInLinkEmailFailureMock([String? code, String? msg]) + : super(code ?? 'unknown', msg ?? 'An unknown error occurred.'); + + SendSignInLinkEmailFailureMock.fromCode(super.code) : super.fromCode(); +} + +/// {@macro confirm_password_reset_failure} +class ConfirmPasswordResetFailureMock + extends ConfirmPasswordResetFailureInterface { + ConfirmPasswordResetFailureMock([String? code, String? msg]) + : super(code ?? 'unknown', msg ?? 'An unknown error occurred.'); + + ConfirmPasswordResetFailureMock.fromCode(super.code) : super.fromCode(); +} + +/// {@macro verify_password_reset_code_failure} +class VerifyPasswordResetCodeFailureMock + extends VerifyPasswordResetCodeFailureInterface { + VerifyPasswordResetCodeFailureMock([String? code, String? msg]) + : super(code ?? 'unknown', msg ?? 'An unknown error occurred.'); + + VerifyPasswordResetCodeFailureMock.fromCode(super.code) + : super.fromCode(); +} + +/// {@macro refresh_failure} +class RefreshFailureMock extends RefreshFailureInterface { + RefreshFailureMock([String? code, String? msg]) + : super(code ?? 'unknown', msg ?? 'An unknown error occurred.'); + RefreshFailureMock.fromCode(super.code) : super.fromCode(); +} + +/// {@macro sign_out_failure} +class SignOutFailureMock extends SignOutFailureInterface { + SignOutFailureMock([String? code, String? msg]) + : super(code ?? 'unknown', msg ?? 'An unknown error occurred.'); + + SignOutFailureMock.fromCode(super.code) : super.fromCode(); +} + +/// {@macro reauthenticate_failure} +class ReauthenticateFailureMock extends ReauthenticateFailureInterface { + ReauthenticateFailureMock([String? code, String? msg]) + : super(code ?? 'unknown', msg ?? 'An unknown error occurred.'); + ReauthenticateFailureMock.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.'; + } + } +} + +/// {@macro update_email_failure} +class UpdateEmailFailureMock extends UpdateEmailFailureInterface { + UpdateEmailFailureMock([String? code, String? msg]) + : super(code ?? 'unknown', msg ?? 'An unknown error occurred.'); + UpdateEmailFailureMock.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.'; + } + } +} + +/// {@macro update_password_failure} +class UpdatePasswordFailureMock extends UpdatePasswordFailureInterface { + UpdatePasswordFailureMock([String? code, String? msg]) + : super(code ?? 'unknown', msg ?? 'An unknown error occurred.'); + UpdatePasswordFailureMock.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.'; + } + } +} + +/// {@macro model_parsing_failure} +class ModelParsingFailureMock extends ModelParsingFailureInterface { + ModelParsingFailureMock([String? code, String? msg]) + : super(code ?? 'unknown', msg ?? 'An unknown error occurred.'); + + ModelParsingFailureMock.fromCode(super.code) : super.fromCode(); +} + +/// {@macro delete_account_failure} +class DeleteAccountFailureMock extends DeleteAccountFailureInterface { + DeleteAccountFailureMock([String? code, String? msg]) + : super(code ?? 'unknown', msg ?? 'An unknown error occurred.'); + + DeleteAccountFailureMock.fromCode(String code) : super.fromCode(code) { + switch (code) { + 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.'; + } + } +} diff --git a/packages/wyatt_authentication_bloc/lib/src/data/data_sources/local/authentication_secure_storage_cache_data_source_impl.dart b/packages/wyatt_authentication_bloc/lib/src/data/data_sources/local/authentication_secure_storage_cache_data_source_impl.dart new file mode 100644 index 00000000..a59f3383 --- /dev/null +++ b/packages/wyatt_authentication_bloc/lib/src/data/data_sources/local/authentication_secure_storage_cache_data_source_impl.dart @@ -0,0 +1,75 @@ +// Copyright (C) 2023 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:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:wyatt_authentication_bloc/src/core/constants/storage.dart'; +import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart'; + +/// {@template authentication_secure_storage_cache_data_source_impl} +/// A data source that manages the cache strategy. +/// This implementation uses Secure Storage. +/// {@endtemplate} +class AuthenticationSecureStorageCacheDataSourceImpl + extends AuthenticationCacheDataSource { + /// {@macro authentication_secure_storage_cache_data_source_impl} + const AuthenticationSecureStorageCacheDataSourceImpl({ + FlutterSecureStorage? secureStorage, + }) : _secureStorage = secureStorage ?? const FlutterSecureStorage(); + + final FlutterSecureStorage _secureStorage; + + @override + Future cacheAccount(Account account) async { + await _secureStorage.write( + key: AuthStorage.id, + value: account.id, + ); + await _secureStorage.write( + key: AuthStorage.email, + value: account.email, + ); + await _secureStorage.write( + key: AuthStorage.accessToken, + value: account.accessToken, + ); + } + + @override + Future getCachedAccount() async { + final id = await _secureStorage.read(key: AuthStorage.id); + final email = await _secureStorage.read(key: AuthStorage.email); + final accessToken = await _secureStorage.read(key: AuthStorage.accessToken); + + if (id == null || email == null || accessToken == null) { + return null; + } + + final currentAccount = AccountModel.fromSecureStorage( + id: id, + email: email, + accessToken: accessToken, + ); + + return currentAccount; + } + + @override + Future removeCachedAccount() async { + await _secureStorage.delete(key: AuthStorage.id); + await _secureStorage.delete(key: AuthStorage.email); + await _secureStorage.delete(key: AuthStorage.accessToken); + } +} diff --git a/packages/wyatt_authentication_bloc/lib/src/data/data_sources/local/local.dart b/packages/wyatt_authentication_bloc/lib/src/data/data_sources/local/local.dart index ba60b5e6..b9bf04e1 100644 --- a/packages/wyatt_authentication_bloc/lib/src/data/data_sources/local/local.dart +++ b/packages/wyatt_authentication_bloc/lib/src/data/data_sources/local/local.dart @@ -15,4 +15,5 @@ // along with this program. If not, see . export 'authentication_firebase_cache_data_source_impl.dart'; +export 'authentication_secure_storage_cache_data_source_impl.dart'; export 'authentication_session_data_source_impl.dart'; diff --git a/packages/wyatt_authentication_bloc/lib/src/data/data_sources/remote/authentication_base_data_source.dart b/packages/wyatt_authentication_bloc/lib/src/data/data_sources/remote/authentication_base_data_source.dart new file mode 100644 index 00000000..bbdd8dd9 --- /dev/null +++ b/packages/wyatt_authentication_bloc/lib/src/data/data_sources/remote/authentication_base_data_source.dart @@ -0,0 +1,106 @@ +// Copyright (C) 2023 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_authentication_bloc/wyatt_authentication_bloc.dart'; + +/// {@template authentication_base_data_source} +/// Base class for custom [AuthenticationRemoteDataSource] implementations. +/// It implements all methods as throwing [UnimplementedError]s. +/// {@endtemplate} +class AuthenticationBaseDataSource + extends AuthenticationRemoteDataSource { + /// {@macro authentication_base_data_source} + const AuthenticationBaseDataSource(); + + @override + Future confirmPasswordReset({ + required String code, + required String newPassword, + }) { + throw UnimplementedError(); + } + + @override + Future delete() { + throw UnimplementedError(); + } + + @override + Future reauthenticate() { + throw UnimplementedError(); + } + + @override + Future refresh() { + throw UnimplementedError(); + } + + @override + Future sendEmailVerification() { + throw UnimplementedError(); + } + + @override + Future sendPasswordResetEmail({required String email}) { + throw UnimplementedError(); + } + + @override + Future signInAnonymously() { + throw UnimplementedError(); + } + + @override + Future signInWithEmailAndPassword({ + required String email, + required String password, + }) { + throw UnimplementedError(); + } + + @override + Future signInWithGoogle() { + throw UnimplementedError(); + } + + @override + Future signOut() { + throw UnimplementedError(); + } + + @override + Future signUpWithEmailAndPassword({ + required String email, + required String password, + }) { + throw UnimplementedError(); + } + + @override + Future updateEmail({required String email}) { + throw UnimplementedError(); + } + + @override + Future updatePassword({required String password}) { + throw UnimplementedError(); + } + + @override + Future verifyPasswordResetCode({required String code}) { + throw UnimplementedError(); + } +} diff --git a/packages/wyatt_authentication_bloc/lib/src/data/data_sources/remote/authentication_mock_data_source_impl.dart b/packages/wyatt_authentication_bloc/lib/src/data/data_sources/remote/authentication_mock_data_source_impl.dart new file mode 100644 index 00000000..ff876dd5 --- /dev/null +++ b/packages/wyatt_authentication_bloc/lib/src/data/data_sources/remote/authentication_mock_data_source_impl.dart @@ -0,0 +1,201 @@ +// Copyright (C) 2023 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:math'; + +import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart'; +import 'package:wyatt_type_utils/wyatt_type_utils.dart'; + +/// {@template authentication_mock_data_source_impl} +/// Implementation of [AuthenticationRemoteDataSource] using Mocks. +/// {@endtemplate} +class AuthenticationMockDataSourceImpl + extends AuthenticationRemoteDataSource { + /// {@macro authentication_mock_data_source_impl} + AuthenticationMockDataSourceImpl({ + this.mockData = const { + 'test@test.fr': Pair('test12', '1'), + 'alice@test.fr': Pair('alice12', '2'), + 'bob@test.fr': Pair('bob1234', '3'), + }, + this.accessToken = 'mock_access_token', + }); + + /// Mock data. + /// Format: > + final Map> mockData; + + /// Current user. + Account? _currentUser; + + /// Access token. + final String accessToken; + + /// A random delay to simulate network latency. + Future _randomDelay() async { + await Future.delayed( + Duration(milliseconds: Random().nextInt(400) + 200), + ); + return; + } + + // SignUp/SignIn methods ==================================================== + + /// {@macro signup_pwd} + @override + Future signUpWithEmailAndPassword({ + required String email, + required String password, + }) async { + await _randomDelay(); + mockData[email] = Pair(password, '4'); + + return _currentUser = Account( + id: '4', + isAnonymous: false, + emailVerified: false, + providerId: 'mock', + email: email, + accessToken: accessToken, + ); + } + + /// {@macro signin_pwd} + @override + Future signInWithEmailAndPassword({ + required String email, + required String password, + }) async { + await _randomDelay(); + final pair = mockData[email]; + + if (pair == null || pair.left != password) { + throw SignInWithEmailAndPasswordFailureMock(); + } + + return _currentUser = Account( + id: pair.right!, + isAnonymous: false, + emailVerified: true, + providerId: 'mock', + email: email, + accessToken: accessToken, + ); + } + + /// {@macro signin_anom} + @override + Future signInAnonymously() async { + await _randomDelay(); + return _currentUser = Account( + id: '5', + isAnonymous: true, + emailVerified: false, + providerId: 'mock', + accessToken: accessToken, + ); + } + + /// {@macro signin_google} + @override + Future signInWithGoogle() async { + await _randomDelay(); + return _currentUser = Account( + id: '6', + isAnonymous: false, + emailVerified: true, + providerId: 'google.com', + email: 'mock@gmail.com', + accessToken: accessToken, + ); + } + + /// {@macro signout} + @override + Future signOut() async { + await _randomDelay(); + _currentUser = null; + } + + // Account management methods =============================================== + + /// {@macro refresh} + @override + Future refresh() async { + await _randomDelay(); + if (_currentUser == null) { + throw RefreshFailureMock(); + } + return _currentUser!; + } + + /// {@macro reauthenticate} + @override + Future reauthenticate() async { + await _randomDelay(); + if (_currentUser == null) { + throw ReauthenticateFailureMock(); + } + return _currentUser!; + } + + /// {@macro update_email} + @override + Future updateEmail({required String email}) async { + throw UnimplementedError(); + } + + /// {@macro update_password} + @override + Future updatePassword({required String password}) async { + throw UnimplementedError(); + } + + /// {@macro delete} + @override + Future delete() async { + throw UnimplementedError(); + } + + // Email related stuff ====================================================== + + /// {@macro send_email_verification} + @override + Future sendEmailVerification() async { + throw UnimplementedError(); + } + + /// {@macro send_password_reset_email} + @override + Future sendPasswordResetEmail({required String email}) async { + throw UnimplementedError(); + } + + /// {@macro confirm_password_reset} + @override + Future confirmPasswordReset({ + required String code, + required String newPassword, + }) async { + throw UnimplementedError(); + } + + /// {@macro verify_password_reset_code} + @override + Future verifyPasswordResetCode({required String code}) async { + throw UnimplementedError(); + } +} diff --git a/packages/wyatt_authentication_bloc/lib/src/data/data_sources/remote/remote.dart b/packages/wyatt_authentication_bloc/lib/src/data/data_sources/remote/remote.dart index 5a8c8679..e76b675f 100644 --- a/packages/wyatt_authentication_bloc/lib/src/data/data_sources/remote/remote.dart +++ b/packages/wyatt_authentication_bloc/lib/src/data/data_sources/remote/remote.dart @@ -14,4 +14,6 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +export 'authentication_base_data_source.dart'; export 'authentication_firebase_data_source_impl.dart'; +export 'authentication_mock_data_source_impl.dart'; diff --git a/packages/wyatt_authentication_bloc/lib/src/data/models/account_model.dart b/packages/wyatt_authentication_bloc/lib/src/data/models/account_model.dart index ebc29f93..683dd500 100644 --- a/packages/wyatt_authentication_bloc/lib/src/data/models/account_model.dart +++ b/packages/wyatt_authentication_bloc/lib/src/data/models/account_model.dart @@ -77,6 +77,30 @@ class AccountModel extends Account { } } + /// {@macro account_model} + factory AccountModel.fromSecureStorage({ + required String? id, + required String? email, + required String? accessToken, + }) { + if (id != null && email != null && accessToken != null) { + return AccountModel._( + user: null, + id: id, + emailVerified: false, + isAnonymous: false, + providerId: '', + email: email, + accessToken: accessToken, + ); + } else { + throw Exception( + 'null-id-email-access-token' + 'id, email, and accessToken cannot be null', + ); + } + } + const AccountModel._({ required this.user, required super.id, 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 index 652cca7a..9b8bc99a 100644 --- 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 @@ -16,8 +16,6 @@ import 'package:wyatt_architecture/wyatt_architecture.dart'; import 'package:wyatt_authentication_bloc/src/core/utils/forms.dart'; -import 'package:wyatt_authentication_bloc/src/domain/data_sources/local/authentication_cache_data_source.dart'; -import 'package:wyatt_authentication_bloc/src/domain/data_sources/local/authentication_session_data_source.dart'; import 'package:wyatt_authentication_bloc/src/domain/domain.dart'; import 'package:wyatt_form_bloc/wyatt_form_bloc.dart'; import 'package:wyatt_type_utils/wyatt_type_utils.dart'; @@ -152,6 +150,8 @@ class AuthenticationRepositoryImpl email: email, password: password, ); + // Cache the account + await authenticationCacheDataSource.cacheAccount(account); return account; }, (error) => error, @@ -170,6 +170,8 @@ class AuthenticationRepositoryImpl email: email, password: password, ); + // Cache the account + await authenticationCacheDataSource.cacheAccount(account); return account; }, (error) => error, @@ -194,6 +196,8 @@ class AuthenticationRepositoryImpl () async { final account = await authenticationRemoteDataSource.signInWithGoogle(); + // Cache the account + await authenticationCacheDataSource.cacheAccount(account); return account; }, (error) => error, @@ -205,6 +209,8 @@ class AuthenticationRepositoryImpl Result.tryCatchAsync( () async { await authenticationRemoteDataSource.signOut(); + // Remove the cached account + await authenticationCacheDataSource.removeCachedAccount(); }, (error) => error, ); @@ -217,6 +223,8 @@ class AuthenticationRepositoryImpl Result.tryCatchAsync( () async { final account = await authenticationRemoteDataSource.refresh(); + // Cache the account + await authenticationCacheDataSource.cacheAccount(account); return account; }, (error) => error, @@ -228,6 +236,8 @@ class AuthenticationRepositoryImpl Result.tryCatchAsync( () async { final account = await authenticationRemoteDataSource.reauthenticate(); + // Cache the account + await authenticationCacheDataSource.cacheAccount(account); return account; }, (error) => error, @@ -240,6 +250,8 @@ class AuthenticationRepositoryImpl () async { final account = await authenticationRemoteDataSource.updateEmail(email: email); + // Cache the account + await authenticationCacheDataSource.cacheAccount(account); return account; }, (error) => error, @@ -253,6 +265,8 @@ class AuthenticationRepositoryImpl final account = await authenticationRemoteDataSource.updatePassword( password: password, ); + // Cache the account + await authenticationCacheDataSource.cacheAccount(account); return account; }, (error) => error, @@ -262,7 +276,11 @@ class AuthenticationRepositoryImpl @override FutureOrResult delete() => Result.tryCatchAsync( - () async => authenticationRemoteDataSource.delete(), + () async { + await authenticationRemoteDataSource.delete(); + // Remove the cached account + await authenticationCacheDataSource.removeCachedAccount(); + }, (error) => error, ); diff --git a/packages/wyatt_authentication_bloc/pubspec.yaml b/packages/wyatt_authentication_bloc/pubspec.yaml index 2caaecd6..707bb932 100644 --- a/packages/wyatt_authentication_bloc/pubspec.yaml +++ b/packages/wyatt_authentication_bloc/pubspec.yaml @@ -29,6 +29,8 @@ dependencies: wyatt_type_utils: hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub/ version: ^0.0.4 + flutter_secure_storage: ^8.0.0 + http: ^0.13.5 dev_dependencies: flutter_test: { sdk: flutter }