1+ import 'dart:async' ;
2+
13import 'package:ht_api/src/rbac/permission_service.dart' ;
24import 'package:ht_api/src/rbac/permissions.dart' ;
35import 'package:ht_api/src/services/auth_token_service.dart' ;
@@ -116,29 +118,21 @@ class AuthService {
116118 }
117119 }
118120
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.
132121 /// Completes the email sign-in process by verifying the code.
133122 ///
134123 /// This method is context-aware and handles multiple scenarios:
135124 ///
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+ ///
140133 /// - **Dashboard Login:** If [isDashboardLogin] is true, it performs a
141134 /// strict login for an existing user with dashboard permissions.
135+ ///
142136 /// - **Standard Sign-In/Sign-Up:** If no authenticated user is present, it
143137 /// either logs in an existing user with the given [email] or creates a
144138 /// new `standardUser` .
@@ -168,21 +162,56 @@ class AuthService {
168162 );
169163 }
170164
171- // 2. Check for Guest-to-Permanent user conversion flow .
165+ // 2. Check if the sign-in is initiated from an authenticated guest session .
172166 if (authenticatedUser != null &&
173167 authenticatedUser.appRole == AppUserRole .guestUser) {
174168 _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 .' ,
180170 );
181- }
182171
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);
184174
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.
186215 User user;
187216 try {
188217 // Attempt to find user by email
@@ -507,29 +536,20 @@ class AuthService {
507536 }
508537 }
509538
510- /// Converts a guest user to a permanent standard user.
539+ /// Converts a guest user to a new permanent standard user.
511540 ///
512541 /// 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.
519544 Future <({User user, String token})> _convertGuestUserToPermanent ({
520545 required User guestUser,
521546 required String verifiedEmail,
522547 }) 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.
531551
532- // 2 . Update the guest user's details to make them permanent.
552+ // 1 . Update the guest user's details to make them permanent.
533553 final updatedUser = guestUser.copyWith (
534554 email: verifiedEmail,
535555 appRole: AppUserRole .standardUser,
@@ -543,7 +563,7 @@ class AuthService {
543563 'User ${permanentUser .id } successfully converted to permanent account with email $verifiedEmail .' ,
544564 );
545565
546- // 3 . Generate a new token for the now-permanent user.
566+ // 2 . Generate a new token for the now-permanent user.
547567 final newToken = await _authTokenService.generateToken (permanentUser);
548568 _log.info ('Generated new token for converted user ${permanentUser .id }' );
549569
0 commit comments