From 297118329f0778e981c64167ca1da848909fc799 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 5 Jul 2025 17:20:34 +0100 Subject: [PATCH 1/6] feat(authentication): pass isDashboardLogin flag in auth bloc This change updates the AuthenticationBloc to pass the 'isDashboardLogin: true' flag when calling the authentication repository. This aligns the dashboard with the updated HtAuthClient contract, which requires this flag to differentiate dashboard-specific login flows. --- lib/authentication/bloc/authentication_bloc.dart | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/authentication/bloc/authentication_bloc.dart b/lib/authentication/bloc/authentication_bloc.dart index 0e2fffcc..2e8f5459 100644 --- a/lib/authentication/bloc/authentication_bloc.dart +++ b/lib/authentication/bloc/authentication_bloc.dart @@ -65,7 +65,10 @@ class AuthenticationBloc } emit(AuthenticationRequestCodeLoading()); try { - await _authenticationRepository.requestSignInCode(event.email); + await _authenticationRepository.requestSignInCode( + event.email, + isDashboardLogin: true, + ); emit(AuthenticationCodeSentSuccess(email: event.email)); } on InvalidInputException catch (e) { emit(AuthenticationFailure('Invalid input: ${e.message}')); @@ -95,7 +98,11 @@ class AuthenticationBloc ) async { emit(AuthenticationLoading()); try { - await _authenticationRepository.verifySignInCode(event.email, event.code); + await _authenticationRepository.verifySignInCode( + event.email, + event.code, + isDashboardLogin: true, + ); // On success, the _AuthenticationUserChanged listener will handle // emitting AuthenticationAuthenticated. } on InvalidInputException catch (e) { From 71876caddc7b290f47b33cca7dff89efbf430be0 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 5 Jul 2025 17:20:42 +0100 Subject: [PATCH 2/6] chore(pubspec): update resolved references for dependencies --- pubspec.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index 85985da8..1024b5c1 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -205,7 +205,7 @@ packages: description: path: "." ref: HEAD - resolved-ref: fd31ce8e255c27e1fcc17e849c41f9c8511a6d87 + resolved-ref: f89241dfd482d2a72b1168f979597a34b1004df5 url: "https://github.com/headlines-toolkit/ht-auth-api.git" source: git version: "0.0.0" @@ -214,7 +214,7 @@ packages: description: path: "." ref: HEAD - resolved-ref: f9c3b44b79fc19dfd9b9a7e0d1e21e60f4885617 + resolved-ref: a003eb493db4fc134db419a721ee2fda0b598032 url: "https://github.com/headlines-toolkit/ht-auth-client.git" source: git version: "0.0.0" @@ -223,7 +223,7 @@ packages: description: path: "." ref: HEAD - resolved-ref: "3a8dc5ff81c59805fa59996517eb0fdf136a0b67" + resolved-ref: "721a028b926a5a8af2b5176de039cd6394a21724" url: "https://github.com/headlines-toolkit/ht-auth-inmemory" source: git version: "0.0.0" @@ -232,7 +232,7 @@ packages: description: path: "." ref: HEAD - resolved-ref: "596ba311cdbbdf61a216f60dac0218fab9e234d9" + resolved-ref: b7de5cc86d432b17710c83a1bf8de105bb4fa00d url: "https://github.com/headlines-toolkit/ht-auth-repository.git" source: git version: "0.0.0" @@ -286,7 +286,7 @@ packages: description: path: "." ref: HEAD - resolved-ref: e2860560d21c1cf43f0e65f28c9ba722823254f2 + resolved-ref: "2378d6698df1cdeb7c5a17470f94fb8a5a99ca01" url: "https://github.com/headlines-toolkit/ht-kv-storage-service.git" source: git version: "0.0.0" @@ -304,7 +304,7 @@ packages: description: path: "." ref: HEAD - resolved-ref: b3a339a2957b35a2bb7baf249e89ef9ca296eb3e + resolved-ref: "30aff4d0e2661ff79f2b84070af5f7982d88ba66" url: "https://github.com/headlines-toolkit/ht-shared.git" source: git version: "0.0.0" From 32c33b8c28b5516a5ab7f15ce8e68c506666d4e3 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 5 Jul 2025 17:21:55 +0100 Subject: [PATCH 3/6] feat(authentication): add specific error handling for dashboard login This change enhances the AuthenticationBloc by adding specific `catch` blocks for `UnauthorizedException`, `ForbiddenException`, and `NotFoundException`. This ensures that the UI can display precise error messages to the user during the dashboard-specific authentication flow, aligning with the updated `HtAuthClient` contract. --- lib/authentication/bloc/authentication_bloc.dart | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/authentication/bloc/authentication_bloc.dart b/lib/authentication/bloc/authentication_bloc.dart index 2e8f5459..9e3d551b 100644 --- a/lib/authentication/bloc/authentication_bloc.dart +++ b/lib/authentication/bloc/authentication_bloc.dart @@ -6,11 +6,14 @@ import 'package:ht_auth_repository/ht_auth_repository.dart'; import 'package:ht_shared/ht_shared.dart' show AuthenticationException, + ForbiddenException, HtHttpException, InvalidInputException, NetworkException, + NotFoundException, OperationFailedException, ServerException, + UnauthorizedException, User; part 'authentication_event.dart'; @@ -72,6 +75,10 @@ class AuthenticationBloc emit(AuthenticationCodeSentSuccess(email: event.email)); } on InvalidInputException catch (e) { emit(AuthenticationFailure('Invalid input: ${e.message}')); + } on UnauthorizedException catch (e) { + emit(AuthenticationFailure(e.message)); + } on ForbiddenException catch (e) { + emit(AuthenticationFailure(e.message)); } on NetworkException catch (_) { emit(const AuthenticationFailure('Network error occurred.')); } on ServerException catch (e) { @@ -109,6 +116,8 @@ class AuthenticationBloc emit(AuthenticationFailure(e.message)); } on AuthenticationException catch (e) { emit(AuthenticationFailure(e.message)); + } on NotFoundException catch (e) { + emit(AuthenticationFailure(e.message)); } on NetworkException catch (_) { emit(const AuthenticationFailure('Network error occurred.')); } on ServerException catch (e) { From 80c1ed5da755e3ea658751314e2b70281da30e1d Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 5 Jul 2025 17:25:26 +0100 Subject: [PATCH 4/6] refactor(app): update AppBloc to handle user roles list This refactors the AppBloc's user change handler to correctly evaluate dashboard access based on the user.roles list instead of a singular role property. The logic now checks for 'admin' or 'publisher' roles to grant authenticated status. It also corrects the condition for fetching user settings to depend on this authenticated status, not just the presence of a user object. --- lib/app/bloc/app_bloc.dart | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/lib/app/bloc/app_bloc.dart b/lib/app/bloc/app_bloc.dart index dd995615..67419dc3 100644 --- a/lib/app/bloc/app_bloc.dart +++ b/lib/app/bloc/app_bloc.dart @@ -45,43 +45,40 @@ class AppBloc extends Bloc { AppUserChanged event, Emitter emit, ) async { - // Determine the AppStatus based on the user object and its role + final user = event.user; final AppStatus status; - switch (event.user?.role) { - case null: - status = AppStatus.unauthenticated; - case UserRole.standardUser: - status = AppStatus.authenticated; - // ignore: no_default_cases - default: // Fallback for any other roles not explicitly handled - status = AppStatus - .unauthenticated; // Treat other roles as unauthenticated for dashboard + if (user != null && + (user.roles.contains(UserRoles.admin) || + user.roles.contains(UserRoles.publisher))) { + status = AppStatus.authenticated; + } else { + status = AppStatus.unauthenticated; } // Emit user and status update - emit(state.copyWith(status: status, user: event.user)); + emit(state.copyWith(status: status, user: user)); // If user is authenticated, load their app settings - if (event.user != null) { + if (status == AppStatus.authenticated && user != null) { try { final userAppSettings = await _userAppSettingsRepository.read( - id: event.user!.id, + id: user.id, ); emit(state.copyWith(userAppSettings: userAppSettings)); } on NotFoundException { // If settings not found, create default ones - final defaultSettings = UserAppSettings(id: event.user!.id); + final defaultSettings = UserAppSettings(id: user.id); await _userAppSettingsRepository.create(item: defaultSettings); emit(state.copyWith(userAppSettings: defaultSettings)); } on HtHttpException catch (e) { // Handle HTTP exceptions during settings load print('Error loading user app settings: ${e.message}'); - emit(state.copyWith()); // Clear settings on error + emit(state.copyWith(clearUserAppSettings: true)); } catch (e) { // Handle any other unexpected errors print('Unexpected error loading user app settings: $e'); - emit(state.copyWith()); // Clear settings on error + emit(state.copyWith(clearUserAppSettings: true)); } } else { // If user is unauthenticated, clear app settings From 7fd9f18744f813bb82fff62eea7603c3d2278943 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 5 Jul 2025 17:26:47 +0100 Subject: [PATCH 5/6] refactor(app): update AppBloc to handle user roles list This refactors the AppBloc's user change handler to correctly evaluate dashboard access based on the user.roles list instead of a singular role property. The logic now checks for 'admin' or 'publisher' roles to grant authenticated status. It also corrects the condition for fetching user settings to depend on this authenticated status, not just the presence of a user object. --- .../view/app_configuration_page.dart | 59 +++++++++++-------- 1 file changed, 35 insertions(+), 24 deletions(-) diff --git a/lib/app_configuration/view/app_configuration_page.dart b/lib/app_configuration/view/app_configuration_page.dart index 31b9d1f8..b021f1b0 100644 --- a/lib/app_configuration/view/app_configuration_page.dart +++ b/lib/app_configuration/view/app_configuration_page.dart @@ -282,8 +282,13 @@ class _AppConfigurationPageState extends State { horizontal: AppSpacing.xxl, ), children: [ - _UserPreferenceLimitsForm( - userRole: UserRole.guestUser, + _UserPreferenceLimitsForm(refactor(app_configuration): use UserRoles string constants + + This change refactors the AppConfigurationPage and its helper form widgets to use string-based role constants from the `UserRoles` class instead of an obsolete `UserRole` enum. + + This aligns the UI with the updated `User` model, which represents roles as a list of strings, ensuring consistency across the application. + + userRole: UserRoles.guestUser, appConfig: appConfig, onConfigChanged: (newConfig) { context.read().add( @@ -303,7 +308,7 @@ class _AppConfigurationPageState extends State { ), children: [ _UserPreferenceLimitsForm( - userRole: UserRole.standardUser, + userRole: UserRoles.standardUser, appConfig: appConfig, onConfigChanged: (newConfig) { context.read().add( @@ -323,7 +328,7 @@ class _AppConfigurationPageState extends State { ), children: [ _UserPreferenceLimitsForm( - userRole: UserRole.premiumUser, + userRole: UserRoles.premiumUser, appConfig: appConfig, onConfigChanged: (newConfig) { context.read().add( @@ -359,7 +364,7 @@ class _AppConfigurationPageState extends State { ), children: [ _AdConfigForm( - userRole: UserRole.guestUser, + userRole: UserRoles.guestUser, appConfig: appConfig, onConfigChanged: (newConfig) { context.read().add( @@ -379,7 +384,7 @@ class _AppConfigurationPageState extends State { ), children: [ _AdConfigForm( - userRole: UserRole.standardUser, + userRole: UserRoles.standardUser, appConfig: appConfig, onConfigChanged: (newConfig) { context.read().add( @@ -399,7 +404,7 @@ class _AppConfigurationPageState extends State { ), children: [ _AdConfigForm( - userRole: UserRole.premiumUser, + userRole: UserRoles.premiumUser, appConfig: appConfig, onConfigChanged: (newConfig) { context.read().add( @@ -438,7 +443,7 @@ class _AppConfigurationPageState extends State { ), children: [ _AccountActionConfigForm( - userRole: UserRole.guestUser, + userRole: UserRoles.guestUser, appConfig: appConfig, onConfigChanged: (newConfig) { context.read().add( @@ -458,7 +463,7 @@ class _AppConfigurationPageState extends State { ), children: [ _AccountActionConfigForm( - userRole: UserRole.standardUser, + userRole: UserRoles.standardUser, appConfig: appConfig, onConfigChanged: (newConfig) { context.read().add( @@ -779,7 +784,7 @@ class _UserPreferenceLimitsForm extends StatefulWidget { required this.buildIntField, }); - final UserRole userRole; + final String userRole; final AppConfig appConfig; final ValueChanged onConfigChanged; final Widget Function( @@ -936,7 +941,7 @@ class _UserPreferenceLimitsFormState extends State<_UserPreferenceLimitsForm> { final userPreferenceLimits = widget.appConfig.userPreferenceLimits; switch (widget.userRole) { - case UserRole.guestUser: + case UserRoles.guestUser: return Column( children: [ widget.buildIntField( @@ -975,7 +980,7 @@ class _UserPreferenceLimitsFormState extends State<_UserPreferenceLimitsForm> { ), ], ); - case UserRole.standardUser: + case UserRoles.standardUser: return Column( children: [ widget.buildIntField( @@ -1015,7 +1020,7 @@ class _UserPreferenceLimitsFormState extends State<_UserPreferenceLimitsForm> { ), ], ); - case UserRole.premiumUser: + case UserRoles.premiumUser: return Column( children: [ widget.buildIntField( @@ -1055,10 +1060,12 @@ class _UserPreferenceLimitsFormState extends State<_UserPreferenceLimitsForm> { ), ], ); - case UserRole.admin: + case UserRoles.admin: // Admin role might not have specific limits here, or could be // a separate configuration. For now, return empty. return const SizedBox.shrink(); + default: + return const SizedBox.shrink(); } } } @@ -1071,7 +1078,7 @@ class _AdConfigForm extends StatefulWidget { required this.buildIntField, }); - final UserRole userRole; + final String userRole; final AppConfig appConfig; final ValueChanged onConfigChanged; final Widget Function( @@ -1271,7 +1278,7 @@ class _AdConfigFormState extends State<_AdConfigForm> { final adConfig = widget.appConfig.adConfig; switch (widget.userRole) { - case UserRole.guestUser: + case UserRoles.guestUser: return Column( children: [ widget.buildIntField( @@ -1329,7 +1336,7 @@ class _AdConfigFormState extends State<_AdConfigForm> { ), ], ); - case UserRole.standardUser: + case UserRoles.standardUser: return Column( children: [ widget.buildIntField( @@ -1391,7 +1398,7 @@ class _AdConfigFormState extends State<_AdConfigForm> { ), ], ); - case UserRole.premiumUser: + case UserRoles.premiumUser: return Column( children: [ widget.buildIntField( @@ -1450,7 +1457,9 @@ class _AdConfigFormState extends State<_AdConfigForm> { ), ], ); - case UserRole.admin: + case UserRoles.admin: + return const SizedBox.shrink(); + default: return const SizedBox.shrink(); } } @@ -1464,7 +1473,7 @@ class _AccountActionConfigForm extends StatefulWidget { required this.buildIntField, }); - final UserRole userRole; + final String userRole; final AppConfig appConfig; final ValueChanged onConfigChanged; final Widget Function( @@ -1553,7 +1562,7 @@ class _AccountActionConfigFormState extends State<_AccountActionConfigForm> { final accountActionConfig = widget.appConfig.accountActionConfig; switch (widget.userRole) { - case UserRole.guestUser: + case UserRoles.guestUser: return Column( children: [ widget.buildIntField( @@ -1576,7 +1585,7 @@ class _AccountActionConfigFormState extends State<_AccountActionConfigForm> { ), ], ); - case UserRole.standardUser: + case UserRoles.standardUser: return Column( children: [ widget.buildIntField( @@ -1599,8 +1608,10 @@ class _AccountActionConfigFormState extends State<_AccountActionConfigForm> { ), ], ); - case UserRole.premiumUser: - case UserRole.admin: + case UserRoles.premiumUser: + case UserRoles.admin: + return const SizedBox.shrink(); + default: return const SizedBox.shrink(); } } From 91ca0b3afd7041798603b00384db4523e0929539 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 5 Jul 2025 17:27:31 +0100 Subject: [PATCH 6/6] refactor(app): update AppBloc to handle user roles list This refactors the AppBloc's user change handler to correctly evaluate dashboard access based on the user.roles list instead of a singular role property. The logic now checks for 'admin' or 'publisher' roles to grant authenticated status. It also corrects the condition for fetching user settings to depend on this authenticated status, not just the presence of a user object. --- lib/app_configuration/view/app_configuration_page.dart | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/lib/app_configuration/view/app_configuration_page.dart b/lib/app_configuration/view/app_configuration_page.dart index b021f1b0..cf9cb902 100644 --- a/lib/app_configuration/view/app_configuration_page.dart +++ b/lib/app_configuration/view/app_configuration_page.dart @@ -282,12 +282,7 @@ class _AppConfigurationPageState extends State { horizontal: AppSpacing.xxl, ), children: [ - _UserPreferenceLimitsForm(refactor(app_configuration): use UserRoles string constants - - This change refactors the AppConfigurationPage and its helper form widgets to use string-based role constants from the `UserRoles` class instead of an obsolete `UserRole` enum. - - This aligns the UI with the updated `User` model, which represents roles as a list of strings, ensuring consistency across the application. - + _UserPreferenceLimitsForm( userRole: UserRoles.guestUser, appConfig: appConfig, onConfigChanged: (newConfig) {