@@ -121,16 +121,29 @@ class AuthService {
121121
122122 /// Completes the email sign-in process by verifying the code.
123123 ///
124- /// If the code is valid, finds or creates the user, generates an auth token.
125- /// Returns the authenticated User and the generated token.
124+ /// This method is context-aware based on the [isDashboardLogin] flag.
125+ ///
126+ /// - If `isDashboardLogin` is `true` , it validates the code and logs in the
127+ /// existing user. It will not create a new user.
128+ /// - If `isDashboardLogin` is `false` (default), it validates the code and
129+ /// either logs in the existing user or creates a new one if they don't
130+ /// exist.
131+ ///
132+ /// As a special case for in-memory setup, if a new user signs up with the
133+ /// email 'admin@example.com', they will be granted the 'admin' role.
134+ ///
135+ /// Returns the authenticated [User] and a new authentication token.
136+ ///
126137 /// Throws [InvalidInputException] if the code is invalid or expired.
127- /// Throws [AuthenticationException] for specific code mismatch.
138+ /// Throws [UnauthorizedException] if `isDashboardLogin` is true and the user
139+ /// is not found (as a safeguard).
128140 /// Throws [OperationFailedException] for user lookup/creation or token errors.
129141 Future <({User user, String token})> completeEmailSignIn (
130142 String email,
131143 String code, {
132- User ? currentAuthUser, // Parameter for potential future linking logic
133- String ? clientType, // e.g., 'dashboard', 'mobile_app'
144+ // Flag to indicate if this is a login attempt from the dashboard,
145+ // which enforces stricter checks.
146+ bool isDashboardLogin = false ,
134147 }) async {
135148 // 1. Validate the code for standard sign-in
136149 final isValidCode = await _verificationCodeStorageService
@@ -151,146 +164,65 @@ class AuthService {
151164 );
152165 }
153166
154- // 2. Find or create the user, and migrate data if anonymous
167+ // 2. Find or create the user based on the context
155168 User user;
156169 try {
157- if (currentAuthUser != null &&
158- currentAuthUser.roles.contains (UserRoles .guestUser)) {
159- // This is an anonymous user linking their account.
160- // Migrate their existing data to the new permanent user.
161- print (
162- 'Anonymous user ${currentAuthUser .id } is linking email $email . '
163- 'Migrating data...' ,
164- );
170+ // Attempt to find user by email
171+ final query = {'email' : email};
172+ final paginatedResponse = await _userRepository.readAllByQuery (query);
165173
166- // Fetch existing settings and preferences for the anonymous user
167- UserAppSettings ? existingAppSettings;
168- UserContentPreferences ? existingUserPreferences;
169- try {
170- existingAppSettings = await _userAppSettingsRepository.read (
171- id: currentAuthUser.id,
172- userId: currentAuthUser.id,
173- );
174- existingUserPreferences = await _userContentPreferencesRepository
175- .read (id: currentAuthUser.id, userId: currentAuthUser.id);
176- print (
177- 'Fetched existing settings and preferences for anonymous user '
178- '${currentAuthUser .id }.' ,
179- );
180- } on NotFoundException {
181- print (
182- 'No existing settings/preferences found for anonymous user '
183- '${currentAuthUser .id }. Creating new ones.' ,
184- );
185- // If not found, proceed to create new ones later.
186- } catch (e) {
174+ if (paginatedResponse.items.isNotEmpty) {
175+ user = paginatedResponse.items.first;
176+ print ('Found existing user: ${user .id } for email $email ' );
177+ } else {
178+ // User not found.
179+ if (isDashboardLogin) {
180+ // This should not happen if the request-code flow is correct.
181+ // It's a safeguard.
187182 print (
188- 'Error fetching existing settings/preferences for anonymous user '
189- '${currentAuthUser .id }: $e ' ,
183+ 'Error: Dashboard login verification failed for non-existent user $email .' ,
190184 );
191- // Log and continue, new defaults will be created.
185+ throw const UnauthorizedException ( 'User account does not exist.' );
192186 }
193187
194- // Update the existing anonymous user to be permanent
195- user = currentAuthUser.copyWith (
188+ // Create a new user for the standard app flow.
189+ print ('User not found for $email , creating new user.' );
190+
191+ // Hardcoded admin email check for in-memory setup.
192+ const adminEmail = 'admin@example.com' ;
193+ final roles = (email == adminEmail)
194+ ? [UserRoles .standardUser, UserRoles .admin]
195+ : [UserRoles .standardUser];
196+
197+ user = User (
198+ id: _uuid.v4 (),
196199 email: email,
197- roles: [ UserRoles .standardUser] ,
200+ roles: roles ,
198201 );
199- user = await _userRepository.update (id: user.id, item: user);
200- print (
201- 'Updated anonymous user ${user .id } to permanent with email $email .' ,
202+ user = await _userRepository.create (item: user);
203+ print ('Created new user: ${user .id } with roles: ${user .roles }' );
204+
205+ // Create default UserAppSettings for the new user
206+ final defaultAppSettings = UserAppSettings (id: user.id);
207+ await _userAppSettingsRepository.create (
208+ item: defaultAppSettings,
209+ userId: user.id,
202210 );
211+ print ('Created default UserAppSettings for user: ${user .id }' );
203212
204- // Update or create UserAppSettings for the now-permanent user
205- if (existingAppSettings != null ) {
206- // Update existing settings with the new user ID (though it's the same)
207- // and persist.
208- await _userAppSettingsRepository.update (
209- id: existingAppSettings.id,
210- item: existingAppSettings.copyWith (id: user.id),
211- userId: user.id,
212- );
213- print ('Migrated UserAppSettings for user: ${user .id }' );
214- } else {
215- // Create default settings if none existed for the anonymous user
216- final defaultAppSettings = UserAppSettings (id: user.id);
217- await _userAppSettingsRepository.create (
218- item: defaultAppSettings,
219- userId: user.id,
220- );
221- print ('Created default UserAppSettings for user: ${user .id }' );
222- }
223-
224- // Update or create UserContentPreferences for the now-permanent user
225- if (existingUserPreferences != null ) {
226- // Update existing preferences with the new user ID (though it's the same)
227- // and persist.
228- await _userContentPreferencesRepository.update (
229- id: existingUserPreferences.id,
230- item: existingUserPreferences.copyWith (id: user.id),
231- userId: user.id,
232- );
233- print ('Migrated UserContentPreferences for user: ${user .id }' );
234- } else {
235- // Create default preferences if none existed for the anonymous user
236- final defaultUserPreferences = UserContentPreferences (id: user.id);
237- await _userContentPreferencesRepository.create (
238- item: defaultUserPreferences,
239- userId: user.id,
240- );
241- print ('Created default UserContentPreferences for user: ${user .id }' );
242- }
243- } else {
244- // Standard sign-in/sign-up flow (not anonymous linking)
245- // Attempt to find user by email
246- final query = {'email' : email};
247- final paginatedResponse = await _userRepository.readAllByQuery (query);
248-
249- if (paginatedResponse.items.isNotEmpty) {
250- user = paginatedResponse.items.first;
251- print ('Found existing user: ${user .id } for email $email ' );
252- } else {
253- // User not found, create a new one
254- print ('User not found for $email , creating new user.' );
255- // Assign roles based on client type. New users from the dashboard
256- // could be granted publisher rights, for example.
257- final roles = (clientType == 'dashboard' )
258- ? [UserRoles .standardUser, UserRoles .publisher]
259- : [UserRoles .standardUser];
260- user = User (
261- id: _uuid.v4 (), // Generate new ID
262- email: email,
263- roles: roles,
264- );
265- user = await _userRepository.create (item: user); // Save the new user
266- print ('Created new user: ${user .id }' );
267-
268- // Create default UserAppSettings for the new user
269- final defaultAppSettings = UserAppSettings (id: user.id);
270- await _userAppSettingsRepository.create (
271- item: defaultAppSettings,
272- userId: user.id, // Pass user ID for scoping
273- );
274- print ('Created default UserAppSettings for user: ${user .id }' );
275-
276- // Create default UserContentPreferences for the new user
277- final defaultUserPreferences = UserContentPreferences (id: user.id);
278- await _userContentPreferencesRepository.create (
279- item: defaultUserPreferences,
280- userId: user.id, // Pass user ID for scoping
281- );
282- print ('Created default UserContentPreferences for user: ${user .id }' );
283- }
213+ // Create default UserContentPreferences for the new user
214+ final defaultUserPreferences = UserContentPreferences (id: user.id);
215+ await _userContentPreferencesRepository.create (
216+ item: defaultUserPreferences,
217+ userId: user.id,
218+ );
219+ print ('Created default UserContentPreferences for user: ${user .id }' );
284220 }
285221 } on HtHttpException catch (e) {
286- print ('Error finding/creating/migrating user for $email : $e ' );
287- throw const OperationFailedException (
288- 'Failed to find, create, or migrate user account.' ,
289- );
222+ print ('Error finding/creating user for $email : $e ' );
223+ throw const OperationFailedException ('Failed to find or create user account.' );
290224 } catch (e) {
291- print (
292- 'Unexpected error during user lookup/creation/migration for $email : $e ' ,
293- );
225+ print ('Unexpected error during user lookup/creation for $email : $e ' );
294226 throw const OperationFailedException ('Failed to process user account.' );
295227 }
296228
@@ -304,7 +236,7 @@ class AuthService {
304236 throw const OperationFailedException (
305237 'Failed to generate authentication token.' ,
306238 );
307- }
239+ }
308240 }
309241
310242 /// Performs anonymous sign-in.
0 commit comments