authentication/feature/google_signin #105

Merged
malo merged 7 commits from authentication/feature/google_signin into master 2022-12-13 22:07:17 +00:00
44 changed files with 149 additions and 213 deletions
Showing only changes of commit 37e00fe9c4 - Show all commits

View File

@ -1,4 +1 @@
include: package:wyatt_analysis/analysis_options.flutter.yaml include: package:wyatt_analysis/analysis_options.flutter.yaml
analyzer:
exclude: "!example/**"

View File

@ -30,10 +30,12 @@ class SubPage extends StatelessWidget {
title: const Text('Sub'), title: const Text('Sub'),
actions: [ actions: [
IconButton( IconButton(
onPressed: () => context.read<AuthenticationCubit<int>>().signOut(), onPressed: () =>
context.read<AuthenticationCubit<int>>().signOut(),
icon: const Icon(Icons.logout_rounded)), icon: const Icon(Icons.logout_rounded)),
IconButton( IconButton(
onPressed: () => context.read<AuthenticationRepository<int>>().refresh(), onPressed: () =>
context.read<AuthenticationRepository<int>>().refresh(),
icon: const Icon(Icons.refresh)) icon: const Icon(Icons.refresh))
], ],
), ),

View File

@ -18,4 +18,3 @@ export 'constants/form_field.dart';
export 'constants/form_name.dart'; export 'constants/form_name.dart';
export 'enums/enums.dart'; export 'enums/enums.dart';
export 'exceptions/exceptions.dart'; export 'exceptions/exceptions.dart';
export 'utils/utils.dart';

View File

@ -20,15 +20,14 @@ part 'exceptions_firebase.dart';
abstract class AuthenticationFailureInterface extends AppException abstract class AuthenticationFailureInterface extends AppException
implements Exception { implements Exception {
AuthenticationFailureInterface(this.code, this.msg);
AuthenticationFailureInterface.fromCode(this.code)
: msg = 'An unknown error occurred.';
String code; String code;
String msg; String msg;
@override @override
String get message => msg; String get message => msg;
AuthenticationFailureInterface(this.code, this.msg);
AuthenticationFailureInterface.fromCode(this.code)
: msg = 'An unknown error occurred.';
} }
/// {@template apply_action_code_failure} /// {@template apply_action_code_failure}

View File

@ -1,39 +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:convert';
import 'dart:math';
import 'package:crypto/crypto.dart';
class Cryptography {
/// Generates a cryptographically secure random nonce, to be included in a
/// credential request.
static String generateNonce([int length = 32]) {
const charset =
'0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._';
final random = Random.secure();
return List.generate(length, (_) => charset[random.nextInt(charset.length)])
.join();
}
/// Returns the sha256 hash of [input] in hex notation.
static String sha256ofString(String input) {
final bytes = utf8.encode(input);
final digest = sha256.convert(bytes);
return digest.toString();
}
}

View File

@ -1,17 +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/>.
export 'cryptography.dart';

View File

@ -20,11 +20,11 @@ import 'package:wyatt_type_utils/wyatt_type_utils.dart';
class AuthenticationCacheDataSourceImpl<T extends Object> class AuthenticationCacheDataSourceImpl<T extends Object>
extends AuthenticationCacheDataSource<T> { extends AuthenticationCacheDataSource<T> {
AuthenticationCacheDataSourceImpl();
Account? _account; Account? _account;
T? _data; T? _data;
AuthenticationCacheDataSourceImpl();
@override @override
Future<void> storeAccount(Account? account) async { Future<void> storeAccount(Account? account) async {
_account = account; _account = account;

View File

@ -20,12 +20,12 @@ import 'package:wyatt_type_utils/wyatt_type_utils.dart';
class AuthenticationFirebaseDataSourceImpl class AuthenticationFirebaseDataSourceImpl
extends AuthenticationRemoteDataSource { extends AuthenticationRemoteDataSource {
final FirebaseAuth _firebaseAuth;
UserCredential? _latestCreds;
AuthenticationFirebaseDataSourceImpl({FirebaseAuth? firebaseAuth}) AuthenticationFirebaseDataSourceImpl({FirebaseAuth? firebaseAuth})
: _firebaseAuth = firebaseAuth ?? FirebaseAuth.instance; : _firebaseAuth = firebaseAuth ?? FirebaseAuth.instance;
final FirebaseAuth _firebaseAuth;
UserCredential? _latestCreds;
Account _mapper(User user) => AccountModel( Account _mapper(User user) => AccountModel(
uid: user.uid, uid: user.uid,
emailVerified: user.emailVerified, emailVerified: user.emailVerified,

View File

@ -21,6 +21,11 @@ import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
import 'package:wyatt_type_utils/wyatt_type_utils.dart'; import 'package:wyatt_type_utils/wyatt_type_utils.dart';
class AuthenticationMockDataSourceImpl extends AuthenticationRemoteDataSource { class AuthenticationMockDataSourceImpl extends AuthenticationRemoteDataSource {
AuthenticationMockDataSourceImpl({
this.idToken = 'fake-id-token',
this.registeredAccounts,
});
Pair<Account, String>? _connectedMock; Pair<Account, String>? _connectedMock;
Pair<Account, String>? _registeredMock; Pair<Account, String>? _registeredMock;
DateTime _lastSignInTime = DateTime.now(); DateTime _lastSignInTime = DateTime.now();
@ -30,11 +35,6 @@ class AuthenticationMockDataSourceImpl extends AuthenticationRemoteDataSource {
final List<Pair<Account, String>>? registeredAccounts; final List<Pair<Account, String>>? registeredAccounts;
final String idToken; final String idToken;
AuthenticationMockDataSourceImpl({
this.idToken = 'fake-id-token',
this.registeredAccounts,
});
Future<void> _randomDelay() async { Future<void> _randomDelay() async {
await Future<void>.delayed( await Future<void>.delayed(
Duration(milliseconds: Random().nextInt(400) + 200), Duration(milliseconds: Random().nextInt(400) + 200),

View File

@ -72,7 +72,8 @@ class AccountModel extends Account {
String? phoneNumber, String? phoneNumber,
String? photoURL, String? photoURL,
String? providerId, String? providerId,
}) => AccountModel( }) =>
AccountModel(
uid: uid ?? this.uid, uid: uid ?? this.uid,
email: email ?? this.email, email: email ?? this.email,
creationTime: creationTime ?? this.creationTime, creationTime: creationTime ?? this.creationTime,

View File

@ -29,7 +29,8 @@ class AccountWrapperModel<T> extends AccountWrapper<T> {
AccountWrapperModel<T> copyWith({ AccountWrapperModel<T> copyWith({
Account? account, Account? account,
T? data, T? data,
}) => AccountWrapperModel<T>( }) =>
AccountWrapperModel<T>(
account ?? this.account, account ?? this.account,
data ?? this.data, data ?? this.data,
); );

View File

@ -42,19 +42,6 @@ typedef OnAuthChange<T> = FutureOrResult<T?> Function(
class AuthenticationRepositoryImpl<T extends Object> class AuthenticationRepositoryImpl<T extends Object>
extends AuthenticationRepository<T> { extends AuthenticationRepository<T> {
final AuthenticationCacheDataSource<T> _authenticationLocalDataSource;
final AuthenticationRemoteDataSource _authenticationRemoteDataSource;
late FormRepository _formRepository;
final OnSignUpSuccess<T>? _onSignUpSuccess;
final OnAuthChange<T>? _onAccountChanges;
final StreamController<FutureOrResult<AccountWrapper<T>>> _signUpStream =
StreamController();
bool _pause = false; // Semaphore
AuthenticationRepositoryImpl({ AuthenticationRepositoryImpl({
required AuthenticationCacheDataSource<T> authenticationCacheDataSource, required AuthenticationCacheDataSource<T> authenticationCacheDataSource,
required AuthenticationRemoteDataSource authenticationRemoteDataSource, required AuthenticationRemoteDataSource authenticationRemoteDataSource,
@ -107,6 +94,19 @@ class AuthenticationRepositoryImpl<T extends Object>
); );
} }
final AuthenticationCacheDataSource<T> _authenticationLocalDataSource;
final AuthenticationRemoteDataSource _authenticationRemoteDataSource;
late FormRepository _formRepository;
final OnSignUpSuccess<T>? _onSignUpSuccess;
final OnAuthChange<T>? _onAccountChanges;
final StreamController<FutureOrResult<AccountWrapper<T>>> _signUpStream =
StreamController();
bool _pause = false;
@override @override
FormRepository get formRepository => _formRepository; FormRepository get formRepository => _formRepository;

View File

@ -26,14 +26,13 @@ import 'package:wyatt_type_utils/wyatt_type_utils.dart';
part 'authentication_state.dart'; part 'authentication_state.dart';
class AuthenticationCubit<Extra> extends Cubit<AuthenticationState<Extra>> { class AuthenticationCubit<Extra> extends Cubit<AuthenticationState<Extra>> {
final AuthenticationRepository<Extra> _authenticationRepository;
AuthenticationCubit({ AuthenticationCubit({
required AuthenticationRepository<Extra> authenticationRepository, required AuthenticationRepository<Extra> authenticationRepository,
}) : _authenticationRepository = authenticationRepository, }) : _authenticationRepository = authenticationRepository,
super(const AuthenticationState.unknown()) { super(const AuthenticationState.unknown()) {
_listenForAuthenticationChanges(); _listenForAuthenticationChanges();
} }
final AuthenticationRepository<Extra> _authenticationRepository;
void _listenForAuthenticationChanges() { void _listenForAuthenticationChanges() {
_authenticationRepository.streamAccount().listen((accountFutureResult) { _authenticationRepository.streamAccount().listen((accountFutureResult) {

View File

@ -17,13 +17,8 @@
part of 'authentication_cubit.dart'; part of 'authentication_cubit.dart';
class AuthenticationState<Extra> extends Equatable { class AuthenticationState<Extra> extends Equatable {
final AuthenticationStatus status; const AuthenticationState.unauthenticated()
final AccountWrapper<Extra>? accountWrapper; : this._(status: AuthenticationStatus.unauthenticated);
const AuthenticationState._({required this.status, this.accountWrapper});
const AuthenticationState.unknown()
: this._(status: AuthenticationStatus.unknown);
const AuthenticationState.authenticated(AccountWrapper<Extra> accountWrapper) const AuthenticationState.authenticated(AccountWrapper<Extra> accountWrapper)
: this._( : this._(
@ -31,8 +26,12 @@ class AuthenticationState<Extra> extends Equatable {
accountWrapper: accountWrapper, accountWrapper: accountWrapper,
); );
const AuthenticationState.unauthenticated() const AuthenticationState.unknown()
: this._(status: AuthenticationStatus.unauthenticated); : this._(status: AuthenticationStatus.unknown);
const AuthenticationState._({required this.status, this.accountWrapper});
final AuthenticationStatus status;
final AccountWrapper<Extra>? accountWrapper;
@override @override
List<Object?> get props => [status, accountWrapper]; List<Object?> get props => [status, accountWrapper];

View File

@ -24,12 +24,11 @@ import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
part 'email_verification_state.dart'; part 'email_verification_state.dart';
class EmailVerificationCubit<Extra> extends Cubit<EmailVerificationState> { class EmailVerificationCubit<Extra> extends Cubit<EmailVerificationState> {
final AuthenticationRepository<Extra> _authenticationRepository;
EmailVerificationCubit({ EmailVerificationCubit({
required AuthenticationRepository<Extra> authenticationRepository, required AuthenticationRepository<Extra> authenticationRepository,
}) : _authenticationRepository = authenticationRepository, }) : _authenticationRepository = authenticationRepository,
super(const EmailVerificationState()); super(const EmailVerificationState());
final AuthenticationRepository<Extra> _authenticationRepository;
FutureOr<void> sendEmailVerification() async { FutureOr<void> sendEmailVerification() async {
emit(state.copyWith(status: FormStatus.submissionInProgress)); emit(state.copyWith(status: FormStatus.submissionInProgress));

View File

@ -25,10 +25,6 @@ import 'package:wyatt_type_utils/wyatt_type_utils.dart';
part 'password_reset_state.dart'; part 'password_reset_state.dart';
class PasswordResetCubit<Extra> extends FormDataCubit<PasswordResetState> { class PasswordResetCubit<Extra> extends FormDataCubit<PasswordResetState> {
final AuthenticationRepository<Extra> _authenticationRepository;
FormRepository get _formRepository =>
_authenticationRepository.formRepository;
PasswordResetCubit({ PasswordResetCubit({
required AuthenticationRepository<Extra> authenticationRepository, required AuthenticationRepository<Extra> authenticationRepository,
}) : _authenticationRepository = authenticationRepository, }) : _authenticationRepository = authenticationRepository,
@ -38,6 +34,9 @@ class PasswordResetCubit<Extra> extends FormDataCubit<PasswordResetState> {
.accessForm(AuthFormName.passwordResetForm), .accessForm(AuthFormName.passwordResetForm),
), ),
); );
final AuthenticationRepository<Extra> _authenticationRepository;
FormRepository get _formRepository =>
_authenticationRepository.formRepository;
@override @override
String get formName => AuthFormName.passwordResetForm; String get formName => AuthFormName.passwordResetForm;

View File

@ -17,13 +17,12 @@
part of 'password_reset_cubit.dart'; part of 'password_reset_cubit.dart';
class PasswordResetState extends FormDataState { class PasswordResetState extends FormDataState {
Email get email => form.validatorOf(AuthFormField.email);
const PasswordResetState({ const PasswordResetState({
required super.form, required super.form,
super.status = FormStatus.pure, super.status = FormStatus.pure,
super.errorMessage, super.errorMessage,
}); });
Email get email => form.validatorOf(AuthFormField.email);
PasswordResetState copyWith({ PasswordResetState copyWith({
WyattForm? form, WyattForm? form,

View File

@ -23,10 +23,6 @@ import 'package:wyatt_type_utils/wyatt_type_utils.dart';
part 'sign_in_state.dart'; part 'sign_in_state.dart';
class SignInCubit<Extra> extends FormDataCubit<SignInState> { class SignInCubit<Extra> extends FormDataCubit<SignInState> {
final AuthenticationRepository<Extra> _authenticationRepository;
FormRepository get _formRepository =>
_authenticationRepository.formRepository;
SignInCubit({ SignInCubit({
required AuthenticationRepository<Extra> authenticationRepository, required AuthenticationRepository<Extra> authenticationRepository,
}) : _authenticationRepository = authenticationRepository, }) : _authenticationRepository = authenticationRepository,
@ -36,6 +32,9 @@ class SignInCubit<Extra> extends FormDataCubit<SignInState> {
.accessForm(AuthFormName.signInForm), .accessForm(AuthFormName.signInForm),
), ),
); );
final AuthenticationRepository<Extra> _authenticationRepository;
FormRepository get _formRepository =>
_authenticationRepository.formRepository;
@override @override
String get formName => AuthFormName.signInForm; String get formName => AuthFormName.signInForm;

View File

@ -17,17 +17,17 @@
part of 'sign_in_cubit.dart'; part of 'sign_in_cubit.dart';
class SignInState extends FormDataState { class SignInState extends FormDataState {
FormInputValidator<String?, ValidationError> get email =>
form.validatorOf(AuthFormField.email);
FormInputValidator<String?, ValidationError> get password =>
form.validatorOf(AuthFormField.password);
const SignInState({ const SignInState({
required super.form, required super.form,
super.status = FormStatus.pure, super.status = FormStatus.pure,
super.errorMessage, super.errorMessage,
}); });
FormInputValidator<String?, ValidationError> get email =>
form.validatorOf(AuthFormField.email);
FormInputValidator<String?, ValidationError> get password =>
form.validatorOf(AuthFormField.password);
SignInState copyWith({ SignInState copyWith({
WyattForm? form, WyattForm? form,
FormStatus? status, FormStatus? status,

View File

@ -25,10 +25,6 @@ import 'package:wyatt_type_utils/wyatt_type_utils.dart';
part 'sign_up_state.dart'; part 'sign_up_state.dart';
class SignUpCubit<Extra> extends FormDataCubit<SignUpState> { class SignUpCubit<Extra> extends FormDataCubit<SignUpState> {
final AuthenticationRepository<Extra> _authenticationRepository;
FormRepository get _formRepository =>
_authenticationRepository.formRepository;
SignUpCubit({ SignUpCubit({
required AuthenticationRepository<Extra> authenticationRepository, required AuthenticationRepository<Extra> authenticationRepository,
}) : _authenticationRepository = authenticationRepository, }) : _authenticationRepository = authenticationRepository,
@ -38,6 +34,9 @@ class SignUpCubit<Extra> extends FormDataCubit<SignUpState> {
.accessForm(AuthFormName.signUpForm), .accessForm(AuthFormName.signUpForm),
), ),
); );
final AuthenticationRepository<Extra> _authenticationRepository;
FormRepository get _formRepository =>
_authenticationRepository.formRepository;
@override @override
String get formName => AuthFormName.signUpForm; String get formName => AuthFormName.signUpForm;

View File

@ -17,16 +17,15 @@
part of 'sign_up_cubit.dart'; part of 'sign_up_cubit.dart';
class SignUpState extends FormDataState { class SignUpState extends FormDataState {
FormInputValidator<String?, ValidationError> get email =>
form.validatorOf(AuthFormField.email);
FormInputValidator<String?, ValidationError> get password =>
form.validatorOf(AuthFormField.password);
const SignUpState({ const SignUpState({
required super.form, required super.form,
super.status = FormStatus.pure, super.status = FormStatus.pure,
super.errorMessage, super.errorMessage,
}); });
FormInputValidator<String?, ValidationError> get email =>
form.validatorOf(AuthFormField.email);
FormInputValidator<String?, ValidationError> get password =>
form.validatorOf(AuthFormField.password);
SignUpState copyWith({ SignUpState copyWith({
WyattForm? form, WyattForm? form,