From 72973ceb5166cb592717e5e799839e5a2c30df21 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 1 Nov 2025 09:35:41 +0100 Subject: [PATCH 01/12] feat(router): implement route permissions mapping - Create a new file for defining route permissions based on user roles - Establish a centralized mapping of dashboard user roles to permitted routes - Include permissions for admin and publisher roles - Utilize the existing Routes and DashboardUserRole enums --- lib/router/route_permissions.dart | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 lib/router/route_permissions.dart diff --git a/lib/router/route_permissions.dart b/lib/router/route_permissions.dart new file mode 100644 index 00000000..a9048eba --- /dev/null +++ b/lib/router/route_permissions.dart @@ -0,0 +1,22 @@ +import 'package:core/core.dart'; +import 'package:flutter_news_app_web_dashboard_full_source_code/router/routes.dart'; + +/// A centralized mapping of dashboard user roles to their permitted routes. +/// +/// This map is used by the router's redirect logic to enforce navigation +/// restrictions based on the authenticated user's role. +final Map> routePermissions = { + // Admins have access to all major sections of the dashboard. + DashboardUserRole.admin: { + Routes.overviewName, + Routes.contentManagementName, + Routes.userManagementName, + Routes.appConfigurationName, + }, + // Publishers have a more restricted access, focused on content creation + // and management. + DashboardUserRole.publisher: { + Routes.overviewName, + Routes.contentManagementName, + }, +}; From 77f1cd63bc77fcac19fa4eb9848bd228a84a1778 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 1 Nov 2025 09:37:26 +0100 Subject: [PATCH 02/12] feat(router): implement role-based access control (RBAC) - Add Role-Based Access Control to restrict user access based on their role - Integrate localization for unauthorized access messages - Implement redirect logic for unauthorized users - Update router to handle RBAC and localization --- lib/router/router.dart | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/lib/router/router.dart b/lib/router/router.dart index ec9238a6..63d62aae 100644 --- a/lib/router/router.dart +++ b/lib/router/router.dart @@ -25,6 +25,7 @@ import 'package:flutter_news_app_web_dashboard_full_source_code/content_manageme import 'package:flutter_news_app_web_dashboard_full_source_code/content_management/view/edit_topic_page.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/content_management/widgets/filter_dialog/bloc/filter_dialog_bloc.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/content_management/widgets/filter_dialog/filter_dialog.dart'; +import 'package:flutter_news_app_web_dashboard_full_source_code/l10n/l10n.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/local_ads_management/bloc/filter_local_ads/filter_local_ads_bloc.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/local_ads_management/view/create_local_banner_ad_page.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/local_ads_management/view/create_local_interstitial_ad_page.dart'; @@ -38,6 +39,7 @@ import 'package:flutter_news_app_web_dashboard_full_source_code/local_ads_manage import 'package:flutter_news_app_web_dashboard_full_source_code/local_ads_management/widgets/local_ads_filter_dialog/bloc/local_ads_filter_dialog_bloc.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/local_ads_management/widgets/local_ads_filter_dialog/local_ads_filter_dialog.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/overview/view/overview_page.dart'; +import 'package:flutter_news_app_web_dashboard_full_source_code/router/route_permissions.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/router/routes.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/settings/view/settings_page.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/shared/widgets/selection_page/searchable_selection_page.dart'; @@ -64,6 +66,7 @@ GoRouter createRouter({ // --- Redirect Logic --- redirect: (BuildContext context, GoRouterState state) { final appStatus = context.read().state.status; + final l10n = AppLocalizationsX(context).l10n; final currentLocation = state.matchedLocation; print( @@ -95,6 +98,32 @@ GoRouter createRouter({ if (appStatus == AppStatus.authenticated) { print(' Redirect Decision: User is $appStatus.'); + // --- Role-Based Access Control (RBAC) --- + final userRole = context.read().state.user?.dashboardRole; + final destinationRouteName = state.topRoute?.name; + + // Allow navigation if role is not yet determined or route is unknown. + if (userRole == null || destinationRouteName == null) { + return null; + } + + final allowedRoutes = routePermissions[userRole]; + + // Check if the user is trying to access a route they are not + // permitted to view. + final isAuthorized = allowedRoutes?.contains(destinationRouteName) ?? + false; + + // Universally allowed routes like 'settings' are exempt from this check. + if (!isAuthorized && destinationRouteName != Routes.settingsName) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(l10n.unauthorizedAccessRedirect)), + ); + // Redirect unauthorized users to the overview page. + return Routes.overview; + } + // --- End of RBAC --- + // If an authenticated user is on any authentication-related path: if (isGoingToAuth) { print( From af0328bb59f2fdd2f6c747fc279fa234f650f3a2 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 1 Nov 2025 09:40:21 +0100 Subject: [PATCH 03/12] feat(app_shell): implement role-based navigation filtering - Add core dependency for AppBloc and AppState - Implement role-based filtering for navigation destinations - Admin can see all destinations - Publisher can only see Overview and Content Management - Refactor widget build process to use BlocBuilder --- lib/app/view/app_shell.dart | 254 ++++++++++++++++++++---------------- 1 file changed, 140 insertions(+), 114 deletions(-) diff --git a/lib/app/view/app_shell.dart b/lib/app/view/app_shell.dart index 7b82dcbf..58474407 100644 --- a/lib/app/view/app_shell.dart +++ b/lib/app/view/app_shell.dart @@ -1,3 +1,4 @@ +import 'package:core/core.dart'; import 'package:flutter/material.dart'; import 'package:flutter_adaptive_scaffold/flutter_adaptive_scaffold.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -24,22 +25,17 @@ class AppShell extends StatelessWidget { @override Widget build(BuildContext context) { - final l10n = AppLocalizationsX(context).l10n; - final theme = Theme.of(context); + return BlocBuilder( + builder: (context, state) { + final l10n = AppLocalizationsX(context).l10n; + final theme = Theme.of(context); + final userRole = state.user?.dashboardRole; - // Use the same text style as the NavigationRail labels for consistency. - final navRailLabelStyle = theme.textTheme.labelMedium; + // Use the same text style as the NavigationRail labels for consistency. + final navRailLabelStyle = theme.textTheme.labelMedium; - return Scaffold( - body: AdaptiveScaffold( - selectedIndex: navigationShell.currentIndex, - onSelectedIndexChange: (index) { - navigationShell.goBranch( - index, - initialLocation: index == navigationShell.currentIndex, - ); - }, - destinations: [ + // A complete list of all possible navigation destinations. + final allDestinations = [ NavigationDestination( icon: const Icon(Icons.dashboard_outlined), selectedIcon: const Icon(Icons.dashboard), @@ -60,119 +56,149 @@ class AppShell extends StatelessWidget { selectedIcon: const Icon(Icons.settings_applications), label: l10n.appConfiguration, ), - ], - leadingUnextendedNavRail: Padding( - padding: const EdgeInsets.symmetric(vertical: AppSpacing.lg), - child: Icon( - Icons.newspaper_outlined, - color: theme.colorScheme.primary, - ), - ), - leadingExtendedNavRail: Padding( - padding: const EdgeInsets.all(AppSpacing.lg), - child: Row( - children: [ - Icon( + ]; + + // Filter the destinations based on the user's role. + final accessibleDestinations = allDestinations.where((destination) { + if (userRole == null) return false; + + switch (userRole) { + case DashboardUserRole.admin: + // Admin can see all destinations. + return true; + case DashboardUserRole.publisher: + // Publisher can only see Overview and Content Management. + return destination.label == l10n.overview || + destination.label == l10n.contentManagement; + case DashboardUserRole.none: + return false; + } + }).toList(); + + return Scaffold( + body: AdaptiveScaffold( + selectedIndex: navigationShell.currentIndex, + onSelectedIndexChange: (index) { + navigationShell.goBranch( + index, + initialLocation: index == navigationShell.currentIndex, + ); + }, + destinations: accessibleDestinations, + leadingUnextendedNavRail: Padding( + padding: const EdgeInsets.symmetric(vertical: AppSpacing.lg), + child: Icon( Icons.newspaper_outlined, color: theme.colorScheme.primary, ), - const SizedBox(width: AppSpacing.md), - Text( - l10n.dashboardTitle, - style: theme.textTheme.titleLarge, + ), + leadingExtendedNavRail: Padding( + padding: const EdgeInsets.all(AppSpacing.lg), + child: Row( + children: [ + Icon( + Icons.newspaper_outlined, + color: theme.colorScheme.primary, + ), + const SizedBox(width: AppSpacing.md), + Text( + l10n.dashboardTitle, + style: theme.textTheme.titleLarge, + ), + ], ), - ], - ), - ), - trailingNavRail: Builder( - builder: (context) { - final isExtended = - Breakpoints.mediumLargeAndUp.isActive(context) || - Breakpoints.small.isActive(context); - return Expanded( - child: Padding( - padding: const EdgeInsets.only(bottom: AppSpacing.lg), - child: Column( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - // Settings Tile - InkWell( - onTap: () => context.goNamed(Routes.settingsName), - child: Padding( - padding: EdgeInsets.symmetric( - vertical: AppSpacing.md, - horizontal: isExtended ? 24 : 16, - ), - child: Row( - mainAxisAlignment: isExtended - ? MainAxisAlignment.start - : MainAxisAlignment.center, - children: [ - Icon( - Icons.settings_outlined, - color: theme.colorScheme.onSurfaceVariant, - size: 24, + ), + trailingNavRail: Builder( + builder: (context) { + final isExtended = + Breakpoints.mediumLargeAndUp.isActive(context) || + Breakpoints.small.isActive(context); + return Expanded( + child: Padding( + padding: const EdgeInsets.only(bottom: AppSpacing.lg), + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + // Settings Tile - universally accessible to all roles. + InkWell( + onTap: () => context.goNamed(Routes.settingsName), + child: Padding( + padding: EdgeInsets.symmetric( + vertical: AppSpacing.md, + horizontal: isExtended ? 24 : 16, ), - if (isExtended) ...[ - const SizedBox(width: AppSpacing.lg), - Text( - l10n.settings, - style: navRailLabelStyle, - ), - ], - ], - ), - ), - ), - // Sign Out Tile - InkWell( - onTap: () => context.read().add( - const AppLogoutRequested(), - ), - child: Padding( - padding: EdgeInsets.symmetric( - vertical: AppSpacing.md, - horizontal: isExtended ? 24 : 16, + child: Row( + mainAxisAlignment: isExtended + ? MainAxisAlignment.start + : MainAxisAlignment.center, + children: [ + Icon( + Icons.settings_outlined, + color: theme.colorScheme.onSurfaceVariant, + size: 24, + ), + if (isExtended) ...[ + const SizedBox(width: AppSpacing.lg), + Text( + l10n.settings, + style: navRailLabelStyle, + ), + ], + ], + ), + ), ), - child: Row( - mainAxisAlignment: isExtended - ? MainAxisAlignment.start - : MainAxisAlignment.center, - children: [ - Icon( - Icons.logout, - color: theme.colorScheme.error, - size: 24, + // Sign Out Tile + InkWell( + onTap: () => context.read().add( + const AppLogoutRequested(), + ), + child: Padding( + padding: EdgeInsets.symmetric( + vertical: AppSpacing.md, + horizontal: isExtended ? 24 : 16, ), - if (isExtended) ...[ - const SizedBox(width: AppSpacing.lg), - Text( - l10n.signOut, - style: navRailLabelStyle?.copyWith( + child: Row( + mainAxisAlignment: isExtended + ? MainAxisAlignment.start + : MainAxisAlignment.center, + children: [ + Icon( + Icons.logout, color: theme.colorScheme.error, + size: 24, ), - ), - ], - ], + if (isExtended) ...[ + const SizedBox(width: AppSpacing.lg), + Text( + l10n.signOut, + style: navRailLabelStyle?.copyWith( + color: theme.colorScheme.error, + ), + ), + ], + ], + ), + ), ), - ), + ], ), - ], - ), + ), + ); + }, + ), + body: (_) => Padding( + padding: const EdgeInsets.fromLTRB( + 0, + AppSpacing.sm, + AppSpacing.sm, + AppSpacing.sm, ), - ); - }, - ), - body: (_) => Padding( - padding: const EdgeInsets.fromLTRB( - 0, - AppSpacing.sm, - AppSpacing.sm, - AppSpacing.sm, + child: navigationShell, + ), ), - child: navigationShell, - ), - ), + ); + }, ); } } From cd4d0acd528c0010032790a73bf9e04c10decee7 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 1 Nov 2025 09:43:19 +0100 Subject: [PATCH 04/12] feat(l10n): add unauthorized access redirect message - Add new localization entries for unauthorized access redirect message in both Arabic and English - Update app_ar.arb and app_en.arb files with the new translations --- lib/l10n/arb/app_ar.arb | 4 ++++ lib/l10n/arb/app_en.arb | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/lib/l10n/arb/app_ar.arb b/lib/l10n/arb/app_ar.arb index 8a3204cc..d7fe2f56 100644 --- a/lib/l10n/arb/app_ar.arb +++ b/lib/l10n/arb/app_ar.arb @@ -2011,5 +2011,9 @@ "subscriptionPremium": "مميز", "@subscriptionPremium": { "description": "حالة الاشتراك لمستخدم مميز" + }, + "unauthorizedAccessRedirect": "إعادة التوجيه: ليس لديك إذن للوصول إلى هذه الصفحة.", + "@unauthorizedAccessRedirect": { + "description": "رسالة شريط التنبيه التي تظهر عند إعادة توجيه المستخدم بسبب نقص الأذونات." } } \ No newline at end of file diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 7f04050b..9dd50fda 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -2007,5 +2007,9 @@ "subscriptionPremium": "Premium", "@subscriptionPremium": { "description": "Subscription status for a premium user" + }, + "unauthorizedAccessRedirect": "Redirecting: You do not have permission to access this page.", + "@unauthorizedAccessRedirect": { + "description": "Snackbar message shown when a user is redirected due to lack of permissions." } } \ No newline at end of file From f81bfd0be67fde164c4955197228b5fb54604dc9 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 1 Nov 2025 09:43:37 +0100 Subject: [PATCH 05/12] build: l10n --- lib/l10n/app_localizations.dart | 6 ++++++ lib/l10n/app_localizations_ar.dart | 4 ++++ lib/l10n/app_localizations_en.dart | 4 ++++ 3 files changed, 14 insertions(+) diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 27bb3437..8f33c92d 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -2977,6 +2977,12 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'Premium'** String get subscriptionPremium; + + /// Snackbar message shown when a user is redirected due to lack of permissions. + /// + /// In en, this message translates to: + /// **'Redirecting: You do not have permission to access this page.'** + String get unauthorizedAccessRedirect; } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_ar.dart b/lib/l10n/app_localizations_ar.dart index 76994143..5e6cf73e 100644 --- a/lib/l10n/app_localizations_ar.dart +++ b/lib/l10n/app_localizations_ar.dart @@ -1582,4 +1582,8 @@ class AppLocalizationsAr extends AppLocalizations { @override String get subscriptionPremium => 'مميز'; + + @override + String get unauthorizedAccessRedirect => + 'إعادة التوجيه: ليس لديك إذن للوصول إلى هذه الصفحة.'; } diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 04325775..bc48ff5d 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -1588,4 +1588,8 @@ class AppLocalizationsEn extends AppLocalizations { @override String get subscriptionPremium => 'Premium'; + + @override + String get unauthorizedAccessRedirect => + 'Redirecting: You do not have permission to access this page.'; } From 537033656a01e2772e9995e51c8df7fd8bccad8a Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 1 Nov 2025 09:56:23 +0100 Subject: [PATCH 06/12] docs(README): add role-based access control description - Introduce RBAC system for secure team member access - Highlight protected navigation and conditional UI features - Explain advantages for team efficiency and security - Add section to README, maintaining content structure --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 218f918e..0f0d2ed7 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,14 @@ A complete and secure user authentication system is built-in for your editorial --- +### 🛡️ Role-Based Access Control (RBAC) +The dashboard implements a robust RBAC system to ensure team members only access the sections relevant to their role. +- **Protected Navigation:** The system prevents direct URL access to restricted areas, automatically redirecting unauthorized users. +- **Conditional UI:** The navigation sidebar dynamically adapts, showing only the links and tools a user is permitted to see. +> **Your Advantage:** Enforce a clear separation of duties within your team. Administrators maintain full control, while Publishers can focus solely on content management, creating a secure and efficient workflow. + +--- + ### 🎨 A Personalized Workspace Empower your team with a dashboard experience they can tailor to their own preferences, improving comfort and productivity. - **Full Appearance Control:** Each team member can configure their own workspace, including light/dark themes, accent colors, and text styles. From 67ac4d8ade96e9e36b3d3ae6e1c5ad76b1a398ae Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 1 Nov 2025 10:03:16 +0100 Subject: [PATCH 07/12] refactor(app): improve navigation item filtering and selection - Implement role-based filtering for navigation items - Add support for parallel list of route names for permission checking - Introduce indexedDestinations to maintain order and association - Enhance selectedIndex determination for accessible destinations - Update goBranch logic to handle filtered navigation items --- lib/app/view/app_shell.dart | 60 ++++++++++++++++++++++++++----------- 1 file changed, 42 insertions(+), 18 deletions(-) diff --git a/lib/app/view/app_shell.dart b/lib/app/view/app_shell.dart index 58474407..9a7e2605 100644 --- a/lib/app/view/app_shell.dart +++ b/lib/app/view/app_shell.dart @@ -4,6 +4,7 @@ import 'package:flutter_adaptive_scaffold/flutter_adaptive_scaffold.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/app/bloc/app_bloc.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/l10n/l10n.dart'; +import 'package:flutter_news_app_web_dashboard_full_source_code/router/route_permissions.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/router/routes.dart'; import 'package:go_router/go_router.dart'; import 'package:ui_kit/ui_kit.dart'; @@ -58,30 +59,53 @@ class AppShell extends StatelessWidget { ), ]; - // Filter the destinations based on the user's role. - final accessibleDestinations = allDestinations.where((destination) { - if (userRole == null) return false; + // A parallel list of route names for permission checking, matching the + // order of `allDestinations`. + const allRouteNames = [ + Routes.overviewName, + Routes.contentManagementName, + Routes.userManagementName, + Routes.appConfigurationName, + ]; + + // Create a list of records containing the destination, its original + // index, and its route name. + final indexedDestinations = [ + for (var i = 0; i < allDestinations.length; i++) + ( + destination: allDestinations[i], + originalIndex: i, + routeName: allRouteNames[i], + ), + ]; - switch (userRole) { - case DashboardUserRole.admin: - // Admin can see all destinations. - return true; - case DashboardUserRole.publisher: - // Publisher can only see Overview and Content Management. - return destination.label == l10n.overview || - destination.label == l10n.contentManagement; - case DashboardUserRole.none: - return false; - } - }).toList(); + // Filter the destinations based on the user's role and allowed routes. + final allowedRoutes = routePermissions[userRole] ?? {}; + final accessibleNavItems = indexedDestinations + .where((item) => allowedRoutes.contains(item.routeName)) + .toList(); + + final accessibleDestinations = accessibleNavItems + .map((item) => item.destination) + .toList(); + + // Find the current index in the list of *accessible* destinations. + final selectedIndex = accessibleNavItems.indexWhere( + (item) => item.originalIndex == navigationShell.currentIndex, + ); return Scaffold( body: AdaptiveScaffold( - selectedIndex: navigationShell.currentIndex, + selectedIndex: selectedIndex > -1 ? selectedIndex : 0, onSelectedIndexChange: (index) { + // Map the index from the accessible list back to the original + // branch index. + final originalBranchIndex = + accessibleNavItems[index].originalIndex; navigationShell.goBranch( - index, - initialLocation: index == navigationShell.currentIndex, + originalBranchIndex, + initialLocation: + originalBranchIndex == navigationShell.currentIndex, ); }, destinations: accessibleDestinations, From ae0a82951a40fcf422aec65b5b58be7120749311 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 1 Nov 2025 10:04:28 +0100 Subject: [PATCH 08/12] fix(l10n): remove redundant "Redirecting:" from access denial messages - Remove "Redirecting:" prefix from unauthorizedAccessRedirect message in app_ar.arb and app_en.arb - Keep the description of the message intact --- lib/l10n/app_localizations.dart | 2 +- lib/l10n/app_localizations_ar.dart | 2 +- lib/l10n/app_localizations_en.dart | 2 +- lib/l10n/arb/app_ar.arb | 2 +- lib/l10n/arb/app_en.arb | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 8f33c92d..3f8f3890 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -2981,7 +2981,7 @@ abstract class AppLocalizations { /// Snackbar message shown when a user is redirected due to lack of permissions. /// /// In en, this message translates to: - /// **'Redirecting: You do not have permission to access this page.'** + /// **'You do not have permission to access this page.'** String get unauthorizedAccessRedirect; } diff --git a/lib/l10n/app_localizations_ar.dart b/lib/l10n/app_localizations_ar.dart index 5e6cf73e..94817591 100644 --- a/lib/l10n/app_localizations_ar.dart +++ b/lib/l10n/app_localizations_ar.dart @@ -1585,5 +1585,5 @@ class AppLocalizationsAr extends AppLocalizations { @override String get unauthorizedAccessRedirect => - 'إعادة التوجيه: ليس لديك إذن للوصول إلى هذه الصفحة.'; + 'ليس لديك إذن للوصول إلى هذه الصفحة.'; } diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index bc48ff5d..126680f9 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -1591,5 +1591,5 @@ class AppLocalizationsEn extends AppLocalizations { @override String get unauthorizedAccessRedirect => - 'Redirecting: You do not have permission to access this page.'; + 'You do not have permission to access this page.'; } diff --git a/lib/l10n/arb/app_ar.arb b/lib/l10n/arb/app_ar.arb index d7fe2f56..255f00fe 100644 --- a/lib/l10n/arb/app_ar.arb +++ b/lib/l10n/arb/app_ar.arb @@ -2012,7 +2012,7 @@ "@subscriptionPremium": { "description": "حالة الاشتراك لمستخدم مميز" }, - "unauthorizedAccessRedirect": "إعادة التوجيه: ليس لديك إذن للوصول إلى هذه الصفحة.", + "unauthorizedAccessRedirect": "ليس لديك إذن للوصول إلى هذه الصفحة.", "@unauthorizedAccessRedirect": { "description": "رسالة شريط التنبيه التي تظهر عند إعادة توجيه المستخدم بسبب نقص الأذونات." } diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 9dd50fda..71a31d13 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -2008,7 +2008,7 @@ "@subscriptionPremium": { "description": "Subscription status for a premium user" }, - "unauthorizedAccessRedirect": "Redirecting: You do not have permission to access this page.", + "unauthorizedAccessRedirect": "You do not have permission to access this page.", "@unauthorizedAccessRedirect": { "description": "Snackbar message shown when a user is redirected due to lack of permissions." } From 0446ede3286980724c80451d285d22dc4c914353 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 1 Nov 2025 10:04:56 +0100 Subject: [PATCH 09/12] style: format --- lib/app/view/app_shell.dart | 1 - lib/router/router.dart | 4 ++-- lib/user_management/bloc/user_management_bloc.dart | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/app/view/app_shell.dart b/lib/app/view/app_shell.dart index 9a7e2605..88ea06e9 100644 --- a/lib/app/view/app_shell.dart +++ b/lib/app/view/app_shell.dart @@ -1,4 +1,3 @@ -import 'package:core/core.dart'; import 'package:flutter/material.dart'; import 'package:flutter_adaptive_scaffold/flutter_adaptive_scaffold.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; diff --git a/lib/router/router.dart b/lib/router/router.dart index 63d62aae..11a86a0c 100644 --- a/lib/router/router.dart +++ b/lib/router/router.dart @@ -111,8 +111,8 @@ GoRouter createRouter({ // Check if the user is trying to access a route they are not // permitted to view. - final isAuthorized = allowedRoutes?.contains(destinationRouteName) ?? - false; + final isAuthorized = + allowedRoutes?.contains(destinationRouteName) ?? false; // Universally allowed routes like 'settings' are exempt from this check. if (!isAuthorized && destinationRouteName != Routes.settingsName) { diff --git a/lib/user_management/bloc/user_management_bloc.dart b/lib/user_management/bloc/user_management_bloc.dart index 0ba3f473..9a50b77d 100644 --- a/lib/user_management/bloc/user_management_bloc.dart +++ b/lib/user_management/bloc/user_management_bloc.dart @@ -1,12 +1,12 @@ import 'dart:async'; import 'package:bloc/bloc.dart'; -import 'package:logging/logging.dart'; import 'package:core/core.dart'; import 'package:data_repository/data_repository.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/shared/constants/app_constants.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/user_management/bloc/user_filter/user_filter_bloc.dart'; +import 'package:logging/logging.dart'; import 'package:ui_kit/ui_kit.dart'; part 'user_management_event.dart'; From 25a00eed994ec82d7ee063bab8ee8bcaf7ddc2a7 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 1 Nov 2025 11:24:42 +0100 Subject: [PATCH 10/12] refactor(router): remove unused localization and snackbar redirect - Remove unused localization import and unused l10n variable - Remove snackbar display for unauthorized access redirect - Add print statement for unauthorized access attempt - Update redirect comment to explain safe redirect logic --- lib/router/router.dart | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/router/router.dart b/lib/router/router.dart index 11a86a0c..bbe86af7 100644 --- a/lib/router/router.dart +++ b/lib/router/router.dart @@ -25,7 +25,6 @@ import 'package:flutter_news_app_web_dashboard_full_source_code/content_manageme import 'package:flutter_news_app_web_dashboard_full_source_code/content_management/view/edit_topic_page.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/content_management/widgets/filter_dialog/bloc/filter_dialog_bloc.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/content_management/widgets/filter_dialog/filter_dialog.dart'; -import 'package:flutter_news_app_web_dashboard_full_source_code/l10n/l10n.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/local_ads_management/bloc/filter_local_ads/filter_local_ads_bloc.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/local_ads_management/view/create_local_banner_ad_page.dart'; import 'package:flutter_news_app_web_dashboard_full_source_code/local_ads_management/view/create_local_interstitial_ad_page.dart'; @@ -66,7 +65,6 @@ GoRouter createRouter({ // --- Redirect Logic --- redirect: (BuildContext context, GoRouterState state) { final appStatus = context.read().state.status; - final l10n = AppLocalizationsX(context).l10n; final currentLocation = state.matchedLocation; print( @@ -116,10 +114,12 @@ GoRouter createRouter({ // Universally allowed routes like 'settings' are exempt from this check. if (!isAuthorized && destinationRouteName != Routes.settingsName) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(l10n.unauthorizedAccessRedirect)), + print( + ' Action: Unauthorized access to "$destinationRouteName". ' + 'Redirecting to $overviewPath.', ); - // Redirect unauthorized users to the overview page. + // Redirect unauthorized users to the overview page. This is a safe + // redirect without side effects. return Routes.overview; } // --- End of RBAC --- From 8631472287fd88b92bbaf8b114f0451a2de97994 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 1 Nov 2025 11:24:59 +0100 Subject: [PATCH 11/12] refactor(l10n): remove unauthorized access redirect localization - Remove "unauthorizedAccessRedirect" key and its description from app_ar.arb and app_en.arb files - This change simplifies the localization files by removing unnecessary entries --- lib/l10n/arb/app_ar.arb | 4 ---- lib/l10n/arb/app_en.arb | 4 ---- 2 files changed, 8 deletions(-) diff --git a/lib/l10n/arb/app_ar.arb b/lib/l10n/arb/app_ar.arb index 255f00fe..8a3204cc 100644 --- a/lib/l10n/arb/app_ar.arb +++ b/lib/l10n/arb/app_ar.arb @@ -2011,9 +2011,5 @@ "subscriptionPremium": "مميز", "@subscriptionPremium": { "description": "حالة الاشتراك لمستخدم مميز" - }, - "unauthorizedAccessRedirect": "ليس لديك إذن للوصول إلى هذه الصفحة.", - "@unauthorizedAccessRedirect": { - "description": "رسالة شريط التنبيه التي تظهر عند إعادة توجيه المستخدم بسبب نقص الأذونات." } } \ No newline at end of file diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 71a31d13..7f04050b 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -2007,9 +2007,5 @@ "subscriptionPremium": "Premium", "@subscriptionPremium": { "description": "Subscription status for a premium user" - }, - "unauthorizedAccessRedirect": "You do not have permission to access this page.", - "@unauthorizedAccessRedirect": { - "description": "Snackbar message shown when a user is redirected due to lack of permissions." } } \ No newline at end of file From c021c5fd273f8036fc10e5ccea5c20d96e995c04 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 1 Nov 2025 11:25:07 +0100 Subject: [PATCH 12/12] build: l10n --- lib/l10n/app_localizations.dart | 6 ------ lib/l10n/app_localizations_ar.dart | 4 ---- lib/l10n/app_localizations_en.dart | 4 ---- 3 files changed, 14 deletions(-) diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 3f8f3890..27bb3437 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -2977,12 +2977,6 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'Premium'** String get subscriptionPremium; - - /// Snackbar message shown when a user is redirected due to lack of permissions. - /// - /// In en, this message translates to: - /// **'You do not have permission to access this page.'** - String get unauthorizedAccessRedirect; } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_ar.dart b/lib/l10n/app_localizations_ar.dart index 94817591..76994143 100644 --- a/lib/l10n/app_localizations_ar.dart +++ b/lib/l10n/app_localizations_ar.dart @@ -1582,8 +1582,4 @@ class AppLocalizationsAr extends AppLocalizations { @override String get subscriptionPremium => 'مميز'; - - @override - String get unauthorizedAccessRedirect => - 'ليس لديك إذن للوصول إلى هذه الصفحة.'; } diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 126680f9..04325775 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -1588,8 +1588,4 @@ class AppLocalizationsEn extends AppLocalizations { @override String get subscriptionPremium => 'Premium'; - - @override - String get unauthorizedAccessRedirect => - 'You do not have permission to access this page.'; }