Skip to content

Commit db19d19

Browse files
committed
feat(auth): Refactor authentication flow
- Uses code verification - Added auth state listener - Updated states and events
1 parent b49d5f4 commit db19d19

File tree

4 files changed

+153
-146
lines changed

4 files changed

+153
-146
lines changed
Lines changed: 102 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
1-
//
2-
// ignore_for_file: lines_longer_than_80_chars
3-
41
import 'dart:async';
52

63
import 'package:bloc/bloc.dart';
74
import 'package:equatable/equatable.dart';
8-
import 'package:ht_authentication_client/ht_authentication_client.dart';
9-
import 'package:ht_authentication_repository/ht_authentication_repository.dart';
5+
import 'package:ht_auth_repository/ht_auth_repository.dart';
6+
import 'package:ht_shared/ht_shared.dart'
7+
show
8+
AuthenticationException,
9+
HtHttpException,
10+
InvalidInputException,
11+
NetworkException,
12+
OperationFailedException,
13+
ServerException,
14+
User;
1015

1116
part 'authentication_event.dart';
1217
part 'authentication_state.dart';
@@ -17,116 +22,126 @@ part 'authentication_state.dart';
1722
class AuthenticationBloc
1823
extends Bloc<AuthenticationEvent, AuthenticationState> {
1924
/// {@macro authentication_bloc}
20-
AuthenticationBloc({
21-
required HtAuthenticationRepository authenticationRepository,
22-
}) : _authenticationRepository = authenticationRepository,
23-
super(AuthenticationInitial()) {
24-
on<AuthenticationSendSignInLinkRequested>(
25-
_onAuthenticationSendSignInLinkRequested,
26-
);
27-
on<AuthenticationSignInWithLinkAttempted>(
28-
_onAuthenticationSignInWithLinkAttempted,
25+
AuthenticationBloc({required HtAuthRepository authenticationRepository})
26+
: _authenticationRepository = authenticationRepository,
27+
super(AuthenticationInitial()) {
28+
// Listen to authentication state changes from the repository
29+
_authenticationRepository.authStateChanges.listen(
30+
(user) => add(_AuthenticationUserChanged(user: user)),
2931
);
30-
on<AuthenticationGoogleSignInRequested>(
31-
_onAuthenticationGoogleSignInRequested,
32+
33+
on<_AuthenticationUserChanged>(_onAuthenticationUserChanged);
34+
on<AuthenticationRequestSignInCodeRequested>(
35+
_onAuthenticationRequestSignInCodeRequested,
3236
);
37+
on<AuthenticationVerifyCodeRequested>(_onAuthenticationVerifyCodeRequested);
3338
on<AuthenticationAnonymousSignInRequested>(
3439
_onAuthenticationAnonymousSignInRequested,
3540
);
3641
on<AuthenticationSignOutRequested>(_onAuthenticationSignOutRequested);
37-
on<AuthenticationDeleteAccountRequested>(
38-
_onAuthenticationDeleteAccountRequested,
39-
);
4042
}
4143

42-
final HtAuthenticationRepository _authenticationRepository;
44+
final HtAuthRepository _authenticationRepository;
4345

44-
/// Handles [AuthenticationSendSignInLinkRequested] events.
45-
Future<void> _onAuthenticationSendSignInLinkRequested(
46-
AuthenticationSendSignInLinkRequested event,
46+
/// Handles [_AuthenticationUserChanged] events.
47+
Future<void> _onAuthenticationUserChanged(
48+
_AuthenticationUserChanged event,
49+
Emitter<AuthenticationState> emit,
50+
) async {
51+
if (event.user != null) {
52+
emit(AuthenticationAuthenticated(user: event.user!));
53+
} else {
54+
emit(AuthenticationUnauthenticated());
55+
}
56+
}
57+
58+
/// Handles [AuthenticationRequestSignInCodeRequested] events.
59+
Future<void> _onAuthenticationRequestSignInCodeRequested(
60+
AuthenticationRequestSignInCodeRequested event,
4761
Emitter<AuthenticationState> emit,
4862
) async {
4963
// Validate email format (basic check)
5064
if (event.email.isEmpty || !event.email.contains('@')) {
5165
emit(const AuthenticationFailure('Please enter a valid email address.'));
5266
return;
5367
}
54-
emit(AuthenticationLinkSending()); // Indicate link sending
68+
emit(
69+
AuthenticationRequestCodeLoading(),
70+
); // Indicate code request is sending
5571
try {
56-
// Simply call the repository method, email temprary storage storage
57-
// is handled internally
58-
await _authenticationRepository.sendSignInLinkToEmail(email: event.email);
59-
emit(AuthenticationLinkSentSuccess()); // Confirm link sent
60-
} on SendSignInLinkException catch (e) {
61-
emit(AuthenticationFailure('Failed to send link: ${e.error}'));
72+
await _authenticationRepository.requestSignInCode(event.email);
73+
emit(
74+
AuthenticationCodeSentSuccess(email: event.email),
75+
); // Confirm code requested and include email
76+
} on InvalidInputException catch (e) {
77+
emit(AuthenticationFailure('Invalid input: ${e.message}'));
78+
} on NetworkException catch (_) {
79+
emit(const AuthenticationFailure('Network error occurred.'));
80+
} on ServerException catch (e) {
81+
emit(AuthenticationFailure('Server error: ${e.message}'));
82+
} on OperationFailedException catch (e) {
83+
emit(AuthenticationFailure('Operation failed: ${e.message}'));
84+
} on HtHttpException catch (e) {
85+
// Catch any other HtHttpException subtypes
86+
emit(AuthenticationFailure('HTTP error: ${e.message}'));
6287
} catch (e) {
6388
// Catch any other unexpected errors
6489
emit(AuthenticationFailure('An unexpected error occurred: $e'));
6590
// Optionally log the stackTrace here
6691
}
6792
}
6893

69-
/// Handles [AuthenticationSignInWithLinkAttempted] events.
70-
/// This assumes the event is dispatched after the app receives the deep link.
71-
Future<void> _onAuthenticationSignInWithLinkAttempted(
72-
AuthenticationSignInWithLinkAttempted event,
94+
/// Handles [AuthenticationVerifyCodeRequested] events.
95+
Future<void> _onAuthenticationVerifyCodeRequested(
96+
AuthenticationVerifyCodeRequested event,
7397
Emitter<AuthenticationState> emit,
7498
) async {
75-
emit(AuthenticationLoading()); // General loading for sign-in attempt
99+
emit(AuthenticationLoading()); // Indicate code verification is loading
76100
try {
77-
// Call the updated repository method (no email needed here)
78-
await _authenticationRepository.signInWithEmailLink(
79-
emailLink: event.emailLink,
80-
);
81-
// On success, AppBloc should react to the user stream change from the repo.
82-
// Resetting to Initial state here.
83-
emit(AuthenticationInitial());
84-
} on InvalidSignInLinkException catch (e) {
85-
emit(
86-
AuthenticationFailure(
87-
'Sign in failed: Invalid or expired link. ${e.error}',
88-
),
89-
);
101+
await _authenticationRepository.verifySignInCode(event.email, event.code);
102+
// On success, the _AuthenticationUserChanged listener will handle
103+
// emitting AuthenticationAuthenticated.
104+
} on InvalidInputException catch (e) {
105+
emit(AuthenticationFailure('Invalid input: ${e.message}'));
106+
} on AuthenticationException catch (e) {
107+
emit(AuthenticationFailure('Authentication failed: ${e.message}'));
108+
} on NetworkException catch (_) {
109+
emit(const AuthenticationFailure('Network error occurred.'));
110+
} on ServerException catch (e) {
111+
emit(AuthenticationFailure('Server error: ${e.message}'));
112+
} on OperationFailedException catch (e) {
113+
emit(AuthenticationFailure('Operation failed: ${e.message}'));
114+
} on HtHttpException catch (e) {
115+
// Catch any other HtHttpException subtypes
116+
emit(AuthenticationFailure('HTTP error: ${e.message}'));
90117
} catch (e) {
91118
// Catch any other unexpected errors
92-
emit(
93-
AuthenticationFailure(
94-
'An unexpected error occurred during sign in: $e',
95-
),
96-
);
119+
emit(AuthenticationFailure('An unexpected error occurred: $e'));
97120
// Optionally log the stackTrace here
98121
}
99122
}
100123

101-
/// Handles [AuthenticationGoogleSignInRequested] events.
102-
Future<void> _onAuthenticationGoogleSignInRequested(
103-
AuthenticationGoogleSignInRequested event,
104-
Emitter<AuthenticationState> emit,
105-
) async {
106-
emit(AuthenticationLoading());
107-
try {
108-
await _authenticationRepository.signInWithGoogle();
109-
emit(AuthenticationInitial());
110-
} on GoogleSignInException catch (e) {
111-
emit(AuthenticationFailure(e.toString()));
112-
} catch (e) {
113-
emit(AuthenticationFailure(e.toString()));
114-
}
115-
}
116-
117124
/// Handles [AuthenticationAnonymousSignInRequested] events.
118125
Future<void> _onAuthenticationAnonymousSignInRequested(
119126
AuthenticationAnonymousSignInRequested event,
120127
Emitter<AuthenticationState> emit,
121128
) async {
122-
emit(AuthenticationLoading());
129+
emit(AuthenticationLoading()); // Indicate anonymous sign-in is loading
123130
try {
124131
await _authenticationRepository.signInAnonymously();
125-
emit(AuthenticationInitial());
126-
} on AnonymousLoginException catch (e) {
127-
emit(AuthenticationFailure(e.toString()));
132+
// On success, the _AuthenticationUserChanged listener will handle
133+
// emitting AuthenticationAuthenticated.
134+
} on NetworkException catch (_) {
135+
emit(const AuthenticationFailure('Network error occurred.'));
136+
} on ServerException catch (e) {
137+
emit(AuthenticationFailure('Server error: ${e.message}'));
138+
} on OperationFailedException catch (e) {
139+
emit(AuthenticationFailure('Operation failed: ${e.message}'));
140+
} on HtHttpException catch (e) {
141+
// Catch any other HtHttpException subtypes
142+
emit(AuthenticationFailure('HTTP error: ${e.message}'));
128143
} catch (e) {
129-
emit(AuthenticationFailure(e.toString()));
144+
emit(AuthenticationFailure('An unexpected error occurred: $e'));
130145
}
131146
}
132147

@@ -135,29 +150,22 @@ class AuthenticationBloc
135150
AuthenticationSignOutRequested event,
136151
Emitter<AuthenticationState> emit,
137152
) async {
138-
emit(AuthenticationLoading());
153+
emit(AuthenticationLoading()); // Indicate sign-out is loading
139154
try {
140155
await _authenticationRepository.signOut();
141-
emit(AuthenticationInitial());
142-
} on LogoutException catch (e) {
143-
emit(AuthenticationFailure(e.toString()));
144-
} catch (e) {
145-
emit(AuthenticationFailure(e.toString()));
146-
}
147-
}
148-
149-
Future<void> _onAuthenticationDeleteAccountRequested(
150-
AuthenticationDeleteAccountRequested event,
151-
Emitter<AuthenticationState> emit,
152-
) async {
153-
emit(AuthenticationLoading());
154-
try {
155-
await _authenticationRepository.deleteAccount();
156-
emit(AuthenticationInitial());
157-
} on DeleteAccountException catch (e) {
158-
emit(AuthenticationFailure(e.toString()));
156+
// On success, the _AuthenticationUserChanged listener will handle
157+
// emitting AuthenticationUnauthenticated.
158+
} on NetworkException catch (_) {
159+
emit(const AuthenticationFailure('Network error occurred.'));
160+
} on ServerException catch (e) {
161+
emit(AuthenticationFailure('Server error: ${e.message}'));
162+
} on OperationFailedException catch (e) {
163+
emit(AuthenticationFailure('Operation failed: ${e.message}'));
164+
} on HtHttpException catch (e) {
165+
// Catch any other HtHttpException subtypes
166+
emit(AuthenticationFailure('HTTP error: ${e.message}'));
159167
} catch (e) {
160-
emit(AuthenticationFailure(e.toString()));
168+
emit(AuthenticationFailure('An unexpected error occurred: $e'));
161169
}
162170
}
163171
}

lib/authentication/bloc/authentication_event.dart

Lines changed: 32 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,17 @@ sealed class AuthenticationEvent extends Equatable {
88
const AuthenticationEvent();
99

1010
@override
11-
List<Object> get props => [];
11+
List<Object?> get props => [];
1212
}
1313

14-
/// {@template authentication_send_sign_in_link_requested}
15-
/// Event triggered when the user requests a sign-in link to be sent
14+
/// {@template authentication_request_sign_in_code_requested}
15+
/// Event triggered when the user requests a sign-in code to be sent
1616
/// to their email.
1717
/// {@endtemplate}
18-
final class AuthenticationSendSignInLinkRequested extends AuthenticationEvent {
19-
/// {@macro authentication_send_sign_in_link_requested}
20-
const AuthenticationSendSignInLinkRequested({required this.email});
18+
final class AuthenticationRequestSignInCodeRequested
19+
extends AuthenticationEvent {
20+
/// {@macro authentication_request_sign_in_code_requested}
21+
const AuthenticationRequestSignInCodeRequested({required this.email});
2122

2223
/// The user's email address.
2324
final String email;
@@ -26,27 +27,24 @@ final class AuthenticationSendSignInLinkRequested extends AuthenticationEvent {
2627
List<Object> get props => [email];
2728
}
2829

29-
/// {@template authentication_sign_in_with_link_attempted}
30-
/// Event triggered when the app attempts to sign in using an email link.
31-
/// This is typically triggered by a deep link handler.
30+
/// {@template authentication_verify_code_requested}
31+
/// Event triggered when the user attempts to sign in using an email and code.
3232
/// {@endtemplate}
33-
final class AuthenticationSignInWithLinkAttempted extends AuthenticationEvent {
34-
/// {@macro authentication_sign_in_with_link_attempted}
35-
const AuthenticationSignInWithLinkAttempted({required this.emailLink});
33+
final class AuthenticationVerifyCodeRequested extends AuthenticationEvent {
34+
/// {@macro authentication_verify_code_requested}
35+
const AuthenticationVerifyCodeRequested({
36+
required this.email,
37+
required this.code,
38+
});
3639

37-
/// The sign-in link received by the app.
38-
final String emailLink;
40+
/// The user's email address.
41+
final String email;
3942

40-
@override
41-
List<Object> get props => [emailLink];
42-
}
43+
/// The verification code received by the user.
44+
final String code;
4345

44-
/// {@template authentication_google_sign_in_requested}
45-
/// Event triggered when the user requests to sign in with Google.
46-
/// {@endtemplate}
47-
final class AuthenticationGoogleSignInRequested extends AuthenticationEvent {
48-
/// {@macro authentication_google_sign_in_requested}
49-
const AuthenticationGoogleSignInRequested();
46+
@override
47+
List<Object> get props => [email, code];
5048
}
5149

5250
/// {@template authentication_anonymous_sign_in_requested}
@@ -65,10 +63,16 @@ final class AuthenticationSignOutRequested extends AuthenticationEvent {
6563
const AuthenticationSignOutRequested();
6664
}
6765

68-
/// {@template authentication_delete_account_requested}
69-
/// Event triggered when the user requests to delete their account.
66+
/// {@template _authentication_user_changed}
67+
/// Internal event triggered when the authentication state changes.
7068
/// {@endtemplate}
71-
final class AuthenticationDeleteAccountRequested extends AuthenticationEvent {
72-
/// {@macro authentication_delete_account_requested}
73-
const AuthenticationDeleteAccountRequested();
69+
final class _AuthenticationUserChanged extends AuthenticationEvent {
70+
/// {@macro _authentication_user_changed}
71+
const _AuthenticationUserChanged({required this.user});
72+
73+
/// The current authenticated user, or null if unauthenticated.
74+
final User? user;
75+
76+
@override
77+
List<Object?> get props => [user];
7478
}

lib/authentication/bloc/authentication_state.dart

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ final class AuthenticationLoading extends AuthenticationState {}
2626
/// {@endtemplate}
2727
final class AuthenticationAuthenticated extends AuthenticationState {
2828
/// {@macro authentication_authenticated}
29-
const AuthenticationAuthenticated(this.user);
29+
const AuthenticationAuthenticated({required this.user});
3030

3131
/// The authenticated [User] object.
3232
final User user;
@@ -40,15 +40,24 @@ final class AuthenticationAuthenticated extends AuthenticationState {
4040
/// {@endtemplate}
4141
final class AuthenticationUnauthenticated extends AuthenticationState {}
4242

43-
/// {@template authentication_link_sending}
44-
/// State indicating that the sign-in link is being sent.
43+
/// {@template authentication_request_code_loading}
44+
/// State indicating that the sign-in code is being requested.
4545
/// {@endtemplate}
46-
final class AuthenticationLinkSending extends AuthenticationState {}
46+
final class AuthenticationRequestCodeLoading extends AuthenticationState {}
4747

48-
/// {@template authentication_link_sent_success}
49-
/// State indicating that the sign-in link was sent successfully.
48+
/// {@template authentication_code_sent_success}
49+
/// State indicating that the sign-in code was sent successfully.
5050
/// {@endtemplate}
51-
final class AuthenticationLinkSentSuccess extends AuthenticationState {}
51+
final class AuthenticationCodeSentSuccess extends AuthenticationState {
52+
/// {@macro authentication_code_sent_success}
53+
const AuthenticationCodeSentSuccess({required this.email});
54+
55+
/// The email address the code was sent to.
56+
final String email;
57+
58+
@override
59+
List<Object> get props => [email];
60+
}
5261

5362
/// {@template authentication_failure}
5463
/// Represents an authentication failure.

0 commit comments

Comments
 (0)