1
+ import 'dart:async' ;
2
+
1
3
import 'package:ht_api/src/rbac/permission_service.dart' ;
2
4
import 'package:ht_api/src/rbac/permissions.dart' ;
3
5
import 'package:ht_api/src/services/auth_token_service.dart' ;
@@ -116,29 +118,21 @@ class AuthService {
116
118
}
117
119
}
118
120
119
- /// Completes the email sign-in process by verifying the code.
120
- ///
121
- /// This method is context-aware based on the [isDashboardLogin] flag.
122
- ///
123
- /// - For the dashboard (`isDashboardLogin: true` ), it validates the code and
124
- /// logs in the existing user. It will not create a new user in this flow.
125
- /// - For the user-facing app (`isDashboardLogin: false` ), it validates the
126
- /// code and either logs in the existing user or creates a new one with a
127
- /// 'standardUser' role if they don't exist.
128
- ///
129
- /// Returns the authenticated [User] and a new authentication token.
130
- ///
131
- /// Throws [InvalidInputException] if the code is invalid or expired.
132
121
/// Completes the email sign-in process by verifying the code.
133
122
///
134
123
/// This method is context-aware and handles multiple scenarios:
135
124
///
136
- /// - **Guest to Permanent Conversion:** If an authenticated `guestUser`
137
- /// (from [authenticatedUser] ) performs this action, their account is
138
- /// upgraded to a permanent `standardUser` with the verified [email] .
139
- /// Their existing data is preserved.
125
+ /// - **Guest Sign-In:** If an authenticated `guestUser` (from
126
+ /// [authenticatedUser] ) performs this action, the service checks if a
127
+ /// permanent account with the verified [email] already exists.
128
+ /// - If it exists, the user is signed into that account, and the temporary
129
+ /// guest account is deleted.
130
+ /// - If it does not exist, the guest account is converted into a new
131
+ /// permanent `standardUser` with the verified [email].
132
+ ///
140
133
/// - **Dashboard Login:** If [isDashboardLogin] is true, it performs a
141
134
/// strict login for an existing user with dashboard permissions.
135
+ ///
142
136
/// - **Standard Sign-In/Sign-Up:** If no authenticated user is present, it
143
137
/// either logs in an existing user with the given [email] or creates a
144
138
/// new `standardUser` .
@@ -168,21 +162,56 @@ class AuthService {
168
162
);
169
163
}
170
164
171
- // 2. Check for Guest-to-Permanent user conversion flow .
165
+ // 2. Check if the sign-in is initiated from an authenticated guest session .
172
166
if (authenticatedUser != null &&
173
167
authenticatedUser.appRole == AppUserRole .guestUser) {
174
168
_log.info (
175
- 'Starting account conversion for guest user ${authenticatedUser .id } to email $email .' ,
176
- );
177
- return _convertGuestUserToPermanent (
178
- guestUser: authenticatedUser,
179
- verifiedEmail: email,
169
+ 'Guest user ${authenticatedUser .id } is attempting to sign in with email $email .' ,
180
170
);
181
- }
182
171
183
- // 3. If not a conversion, proceed with standard or dashboard login.
172
+ // Check if an account with the target email already exists.
173
+ final existingUser = await _findUserByEmail (email);
184
174
185
- // Find or create the user based on the context.
175
+ if (existingUser != null ) {
176
+ // --- Scenario A: Sign-in to an existing account ---
177
+ // The user wants to log into their existing account, abandoning the
178
+ // guest session.
179
+ _log.info (
180
+ 'Existing account found for email $email (ID: ${existingUser .id }). '
181
+ 'Signing in and abandoning guest session ${authenticatedUser .id }.' ,
182
+ );
183
+
184
+ // Delete the now-orphaned anonymous user account and its data.
185
+ // This is a fire-and-forget operation; we don't want to block the
186
+ // login if cleanup fails, but we should log any errors.
187
+ unawaited (
188
+ deleteAccount (userId: authenticatedUser.id).catchError ((e, s) {
189
+ _log.severe (
190
+ 'Failed to clean up orphaned anonymous user ${authenticatedUser .id } after sign-in.' ,
191
+ e,
192
+ s is StackTrace ? s : null ,
193
+ );
194
+ }),
195
+ );
196
+
197
+ // Generate a new token for the existing permanent user.
198
+ final token = await _authTokenService.generateToken (existingUser);
199
+ _log.info ('Generated new token for existing user ${existingUser .id }.' );
200
+ return (user: existingUser, token: token);
201
+ } else {
202
+ // --- Scenario B: Convert guest to a new permanent account ---
203
+ // No account exists with this email, so proceed with conversion.
204
+ _log.info (
205
+ 'No existing account for $email . Converting guest user ${authenticatedUser .id } to a new permanent account.' ,
206
+ );
207
+ return _convertGuestUserToPermanent (
208
+ guestUser: authenticatedUser,
209
+ verifiedEmail: email,
210
+ );
211
+ }
212
+ }
213
+
214
+ // 3. If not a guest flow, proceed with standard or dashboard login.
186
215
User user;
187
216
try {
188
217
// Attempt to find user by email
@@ -507,29 +536,20 @@ class AuthService {
507
536
}
508
537
}
509
538
510
- /// Converts a guest user to a permanent standard user.
539
+ /// Converts a guest user to a new permanent standard user.
511
540
///
512
541
/// This helper method encapsulates the logic for updating the user's
513
- /// record with a verified email, upgrading their role, and generating a new
514
- /// authentication token. It ensures that all associated user data is
515
- /// preserved during the conversion.
516
- ///
517
- /// Throws [ConflictException] if the target email is already in use by
518
- /// another permanent account.
542
+ /// record with a verified email and upgrading their role. It assumes that
543
+ /// the target email is not already in use by another account.
519
544
Future <({User user, String token})> _convertGuestUserToPermanent ({
520
545
required User guestUser,
521
546
required String verifiedEmail,
522
547
}) async {
523
- // 1. Check if the target email is already in use by another permanent user.
524
- final existingUser = await _findUserByEmail (verifiedEmail);
525
- if (existingUser != null && existingUser.id != guestUser.id) {
526
- // If a different user already exists with this email, throw an error.
527
- throw ConflictException (
528
- 'This email address is already associated with another account.' ,
529
- );
530
- }
548
+ // The check for an existing user with the verifiedEmail is now handled
549
+ // by the calling method, `completeEmailSignIn`. This method now only
550
+ // handles the conversion itself.
531
551
532
- // 2 . Update the guest user's details to make them permanent.
552
+ // 1 . Update the guest user's details to make them permanent.
533
553
final updatedUser = guestUser.copyWith (
534
554
email: verifiedEmail,
535
555
appRole: AppUserRole .standardUser,
@@ -543,7 +563,7 @@ class AuthService {
543
563
'User ${permanentUser .id } successfully converted to permanent account with email $verifiedEmail .' ,
544
564
);
545
565
546
- // 3 . Generate a new token for the now-permanent user.
566
+ // 2 . Generate a new token for the now-permanent user.
547
567
final newToken = await _authTokenService.generateToken (permanentUser);
548
568
_log.info ('Generated new token for converted user ${permanentUser .id }' );
549
569
0 commit comments