Imporve the role based forntend protected roles

This commit is contained in:
Urtzi Alfaro
2025-09-09 07:32:59 +02:00
parent ddb75f8e55
commit 5269a083b6
15 changed files with 286 additions and 91 deletions

View File

@@ -5,6 +5,7 @@
import React from 'react';
import { Navigate, useLocation } from 'react-router-dom';
import { useAuthUser, useIsAuthenticated, useAuthLoading } from '../stores';
import { useCurrentTenantAccess, useTenantPermissions } from '../stores/tenant.store';
import { RouteConfig, canAccessRoute, ROUTES } from './routes.config';
interface ProtectedRouteProps {
@@ -126,6 +127,8 @@ export const ProtectedRoute: React.FC<ProtectedRouteProps> = ({
const user = useAuthUser();
const isAuthenticated = useIsAuthenticated();
const isLoading = useAuthLoading();
const currentTenantAccess = useCurrentTenantAccess();
const { hasPermission } = useTenantPermissions();
const location = useLocation();
// Note: Onboarding routes are now properly protected and require authentication
@@ -160,16 +163,21 @@ export const ProtectedRoute: React.FC<ProtectedRouteProps> = ({
}
// Get user roles and permissions
const userRoles = user?.role ? [user.role] : [];
const userPermissions: string[] = [];
const globalUserRoles = user?.role ? [user.role] : [];
const tenantRole = currentTenantAccess?.role;
const tenantRoles = tenantRole ? [tenantRole] : [];
// Combine global and tenant roles for comprehensive access control
const allUserRoles = [...globalUserRoles, ...tenantRoles];
const tenantPermissions = currentTenantAccess?.permissions || [];
// Check if user can access this route
const canAccess = canAccessRoute(route, isAuthenticated, userRoles, userPermissions);
const canAccess = canAccessRoute(route, isAuthenticated, allUserRoles, tenantPermissions);
if (!canAccess) {
// Check if it's a permission issue or role issue
const hasRequiredRoles = !route.requiredRoles ||
route.requiredRoles.some(role => userRoles.includes(role));
route.requiredRoles.some(role => allUserRoles.includes(role as string));
if (!hasRequiredRoles) {
return <UnauthorizedPage />;
@@ -211,22 +219,29 @@ export const useRouteAccess = (route: RouteConfig) => {
const user = useAuthUser();
const isAuthenticated = useIsAuthenticated();
const isLoading = useAuthLoading();
const currentTenantAccess = useCurrentTenantAccess();
const userRoles = user?.role ? [user.role] : [];
const userPermissions: string[] = [];
const globalUserRoles = user?.role ? [user.role as string] : [];
const tenantRole = currentTenantAccess?.role;
const tenantRoles = tenantRole ? [tenantRole as string] : [];
const allUserRoles = [...globalUserRoles, ...tenantRoles];
const tenantPermissions = currentTenantAccess?.permissions || [];
const canAccess = canAccessRoute(route, isAuthenticated, userRoles, userPermissions);
const canAccess = canAccessRoute(route, isAuthenticated, allUserRoles, tenantPermissions);
return {
canAccess,
isLoading,
isAuthenticated,
userRoles,
userPermissions,
globalUserRoles,
tenantRoles,
allUserRoles,
tenantPermissions,
currentTenantAccess,
hasRequiredRoles: !route.requiredRoles ||
route.requiredRoles.some(role => userRoles.includes(role)),
route.requiredRoles.some(role => allUserRoles.includes(role as string)),
hasRequiredPermissions: !route.requiredPermissions ||
route.requiredPermissions.every(permission => userPermissions.includes(permission)),
route.requiredPermissions.every(permission => tenantPermissions.includes(permission)),
};
};
@@ -248,21 +263,25 @@ export const ConditionalRender: React.FC<ConditionalRenderProps> = ({
}) => {
const user = useAuthUser();
const isAuthenticated = useIsAuthenticated();
const currentTenantAccess = useCurrentTenantAccess();
if (!isAuthenticated || !user) {
return <>{fallback}</>;
}
const userRoles = user.role ? [user.role] : [];
const userPermissions: string[] = [];
const globalUserRoles = user.role ? [user.role as string] : [];
const tenantRole = currentTenantAccess?.role;
const tenantRoles = tenantRole ? [tenantRole as string] : [];
const allUserRoles = [...globalUserRoles, ...tenantRoles];
const tenantPermissions = currentTenantAccess?.permissions || [];
// Check roles
let hasRoles = true;
if (requiredRoles.length > 0) {
if (requireAll) {
hasRoles = requiredRoles.every(role => userRoles.includes(role));
hasRoles = requiredRoles.every(role => allUserRoles.includes(role as string));
} else {
hasRoles = requiredRoles.some(role => userRoles.includes(role));
hasRoles = requiredRoles.some(role => allUserRoles.includes(role as string));
}
}
@@ -270,9 +289,9 @@ export const ConditionalRender: React.FC<ConditionalRenderProps> = ({
let hasPermissions = true;
if (requiredPermissions.length > 0) {
if (requireAll) {
hasPermissions = requiredPermissions.every(permission => userPermissions.includes(permission));
hasPermissions = requiredPermissions.every(permission => tenantPermissions.includes(permission));
} else {
hasPermissions = requiredPermissions.some(permission => userPermissions.includes(permission));
hasPermissions = requiredPermissions.some(permission => tenantPermissions.includes(permission));
}
}
@@ -283,11 +302,11 @@ export const ConditionalRender: React.FC<ConditionalRenderProps> = ({
return <>{fallback}</>;
};
// Route guard for admin-only routes
// Route guard for admin-only routes (global admin or tenant owner/admin)
export const AdminRoute: React.FC<{ children: React.ReactNode }> = ({ children }) => {
return (
<ConditionalRender
requiredRoles={['admin', 'super_admin']}
requiredRoles={['admin', 'super_admin', 'owner']}
requireAll={false}
fallback={<UnauthorizedPage />}
>
@@ -296,11 +315,24 @@ export const AdminRoute: React.FC<{ children: React.ReactNode }> = ({ children }
);
};
// Route guard for manager-level routes
// Route guard for manager-level routes (global admin/manager or tenant admin/owner)
export const ManagerRoute: React.FC<{ children: React.ReactNode }> = ({ children }) => {
return (
<ConditionalRender
requiredRoles={['admin', 'super_admin', 'manager']}
requiredRoles={['admin', 'super_admin', 'manager', 'owner']}
requireAll={false}
fallback={<UnauthorizedPage />}
>
{children}
</ConditionalRender>
);
};
// Route guard for tenant owner-only routes
export const OwnerRoute: React.FC<{ children: React.ReactNode }> = ({ children }) => {
return (
<ConditionalRender
requiredRoles={['super_admin', 'owner']}
requireAll={false}
fallback={<UnauthorizedPage />}
>

View File

@@ -2,6 +2,8 @@
* Route configuration for the bakery management application
*/
import { ROLE_COMBINATIONS } from '../types/roles';
export interface RouteConfig {
path: string;
name: string;
@@ -286,7 +288,7 @@ export const routesConfig: RouteConfig[] = [
title: 'Analytics',
icon: 'sales',
requiresAuth: true,
requiredRoles: ['admin', 'manager'],
requiredRoles: ROLE_COMBINATIONS.MANAGEMENT_ACCESS,
showInNavigation: true,
children: [
{
@@ -296,7 +298,7 @@ export const routesConfig: RouteConfig[] = [
title: 'Pronósticos',
icon: 'forecasting',
requiresAuth: true,
requiredRoles: ['admin', 'manager'],
requiredRoles: ROLE_COMBINATIONS.MANAGEMENT_ACCESS,
showInNavigation: true,
showInBreadcrumbs: true,
},
@@ -307,7 +309,7 @@ export const routesConfig: RouteConfig[] = [
title: 'Análisis de Ventas',
icon: 'sales',
requiresAuth: true,
requiredRoles: ['admin', 'manager'],
requiredRoles: ROLE_COMBINATIONS.MANAGEMENT_ACCESS,
showInNavigation: true,
showInBreadcrumbs: true,
},
@@ -318,7 +320,7 @@ export const routesConfig: RouteConfig[] = [
title: 'Análisis de Rendimiento',
icon: 'sales',
requiresAuth: true,
requiredRoles: ['admin', 'manager'],
requiredRoles: ROLE_COMBINATIONS.MANAGEMENT_ACCESS,
showInNavigation: true,
showInBreadcrumbs: true,
},
@@ -329,7 +331,7 @@ export const routesConfig: RouteConfig[] = [
title: 'Insights de IA',
icon: 'forecasting',
requiresAuth: true,
requiredRoles: ['admin', 'manager'],
requiredRoles: ROLE_COMBINATIONS.MANAGEMENT_ACCESS,
showInNavigation: true,
showInBreadcrumbs: true,
},
@@ -363,7 +365,7 @@ export const routesConfig: RouteConfig[] = [
title: 'Configuración de Panadería',
icon: 'settings',
requiresAuth: true,
requiredRoles: ['admin'],
requiredRoles: ROLE_COMBINATIONS.ADMIN_ACCESS,
showInNavigation: true,
showInBreadcrumbs: true,
},
@@ -374,7 +376,7 @@ export const routesConfig: RouteConfig[] = [
title: 'Gestión de Equipo',
icon: 'settings',
requiresAuth: true,
requiredRoles: ['admin', 'manager'],
requiredRoles: ROLE_COMBINATIONS.ADMIN_ACCESS,
showInNavigation: true,
showInBreadcrumbs: true,
},
@@ -385,7 +387,7 @@ export const routesConfig: RouteConfig[] = [
title: 'Suscripción y Facturación',
icon: 'credit-card',
requiresAuth: true,
requiredRoles: ['admin', 'owner'],
requiredRoles: ROLE_COMBINATIONS.ADMIN_ACCESS,
showInNavigation: true,
showInBreadcrumbs: true,
},