From cca71a25f2685e88df053b48c7d2c588ebf01779 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 5 Jul 2025 09:12:30 +0100 Subject: [PATCH 01/10] feat(auth): make code request context-aware for dashboard login Implements context-aware logic in the `AuthService.initiateEmailSignIn` method. - The method now accepts an `isDashboardLogin` boolean flag. - If `isDashboardLogin` is true, the service first validates that a user with the provided email exists and has either the 'admin' or 'publisher' role. - An `UnauthorizedException` is thrown if the user does not exist. - A `ForbiddenException` is thrown if the user exists but lacks the required roles. - A verification code is only sent if these checks pass. - If `isDashboardLogin` is false, the original behavior of sending a code without pre-validation is maintained for the user-facing app's sign-in/sign-up flow. This change enforces security at the first step of the dashboard login process, preventing code generation for unauthorized users. --- lib/src/services/auth_service.dart | 58 ++++++++++++++++++++++++++++-- 1 file changed, 56 insertions(+), 2 deletions(-) diff --git a/lib/src/services/auth_service.dart b/lib/src/services/auth_service.dart index 3706214..040a0fb 100644 --- a/lib/src/services/auth_service.dart +++ b/lib/src/services/auth_service.dart @@ -41,11 +41,65 @@ class AuthService { /// Initiates the email sign-in process. /// - /// Generates a verification code, stores it, and sends it via email. + /// For standard sign-in (user-facing app), it generates a verification code, + /// stores it, and sends it via email without checking for user existence. + /// + /// For dashboard login (`isDashboardLogin: true`), it first verifies that a + /// user with the given [email] exists and has either the 'admin' or + /// 'publisher' role before sending the code. + /// + /// - [email]: The email address to send the code to. + /// - [isDashboardLogin]: A flag to indicate if this is a login attempt from + /// the dashboard, which enforces stricter checks. + /// /// Throws [InvalidInputException] for invalid email format (via email client). + /// Throws [UnauthorizedException] if `isDashboardLogin` is true and the user + /// does not exist. + /// Throws [ForbiddenException] if `isDashboardLogin` is true and the user + /// exists but lacks the required roles. /// Throws [OperationFailedException] if code generation/storage/email fails. - Future initiateEmailSignIn(String email) async { + Future initiateEmailSignIn( + String email, { + bool isDashboardLogin = false, + }) async { try { + // For dashboard login, first validate the user exists and has permissions. + if (isDashboardLogin) { + print('Dashboard login initiated for $email. Verifying user...'); + User? user; + try { + final query = {'email': email}; + final response = await _userRepository.readAllByQuery(query); + if (response.items.isNotEmpty) { + user = response.items.first; + } + } on HtHttpException catch (e) { + print('Repository error while verifying dashboard user $email: $e'); + rethrow; + } + + if (user == null) { + print('Dashboard login failed: User $email not found.'); + throw const UnauthorizedException( + 'This email address is not registered for dashboard access.', + ); + } + + final hasRequiredRole = + user.roles.contains(UserRoles.admin) || + user.roles.contains(UserRoles.publisher); + + if (!hasRequiredRole) { + print( + 'Dashboard login failed: User ${user.id} lacks required roles.', + ); + throw const ForbiddenException( + 'Your account does not have the required permissions to sign in.', + ); + } + print('Dashboard user ${user.id} verified successfully.'); + } + // Generate and store the code for standard sign-in final code = await _verificationCodeStorageService .generateAndStoreSignInCode(email); From 0a93674d7d3ab8458128f9db75213bc3ab3d740d Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 5 Jul 2025 09:13:51 +0100 Subject: [PATCH 02/10] feat(auth): update request-code handler to be context-aware Modifies the `/api/v1/auth/request-code` route handler to parse an optional `is_dashboard_login` boolean field from the request body. This flag is then passed to the `AuthService.initiateEmailSignIn` method, enabling it to trigger the appropriate validation logic for either a standard user-facing app sign-in or a restricted dashboard login. --- routes/api/v1/auth/request-code.dart | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/routes/api/v1/auth/request-code.dart b/routes/api/v1/auth/request-code.dart index 4c62e45..5feabb1 100644 --- a/routes/api/v1/auth/request-code.dart +++ b/routes/api/v1/auth/request-code.dart @@ -7,7 +7,11 @@ import 'package:ht_shared/ht_shared.dart'; // For exceptions /// Handles POST requests to `/api/v1/auth/request-code`. /// /// Initiates the email sign-in process by requesting a verification code -/// be sent to the provided email address. +/// be sent to the provided email address. It supports a context-aware flow +/// for dashboard logins by checking for an `is_dashboard_login` flag in the +/// request body. If this flag is true, the `AuthService` will perform +/// additional checks to ensure the user exists and has the required +/// permissions before sending a code. Future onRequest(RequestContext context) async { // Ensure this is a POST request if (context.request.method != HttpMethod.post) { @@ -37,6 +41,9 @@ Future onRequest(RequestContext context) async { ); } + // Check for the optional dashboard login flag. Default to false if not present. + final isDashboardLogin = (body['is_dashboard_login'] as bool?) ?? false; + // Basic email format check (more robust validation can be added) // Using a slightly more common regex pattern final emailRegex = RegExp( @@ -48,8 +55,11 @@ Future onRequest(RequestContext context) async { } try { - // Call the AuthService to handle the logic - await authService.initiateEmailSignIn(email); + // Call the AuthService to handle the logic, passing the context flag. + await authService.initiateEmailSignIn( + email, + isDashboardLogin: isDashboardLogin, + ); // Return 202 Accepted: The request is accepted for processing, // but the processing (email sending) hasn't necessarily completed. From 305388ebeb7d96d948fa8524b21980b462462dc4 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 5 Jul 2025 09:16:38 +0100 Subject: [PATCH 03/10] refactor(auth): make code verification context-aware Refactors the `AuthService.completeEmailSignIn` method to be context-aware based on an `isDashboardLogin` flag. - The method now accepts `isDashboardLogin` instead of `clientType` and `currentAuthUser`. - If `isDashboardLogin` is true, the method only attempts to find and log in an existing user. It will not create a new account. - If `isDashboardLogin` is false, it performs the standard sign-in/sign-up flow for the user-facing app. - A special case is added to grant 'admin' role to a new user signing up with 'admin@example.com' for easier in-memory setup. - The redundant logic for anonymous user account linking has been removed, as it is correctly handled by `completeLinkEmailProcess`. --- lib/src/services/auth_service.dart | 196 ++++++++++------------------- 1 file changed, 64 insertions(+), 132 deletions(-) diff --git a/lib/src/services/auth_service.dart b/lib/src/services/auth_service.dart index 040a0fb..27e1896 100644 --- a/lib/src/services/auth_service.dart +++ b/lib/src/services/auth_service.dart @@ -121,16 +121,29 @@ class AuthService { /// Completes the email sign-in process by verifying the code. /// - /// If the code is valid, finds or creates the user, generates an auth token. - /// Returns the authenticated User and the generated token. + /// This method is context-aware based on the [isDashboardLogin] flag. + /// + /// - If `isDashboardLogin` is `true`, it validates the code and logs in the + /// existing user. It will not create a new user. + /// - If `isDashboardLogin` is `false` (default), it validates the code and + /// either logs in the existing user or creates a new one if they don't + /// exist. + /// + /// As a special case for in-memory setup, if a new user signs up with the + /// email 'admin@example.com', they will be granted the 'admin' role. + /// + /// Returns the authenticated [User] and a new authentication token. + /// /// Throws [InvalidInputException] if the code is invalid or expired. - /// Throws [AuthenticationException] for specific code mismatch. + /// Throws [UnauthorizedException] if `isDashboardLogin` is true and the user + /// is not found (as a safeguard). /// Throws [OperationFailedException] for user lookup/creation or token errors. Future<({User user, String token})> completeEmailSignIn( String email, String code, { - User? currentAuthUser, // Parameter for potential future linking logic - String? clientType, // e.g., 'dashboard', 'mobile_app' + // Flag to indicate if this is a login attempt from the dashboard, + // which enforces stricter checks. + bool isDashboardLogin = false, }) async { // 1. Validate the code for standard sign-in final isValidCode = await _verificationCodeStorageService @@ -151,146 +164,65 @@ class AuthService { ); } - // 2. Find or create the user, and migrate data if anonymous + // 2. Find or create the user based on the context User user; try { - if (currentAuthUser != null && - currentAuthUser.roles.contains(UserRoles.guestUser)) { - // This is an anonymous user linking their account. - // Migrate their existing data to the new permanent user. - print( - 'Anonymous user ${currentAuthUser.id} is linking email $email. ' - 'Migrating data...', - ); + // Attempt to find user by email + final query = {'email': email}; + final paginatedResponse = await _userRepository.readAllByQuery(query); - // Fetch existing settings and preferences for the anonymous user - UserAppSettings? existingAppSettings; - UserContentPreferences? existingUserPreferences; - try { - existingAppSettings = await _userAppSettingsRepository.read( - id: currentAuthUser.id, - userId: currentAuthUser.id, - ); - existingUserPreferences = await _userContentPreferencesRepository - .read(id: currentAuthUser.id, userId: currentAuthUser.id); - print( - 'Fetched existing settings and preferences for anonymous user ' - '${currentAuthUser.id}.', - ); - } on NotFoundException { - print( - 'No existing settings/preferences found for anonymous user ' - '${currentAuthUser.id}. Creating new ones.', - ); - // If not found, proceed to create new ones later. - } catch (e) { + if (paginatedResponse.items.isNotEmpty) { + user = paginatedResponse.items.first; + print('Found existing user: ${user.id} for email $email'); + } else { + // User not found. + if (isDashboardLogin) { + // This should not happen if the request-code flow is correct. + // It's a safeguard. print( - 'Error fetching existing settings/preferences for anonymous user ' - '${currentAuthUser.id}: $e', + 'Error: Dashboard login verification failed for non-existent user $email.', ); - // Log and continue, new defaults will be created. + throw const UnauthorizedException('User account does not exist.'); } - // Update the existing anonymous user to be permanent - user = currentAuthUser.copyWith( + // Create a new user for the standard app flow. + print('User not found for $email, creating new user.'); + + // Hardcoded admin email check for in-memory setup. + const adminEmail = 'admin@example.com'; + final roles = (email == adminEmail) + ? [UserRoles.standardUser, UserRoles.admin] + : [UserRoles.standardUser]; + + user = User( + id: _uuid.v4(), email: email, - roles: [UserRoles.standardUser], + roles: roles, ); - user = await _userRepository.update(id: user.id, item: user); - print( - 'Updated anonymous user ${user.id} to permanent with email $email.', + user = await _userRepository.create(item: user); + print('Created new user: ${user.id} with roles: ${user.roles}'); + + // Create default UserAppSettings for the new user + final defaultAppSettings = UserAppSettings(id: user.id); + await _userAppSettingsRepository.create( + item: defaultAppSettings, + userId: user.id, ); + print('Created default UserAppSettings for user: ${user.id}'); - // Update or create UserAppSettings for the now-permanent user - if (existingAppSettings != null) { - // Update existing settings with the new user ID (though it's the same) - // and persist. - await _userAppSettingsRepository.update( - id: existingAppSettings.id, - item: existingAppSettings.copyWith(id: user.id), - userId: user.id, - ); - print('Migrated UserAppSettings for user: ${user.id}'); - } else { - // Create default settings if none existed for the anonymous user - final defaultAppSettings = UserAppSettings(id: user.id); - await _userAppSettingsRepository.create( - item: defaultAppSettings, - userId: user.id, - ); - print('Created default UserAppSettings for user: ${user.id}'); - } - - // Update or create UserContentPreferences for the now-permanent user - if (existingUserPreferences != null) { - // Update existing preferences with the new user ID (though it's the same) - // and persist. - await _userContentPreferencesRepository.update( - id: existingUserPreferences.id, - item: existingUserPreferences.copyWith(id: user.id), - userId: user.id, - ); - print('Migrated UserContentPreferences for user: ${user.id}'); - } else { - // Create default preferences if none existed for the anonymous user - final defaultUserPreferences = UserContentPreferences(id: user.id); - await _userContentPreferencesRepository.create( - item: defaultUserPreferences, - userId: user.id, - ); - print('Created default UserContentPreferences for user: ${user.id}'); - } - } else { - // Standard sign-in/sign-up flow (not anonymous linking) - // Attempt to find user by email - final query = {'email': email}; - final paginatedResponse = await _userRepository.readAllByQuery(query); - - if (paginatedResponse.items.isNotEmpty) { - user = paginatedResponse.items.first; - print('Found existing user: ${user.id} for email $email'); - } else { - // User not found, create a new one - print('User not found for $email, creating new user.'); - // Assign roles based on client type. New users from the dashboard - // could be granted publisher rights, for example. - final roles = (clientType == 'dashboard') - ? [UserRoles.standardUser, UserRoles.publisher] - : [UserRoles.standardUser]; - user = User( - id: _uuid.v4(), // Generate new ID - email: email, - roles: roles, - ); - user = await _userRepository.create(item: user); // Save the new user - print('Created new user: ${user.id}'); - - // Create default UserAppSettings for the new user - final defaultAppSettings = UserAppSettings(id: user.id); - await _userAppSettingsRepository.create( - item: defaultAppSettings, - userId: user.id, // Pass user ID for scoping - ); - print('Created default UserAppSettings for user: ${user.id}'); - - // Create default UserContentPreferences for the new user - final defaultUserPreferences = UserContentPreferences(id: user.id); - await _userContentPreferencesRepository.create( - item: defaultUserPreferences, - userId: user.id, // Pass user ID for scoping - ); - print('Created default UserContentPreferences for user: ${user.id}'); - } + // Create default UserContentPreferences for the new user + final defaultUserPreferences = UserContentPreferences(id: user.id); + await _userContentPreferencesRepository.create( + item: defaultUserPreferences, + userId: user.id, + ); + print('Created default UserContentPreferences for user: ${user.id}'); } } on HtHttpException catch (e) { - print('Error finding/creating/migrating user for $email: $e'); - throw const OperationFailedException( - 'Failed to find, create, or migrate user account.', - ); + print('Error finding/creating user for $email: $e'); + throw const OperationFailedException('Failed to find or create user account.'); } catch (e) { - print( - 'Unexpected error during user lookup/creation/migration for $email: $e', - ); + print('Unexpected error during user lookup/creation for $email: $e'); throw const OperationFailedException('Failed to process user account.'); } @@ -304,7 +236,7 @@ class AuthService { throw const OperationFailedException( 'Failed to generate authentication token.', ); - } + } } /// Performs anonymous sign-in. From 183c82864b8cf2e1ad3913b9c5d6622d687d14b2 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 5 Jul 2025 09:19:04 +0100 Subject: [PATCH 04/10] feat(auth): update verify-code handler to be context-aware Modifies the `/api/v1/auth/verify-code` route handler to parse an optional `is_dashboard_login` boolean field from the request body. This flag is passed to the `AuthService.completeEmailSignIn` method, enabling it to trigger the appropriate verification logic for either a standard user-facing app sign-in/sign-up or a restricted dashboard login. The handler is also simplified by removing the now-redundant logic for passing the `authenticatedUser` to the service. --- routes/api/v1/auth/verify-code.dart | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/routes/api/v1/auth/verify-code.dart b/routes/api/v1/auth/verify-code.dart index 4966899..a1fb386 100644 --- a/routes/api/v1/auth/verify-code.dart +++ b/routes/api/v1/auth/verify-code.dart @@ -8,20 +8,21 @@ import 'package:ht_shared/ht_shared.dart'; /// Handles POST requests to `/api/v1/auth/verify-code`. /// /// Verifies the provided email and code, completes the sign-in/sign-up, -/// and returns the authenticated User object along with an auth token. +/// and returns the authenticated User object along with an auth token. It +/// supports a context-aware flow by checking for an `is_dashboard_login` +/// flag in the request body, which dictates whether to perform a strict +/// login-only check or a standard sign-in/sign-up. Future onRequest(RequestContext context) async { // Ensure this is a POST request if (context.request.method != HttpMethod.post) { return Response(statusCode: HttpStatus.methodNotAllowed); } - // Read the AuthService provided by middleware + // Read the AuthService provided by middleware. + // The `authenticatedUser` is no longer needed here as the service handles + // all context internally. final authService = context.read(); - // Read the authenticated User from context (provided by authentication middleware) - // This user might be null (if not authenticated) or an anonymous user. - final authenticatedUser = context.read(); - // Parse the request body final dynamic body; try { @@ -65,13 +66,16 @@ Future onRequest(RequestContext context) async { ); } + // Check for the optional dashboard login flag. Default to false. + final isDashboardLogin = (body['is_dashboard_login'] as bool?) ?? false; + try { // Call the AuthService to handle the verification and sign-in logic - // Pass the current authenticated user for potential data migration. + // Pass the context flag to determine the correct flow. final result = await authService.completeEmailSignIn( email, code, - currentAuthUser: authenticatedUser, + isDashboardLogin: isDashboardLogin, ); // Create the specific payload containing user and token From 85bc8e94c1a88d766e1ef679e64ff5913b696eea Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 5 Jul 2025 09:25:27 +0100 Subject: [PATCH 05/10] fix(auth): remove insecure hardcoded admin creation from service Removes the flawed logic that granted admin privileges to a user signing up with a specific hardcoded email address. This was a security risk as the service logic runs in all environments. All new users created via the public API will now correctly and safely be assigned only the 'standardUser' role. Privileged users like administrators must be provisioned out-of-band (e.g., via data fixtures), which is the correct and secure approach. --- lib/src/services/auth_service.dart | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/lib/src/services/auth_service.dart b/lib/src/services/auth_service.dart index 27e1896..abb1a3a 100644 --- a/lib/src/services/auth_service.dart +++ b/lib/src/services/auth_service.dart @@ -128,9 +128,7 @@ class AuthService { /// - If `isDashboardLogin` is `false` (default), it validates the code and /// either logs in the existing user or creates a new one if they don't /// exist. - /// - /// As a special case for in-memory setup, if a new user signs up with the - /// email 'admin@example.com', they will be granted the 'admin' role. + /// New users are created with the 'standardUser' role. /// /// Returns the authenticated [User] and a new authentication token. /// @@ -188,11 +186,9 @@ class AuthService { // Create a new user for the standard app flow. print('User not found for $email, creating new user.'); - // Hardcoded admin email check for in-memory setup. - const adminEmail = 'admin@example.com'; - final roles = (email == adminEmail) - ? [UserRoles.standardUser, UserRoles.admin] - : [UserRoles.standardUser]; + // All new users created via the public API get the standard role. + // Admin users must be provisioned out-of-band (e.g., via databse seed). + final roles = [UserRoles.standardUser]; user = User( id: _uuid.v4(), From 8d99f3ff2c4dc2bdedf9661865821ed6a3a7e86e Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 5 Jul 2025 09:26:58 +0100 Subject: [PATCH 06/10] feat(fixtures): add initial admin user fixture Creates a new data fixture file to seed an initial administrator user with the email 'admin@example.com'. This user is loaded into the in-memory repository on startup, providing a pre-existing privileged account for development and testing of the dashboard login flow. This is the correct, secure way to provision initial admin users, removing the need for insecure logic in the public API services. --- lib/src/fixtures/user_fixtures.dart | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 lib/src/fixtures/user_fixtures.dart diff --git a/lib/src/fixtures/user_fixtures.dart b/lib/src/fixtures/user_fixtures.dart new file mode 100644 index 0000000..ac851db --- /dev/null +++ b/lib/src/fixtures/user_fixtures.dart @@ -0,0 +1,18 @@ +import 'package:ht_shared/ht_shared.dart'; + +/// A list of initial user data to be loaded into the in-memory user repository. +/// +/// This list includes a pre-configured administrator user, which is essential +/// for accessing the dashboard in a development environment. +final List userFixtures = [ + // The initial administrator user. + User( + id: 'admin-user-id', // A fixed, predictable ID for the admin. + email: 'admin@example.com', + roles: const [UserRoles.standardUser, UserRoles.admin], + createdAt: DateTime.now().toUtc(), + ), + // Add other initial users for testing if needed. + // Example: A standard user + // User( ... ), +]; From 5b3125e633c93c6a2d5c9e0803fa80a7882145c4 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 5 Jul 2025 09:29:21 +0100 Subject: [PATCH 07/10] docs(auth): update service method comments for context-aware flow Updates the documentation comments for `initiateEmailSignIn` and `completeEmailSignIn` in the `AuthService`. The new comments clearly explain the context-aware behavior based on the `isDashboardLogin` flag, detailing the different validation logic for the user-facing app versus the dashboard login. This improves clarity for future development. --- lib/src/services/auth_service.dart | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/lib/src/services/auth_service.dart b/lib/src/services/auth_service.dart index abb1a3a..3af933a 100644 --- a/lib/src/services/auth_service.dart +++ b/lib/src/services/auth_service.dart @@ -41,23 +41,23 @@ class AuthService { /// Initiates the email sign-in process. /// - /// For standard sign-in (user-facing app), it generates a verification code, - /// stores it, and sends it via email without checking for user existence. + /// This method is context-aware based on the [isDashboardLogin] flag. /// - /// For dashboard login (`isDashboardLogin: true`), it first verifies that a - /// user with the given [email] exists and has either the 'admin' or - /// 'publisher' role before sending the code. + /// - For the user-facing app (`isDashboardLogin: false`), it generates and + /// sends a verification code to the given [email] without pre-validation, + /// supporting a unified sign-in/sign-up flow. + /// - For the dashboard (`isDashboardLogin: true`), it performs a strict + /// login-only check. It verifies that a user with the given [email] exists + /// and has either the 'admin' or 'publisher' role *before* sending a code. /// /// - [email]: The email address to send the code to. /// - [isDashboardLogin]: A flag to indicate if this is a login attempt from /// the dashboard, which enforces stricter checks. /// - /// Throws [InvalidInputException] for invalid email format (via email client). /// Throws [UnauthorizedException] if `isDashboardLogin` is true and the user /// does not exist. /// Throws [ForbiddenException] if `isDashboardLogin` is true and the user /// exists but lacks the required roles. - /// Throws [OperationFailedException] if code generation/storage/email fails. Future initiateEmailSignIn( String email, { bool isDashboardLogin = false, @@ -123,19 +123,15 @@ class AuthService { /// /// This method is context-aware based on the [isDashboardLogin] flag. /// - /// - If `isDashboardLogin` is `true`, it validates the code and logs in the - /// existing user. It will not create a new user. - /// - If `isDashboardLogin` is `false` (default), it validates the code and - /// either logs in the existing user or creates a new one if they don't - /// exist. - /// New users are created with the 'standardUser' role. + /// - For the dashboard (`isDashboardLogin: true`), it validates the code and + /// logs in the existing user. It will not create a new user in this flow. + /// - For the user-facing app (`isDashboardLogin: false`), it validates the + /// code and either logs in the existing user or creates a new one with a + /// 'standardUser' role if they don't exist. /// /// Returns the authenticated [User] and a new authentication token. /// /// Throws [InvalidInputException] if the code is invalid or expired. - /// Throws [UnauthorizedException] if `isDashboardLogin` is true and the user - /// is not found (as a safeguard). - /// Throws [OperationFailedException] for user lookup/creation or token errors. Future<({User user, String token})> completeEmailSignIn( String email, String code, { @@ -187,7 +183,7 @@ class AuthService { print('User not found for $email, creating new user.'); // All new users created via the public API get the standard role. - // Admin users must be provisioned out-of-band (e.g., via databse seed). + // Admin users must be provisioned out-of-band (e.g., via fixtures). final roles = [UserRoles.standardUser]; user = User( From 0208ee471cac084eabbc566fbdde041f991f793a Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 5 Jul 2025 09:30:51 +0100 Subject: [PATCH 08/10] docs(auth): update route handler comments for context-aware flow Updates the documentation comments for the `/request-code` and `/verify-code` route handlers. The new comments clearly explain the context-aware behavior driven by the `is_dashboard_login` request body flag, detailing the different logic for the user-facing app versus the dashboard. --- routes/api/v1/auth/request-code.dart | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/routes/api/v1/auth/request-code.dart b/routes/api/v1/auth/request-code.dart index 5feabb1..aa3bd69 100644 --- a/routes/api/v1/auth/request-code.dart +++ b/routes/api/v1/auth/request-code.dart @@ -6,12 +6,14 @@ import 'package:ht_shared/ht_shared.dart'; // For exceptions /// Handles POST requests to `/api/v1/auth/request-code`. /// -/// Initiates the email sign-in process by requesting a verification code -/// be sent to the provided email address. It supports a context-aware flow -/// for dashboard logins by checking for an `is_dashboard_login` flag in the -/// request body. If this flag is true, the `AuthService` will perform -/// additional checks to ensure the user exists and has the required -/// permissions before sending a code. +/// Initiates an email-based sign-in process. This endpoint is context-aware. +/// +/// - For the user-facing app, it sends a verification code to the provided +/// email, supporting both sign-in and sign-up. +/// - For the dashboard, the request body must include `"is_dashboard_login": true`. +/// In this mode, it first verifies the user exists and has 'admin' or +/// 'publisher' roles before sending a code, effectively acting as a +/// login-only gate. Future onRequest(RequestContext context) async { // Ensure this is a POST request if (context.request.method != HttpMethod.post) { From bb5698bbb03d833b1060d45474f0b14987590731 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 5 Jul 2025 09:33:56 +0100 Subject: [PATCH 09/10] docs: merge redundant authentication points in readme Consolidates the two separate "Key Capabilities" points related to authentication into a single, more concise entry. This improves readability and removes redundancy while still effectively highlighting the full scope of the authentication system, including both standard user flows and the secure dashboard login. --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index e66eb0f..a8d3b92 100644 --- a/README.md +++ b/README.md @@ -16,10 +16,10 @@ management dashboard](https://github.com/headlines-toolkit/ht-dashboard). ## ✨ Key Capabilities -* 🔒 **Effortless User Authentication:** Provide secure and seamless user access - with flexible flows including passwordless sign-in, anonymous access, and - the ability to easily link anonymous accounts to permanent ones. Focus on - user experience while `ht_api` handles the security complexities. +* 🔒 **Flexible & Secure Authentication:** Provide seamless user access with + a unified system supporting passwordless sign-in, anonymous guest + accounts, and a secure, context-aware login flow for privileged dashboard + users (e.g., 'admin', 'publisher'). * ⚡️ **Flexible Role-Based Access Control (RBAC):** Implement granular permissions with a flexible, multi-role system. Assign multiple roles to From fd3dc6a1456ddad02fa74e5b58addec68e365725 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 5 Jul 2025 09:35:28 +0100 Subject: [PATCH 10/10] docs: merge data management and sorting points in readme Consolidates the "Robust Data Management" and "Flexible Data Sorting" capabilities into a single, more comprehensive point. This removes redundancy and improves the clarity of the feature list. --- README.md | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index a8d3b92..45cb4ce 100644 --- a/README.md +++ b/README.md @@ -33,13 +33,9 @@ management dashboard](https://github.com/headlines-toolkit/ht-dashboard). * 👤 **Personalized User Preferences:** Enable richer user interactions by managing and syncing user-specific data such as saved headlines, followed sources, or other personalized content tailored to individual users. -* 💾 **Robust Data Management:** Securely manage core news application data, - including headlines, categories, and sources, through a well-structured - and protected API. - -* 🔀 **Flexible Data Sorting:** Order lists of headlines, sources, and other - data by various fields in ascending or descending order, allowing for - dynamic and user-driven content presentation. +* 💾 **Robust Data Management:** Securely manage core news data (headlines, + categories, sources) through a well-structured API that supports flexible + querying and sorting for dynamic content presentation. * 📊 **Dynamic Dashboard Summary:** Access real-time, aggregated metrics on key data points like total headlines, categories, and sources, providing