Imporve the role based forntend protected roles
This commit is contained in:
@@ -54,6 +54,12 @@ export class TenantService {
|
|||||||
return apiClient.get<TenantAccessResponse>(`${this.baseUrl}/${tenantId}/access/${userId}`);
|
return apiClient.get<TenantAccessResponse>(`${this.baseUrl}/${tenantId}/access/${userId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getCurrentUserTenantAccess(tenantId: string): Promise<TenantAccessResponse> {
|
||||||
|
// This will use the current user from the auth token
|
||||||
|
// The backend endpoint handles extracting user_id from the token
|
||||||
|
return apiClient.get<TenantAccessResponse>(`${this.baseUrl}/${tenantId}/my-access`);
|
||||||
|
}
|
||||||
|
|
||||||
// Search & Discovery
|
// Search & Discovery
|
||||||
async searchTenants(params: TenantSearchParams): Promise<TenantResponse[]> {
|
async searchTenants(params: TenantSearchParams): Promise<TenantResponse[]> {
|
||||||
const queryParams = new URLSearchParams();
|
const queryParams = new URLSearchParams();
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
* Auth API Types - Mirror backend schemas
|
* Auth API Types - Mirror backend schemas
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import type { GlobalUserRole } from '../../types/roles';
|
||||||
|
|
||||||
export interface User {
|
export interface User {
|
||||||
id: string;
|
id: string;
|
||||||
email: string;
|
email: string;
|
||||||
@@ -14,7 +16,7 @@ export interface User {
|
|||||||
language?: string;
|
language?: string;
|
||||||
timezone?: string;
|
timezone?: string;
|
||||||
tenant_id?: string;
|
tenant_id?: string;
|
||||||
role?: string;
|
role?: GlobalUserRole;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UserRegistration {
|
export interface UserRegistration {
|
||||||
@@ -65,7 +67,7 @@ export interface UserResponse {
|
|||||||
language?: string;
|
language?: string;
|
||||||
timezone?: string;
|
timezone?: string;
|
||||||
tenant_id?: string;
|
tenant_id?: string;
|
||||||
role?: string;
|
role?: GlobalUserRole;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UserUpdate {
|
export interface UserUpdate {
|
||||||
@@ -79,7 +81,7 @@ export interface TokenVerificationResponse {
|
|||||||
valid: boolean;
|
valid: boolean;
|
||||||
user_id?: string;
|
user_id?: string;
|
||||||
email?: string;
|
email?: string;
|
||||||
role?: string;
|
role?: GlobalUserRole;
|
||||||
exp?: number;
|
exp?: number;
|
||||||
message?: string;
|
message?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
* Tenant API Types - Mirror backend schemas
|
* Tenant API Types - Mirror backend schemas
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import type { TenantRole } from '../../types/roles';
|
||||||
|
|
||||||
export interface BakeryRegistration {
|
export interface BakeryRegistration {
|
||||||
name: string;
|
name: string;
|
||||||
address: string;
|
address: string;
|
||||||
@@ -38,8 +40,10 @@ export interface TenantResponse {
|
|||||||
|
|
||||||
export interface TenantAccessResponse {
|
export interface TenantAccessResponse {
|
||||||
has_access: boolean;
|
has_access: boolean;
|
||||||
role?: string;
|
role?: TenantRole;
|
||||||
permissions?: string[];
|
permissions?: string[];
|
||||||
|
membership_id?: string;
|
||||||
|
joined_at?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TenantUpdate {
|
export interface TenantUpdate {
|
||||||
@@ -62,7 +66,7 @@ export interface TenantMemberResponse {
|
|||||||
id: string;
|
id: string;
|
||||||
tenant_id: string;
|
tenant_id: string;
|
||||||
user_id: string;
|
user_id: string;
|
||||||
role: string;
|
role: TenantRole;
|
||||||
is_active: boolean;
|
is_active: boolean;
|
||||||
joined_at: string;
|
joined_at: string;
|
||||||
user_email?: string;
|
user_email?: string;
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export const LoginForm: React.FC<LoginFormProps> = ({
|
|||||||
const { login } = useAuthActions();
|
const { login } = useAuthActions();
|
||||||
const isLoading = useAuthLoading();
|
const isLoading = useAuthLoading();
|
||||||
const error = useAuthError();
|
const error = useAuthError();
|
||||||
const { showToast } = useToast();
|
const { success, error: showError } = useToast();
|
||||||
|
|
||||||
// Auto-focus on email field when component mounts
|
// Auto-focus on email field when component mounts
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -76,17 +76,13 @@ export const LoginForm: React.FC<LoginFormProps> = ({
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await login(credentials.email, credentials.password);
|
await login(credentials.email, credentials.password);
|
||||||
showToast({
|
success('¡Bienvenido de vuelta a tu panadería!', {
|
||||||
type: 'success',
|
title: 'Sesión iniciada correctamente'
|
||||||
title: 'Sesión iniciada correctamente',
|
|
||||||
message: '¡Bienvenido de vuelta a tu panadería!'
|
|
||||||
});
|
});
|
||||||
onSuccess?.();
|
onSuccess?.();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
showToast({
|
showError(error || 'Email o contraseña incorrectos. Verifica tus credenciales.', {
|
||||||
type: 'error',
|
title: 'Error al iniciar sesión'
|
||||||
title: 'Error al iniciar sesión',
|
|
||||||
message: error || 'Email o contraseña incorrectos. Verifica tus credenciales.'
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import React, { useState, useCallback, forwardRef, useMemo } from 'react';
|
|||||||
import { clsx } from 'clsx';
|
import { clsx } from 'clsx';
|
||||||
import { useLocation, useNavigate } from 'react-router-dom';
|
import { useLocation, useNavigate } from 'react-router-dom';
|
||||||
import { useAuthUser, useIsAuthenticated } from '../../../stores';
|
import { useAuthUser, useIsAuthenticated } from '../../../stores';
|
||||||
|
import { useCurrentTenantAccess } from '../../../stores/tenant.store';
|
||||||
import { getNavigationRoutes, canAccessRoute, ROUTES } from '../../../router/routes.config';
|
import { getNavigationRoutes, canAccessRoute, ROUTES } from '../../../router/routes.config';
|
||||||
import { Button } from '../../ui';
|
import { Button } from '../../ui';
|
||||||
import { Badge } from '../../ui';
|
import { Badge } from '../../ui';
|
||||||
@@ -127,6 +128,7 @@ export const Sidebar = forwardRef<SidebarRef, SidebarProps>(({
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const user = useAuthUser();
|
const user = useAuthUser();
|
||||||
const isAuthenticated = useIsAuthenticated();
|
const isAuthenticated = useIsAuthenticated();
|
||||||
|
const currentTenantAccess = useCurrentTenantAccess();
|
||||||
|
|
||||||
const [expandedItems, setExpandedItems] = useState<Set<string>>(new Set());
|
const [expandedItems, setExpandedItems] = useState<Set<string>>(new Set());
|
||||||
const [hoveredItem, setHoveredItem] = useState<string | null>(null);
|
const [hoveredItem, setHoveredItem] = useState<string | null>(null);
|
||||||
@@ -160,8 +162,12 @@ export const Sidebar = forwardRef<SidebarRef, SidebarProps>(({
|
|||||||
...item, // Create a shallow copy to avoid mutation
|
...item, // Create a shallow copy to avoid mutation
|
||||||
children: item.children ? filterItemsByPermissions(item.children) : item.children
|
children: item.children ? filterItemsByPermissions(item.children) : item.children
|
||||||
})).filter(item => {
|
})).filter(item => {
|
||||||
const userRoles = user.role ? [user.role] : [];
|
// Combine global and tenant roles for comprehensive access control
|
||||||
const userPermissions: string[] = user?.permissions || [];
|
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 hasAccess = !item.requiredPermissions && !item.requiredRoles ||
|
const hasAccess = !item.requiredPermissions && !item.requiredRoles ||
|
||||||
canAccessRoute(
|
canAccessRoute(
|
||||||
@@ -171,8 +177,8 @@ export const Sidebar = forwardRef<SidebarRef, SidebarProps>(({
|
|||||||
requiredPermissions: item.requiredPermissions
|
requiredPermissions: item.requiredPermissions
|
||||||
} as any,
|
} as any,
|
||||||
isAuthenticated,
|
isAuthenticated,
|
||||||
userRoles,
|
allUserRoles,
|
||||||
userPermissions
|
tenantPermissions
|
||||||
);
|
);
|
||||||
|
|
||||||
return hasAccess;
|
return hasAccess;
|
||||||
@@ -180,7 +186,7 @@ export const Sidebar = forwardRef<SidebarRef, SidebarProps>(({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return filterItemsByPermissions(navigationItems);
|
return filterItemsByPermissions(navigationItems);
|
||||||
}, [navigationItems, isAuthenticated, user]);
|
}, [navigationItems, isAuthenticated, user, currentTenantAccess]);
|
||||||
|
|
||||||
// Handle item click
|
// Handle item click
|
||||||
const handleItemClick = useCallback((item: NavigationItem) => {
|
const handleItemClick = useCallback((item: NavigationItem) => {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Navigate, useLocation } from 'react-router-dom';
|
import { Navigate, useLocation } from 'react-router-dom';
|
||||||
import { useAuthUser, useIsAuthenticated, useAuthLoading } from '../stores';
|
import { useAuthUser, useIsAuthenticated, useAuthLoading } from '../stores';
|
||||||
|
import { useCurrentTenantAccess, useTenantPermissions } from '../stores/tenant.store';
|
||||||
import { RouteConfig, canAccessRoute, ROUTES } from './routes.config';
|
import { RouteConfig, canAccessRoute, ROUTES } from './routes.config';
|
||||||
|
|
||||||
interface ProtectedRouteProps {
|
interface ProtectedRouteProps {
|
||||||
@@ -126,6 +127,8 @@ export const ProtectedRoute: React.FC<ProtectedRouteProps> = ({
|
|||||||
const user = useAuthUser();
|
const user = useAuthUser();
|
||||||
const isAuthenticated = useIsAuthenticated();
|
const isAuthenticated = useIsAuthenticated();
|
||||||
const isLoading = useAuthLoading();
|
const isLoading = useAuthLoading();
|
||||||
|
const currentTenantAccess = useCurrentTenantAccess();
|
||||||
|
const { hasPermission } = useTenantPermissions();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|
||||||
// Note: Onboarding routes are now properly protected and require authentication
|
// 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
|
// Get user roles and permissions
|
||||||
const userRoles = user?.role ? [user.role] : [];
|
const globalUserRoles = user?.role ? [user.role] : [];
|
||||||
const userPermissions: string[] = [];
|
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
|
// Check if user can access this route
|
||||||
const canAccess = canAccessRoute(route, isAuthenticated, userRoles, userPermissions);
|
const canAccess = canAccessRoute(route, isAuthenticated, allUserRoles, tenantPermissions);
|
||||||
|
|
||||||
if (!canAccess) {
|
if (!canAccess) {
|
||||||
// Check if it's a permission issue or role issue
|
// Check if it's a permission issue or role issue
|
||||||
const hasRequiredRoles = !route.requiredRoles ||
|
const hasRequiredRoles = !route.requiredRoles ||
|
||||||
route.requiredRoles.some(role => userRoles.includes(role));
|
route.requiredRoles.some(role => allUserRoles.includes(role as string));
|
||||||
|
|
||||||
if (!hasRequiredRoles) {
|
if (!hasRequiredRoles) {
|
||||||
return <UnauthorizedPage />;
|
return <UnauthorizedPage />;
|
||||||
@@ -211,22 +219,29 @@ export const useRouteAccess = (route: RouteConfig) => {
|
|||||||
const user = useAuthUser();
|
const user = useAuthUser();
|
||||||
const isAuthenticated = useIsAuthenticated();
|
const isAuthenticated = useIsAuthenticated();
|
||||||
const isLoading = useAuthLoading();
|
const isLoading = useAuthLoading();
|
||||||
|
const currentTenantAccess = useCurrentTenantAccess();
|
||||||
|
|
||||||
const userRoles = user?.role ? [user.role] : [];
|
const globalUserRoles = user?.role ? [user.role as string] : [];
|
||||||
const userPermissions: 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 {
|
return {
|
||||||
canAccess,
|
canAccess,
|
||||||
isLoading,
|
isLoading,
|
||||||
isAuthenticated,
|
isAuthenticated,
|
||||||
userRoles,
|
globalUserRoles,
|
||||||
userPermissions,
|
tenantRoles,
|
||||||
|
allUserRoles,
|
||||||
|
tenantPermissions,
|
||||||
|
currentTenantAccess,
|
||||||
hasRequiredRoles: !route.requiredRoles ||
|
hasRequiredRoles: !route.requiredRoles ||
|
||||||
route.requiredRoles.some(role => userRoles.includes(role)),
|
route.requiredRoles.some(role => allUserRoles.includes(role as string)),
|
||||||
hasRequiredPermissions: !route.requiredPermissions ||
|
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 user = useAuthUser();
|
||||||
const isAuthenticated = useIsAuthenticated();
|
const isAuthenticated = useIsAuthenticated();
|
||||||
|
const currentTenantAccess = useCurrentTenantAccess();
|
||||||
|
|
||||||
if (!isAuthenticated || !user) {
|
if (!isAuthenticated || !user) {
|
||||||
return <>{fallback}</>;
|
return <>{fallback}</>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const userRoles = user.role ? [user.role] : [];
|
const globalUserRoles = user.role ? [user.role as string] : [];
|
||||||
const userPermissions: string[] = [];
|
const tenantRole = currentTenantAccess?.role;
|
||||||
|
const tenantRoles = tenantRole ? [tenantRole as string] : [];
|
||||||
|
const allUserRoles = [...globalUserRoles, ...tenantRoles];
|
||||||
|
const tenantPermissions = currentTenantAccess?.permissions || [];
|
||||||
|
|
||||||
// Check roles
|
// Check roles
|
||||||
let hasRoles = true;
|
let hasRoles = true;
|
||||||
if (requiredRoles.length > 0) {
|
if (requiredRoles.length > 0) {
|
||||||
if (requireAll) {
|
if (requireAll) {
|
||||||
hasRoles = requiredRoles.every(role => userRoles.includes(role));
|
hasRoles = requiredRoles.every(role => allUserRoles.includes(role as string));
|
||||||
} else {
|
} 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;
|
let hasPermissions = true;
|
||||||
if (requiredPermissions.length > 0) {
|
if (requiredPermissions.length > 0) {
|
||||||
if (requireAll) {
|
if (requireAll) {
|
||||||
hasPermissions = requiredPermissions.every(permission => userPermissions.includes(permission));
|
hasPermissions = requiredPermissions.every(permission => tenantPermissions.includes(permission));
|
||||||
} else {
|
} 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}</>;
|
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 }) => {
|
export const AdminRoute: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||||
return (
|
return (
|
||||||
<ConditionalRender
|
<ConditionalRender
|
||||||
requiredRoles={['admin', 'super_admin']}
|
requiredRoles={['admin', 'super_admin', 'owner']}
|
||||||
requireAll={false}
|
requireAll={false}
|
||||||
fallback={<UnauthorizedPage />}
|
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 }) => {
|
export const ManagerRoute: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||||
return (
|
return (
|
||||||
<ConditionalRender
|
<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}
|
requireAll={false}
|
||||||
fallback={<UnauthorizedPage />}
|
fallback={<UnauthorizedPage />}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
* Route configuration for the bakery management application
|
* Route configuration for the bakery management application
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { ROLE_COMBINATIONS } from '../types/roles';
|
||||||
|
|
||||||
export interface RouteConfig {
|
export interface RouteConfig {
|
||||||
path: string;
|
path: string;
|
||||||
name: string;
|
name: string;
|
||||||
@@ -286,7 +288,7 @@ export const routesConfig: RouteConfig[] = [
|
|||||||
title: 'Analytics',
|
title: 'Analytics',
|
||||||
icon: 'sales',
|
icon: 'sales',
|
||||||
requiresAuth: true,
|
requiresAuth: true,
|
||||||
requiredRoles: ['admin', 'manager'],
|
requiredRoles: ROLE_COMBINATIONS.MANAGEMENT_ACCESS,
|
||||||
showInNavigation: true,
|
showInNavigation: true,
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
@@ -296,7 +298,7 @@ export const routesConfig: RouteConfig[] = [
|
|||||||
title: 'Pronósticos',
|
title: 'Pronósticos',
|
||||||
icon: 'forecasting',
|
icon: 'forecasting',
|
||||||
requiresAuth: true,
|
requiresAuth: true,
|
||||||
requiredRoles: ['admin', 'manager'],
|
requiredRoles: ROLE_COMBINATIONS.MANAGEMENT_ACCESS,
|
||||||
showInNavigation: true,
|
showInNavigation: true,
|
||||||
showInBreadcrumbs: true,
|
showInBreadcrumbs: true,
|
||||||
},
|
},
|
||||||
@@ -307,7 +309,7 @@ export const routesConfig: RouteConfig[] = [
|
|||||||
title: 'Análisis de Ventas',
|
title: 'Análisis de Ventas',
|
||||||
icon: 'sales',
|
icon: 'sales',
|
||||||
requiresAuth: true,
|
requiresAuth: true,
|
||||||
requiredRoles: ['admin', 'manager'],
|
requiredRoles: ROLE_COMBINATIONS.MANAGEMENT_ACCESS,
|
||||||
showInNavigation: true,
|
showInNavigation: true,
|
||||||
showInBreadcrumbs: true,
|
showInBreadcrumbs: true,
|
||||||
},
|
},
|
||||||
@@ -318,7 +320,7 @@ export const routesConfig: RouteConfig[] = [
|
|||||||
title: 'Análisis de Rendimiento',
|
title: 'Análisis de Rendimiento',
|
||||||
icon: 'sales',
|
icon: 'sales',
|
||||||
requiresAuth: true,
|
requiresAuth: true,
|
||||||
requiredRoles: ['admin', 'manager'],
|
requiredRoles: ROLE_COMBINATIONS.MANAGEMENT_ACCESS,
|
||||||
showInNavigation: true,
|
showInNavigation: true,
|
||||||
showInBreadcrumbs: true,
|
showInBreadcrumbs: true,
|
||||||
},
|
},
|
||||||
@@ -329,7 +331,7 @@ export const routesConfig: RouteConfig[] = [
|
|||||||
title: 'Insights de IA',
|
title: 'Insights de IA',
|
||||||
icon: 'forecasting',
|
icon: 'forecasting',
|
||||||
requiresAuth: true,
|
requiresAuth: true,
|
||||||
requiredRoles: ['admin', 'manager'],
|
requiredRoles: ROLE_COMBINATIONS.MANAGEMENT_ACCESS,
|
||||||
showInNavigation: true,
|
showInNavigation: true,
|
||||||
showInBreadcrumbs: true,
|
showInBreadcrumbs: true,
|
||||||
},
|
},
|
||||||
@@ -363,7 +365,7 @@ export const routesConfig: RouteConfig[] = [
|
|||||||
title: 'Configuración de Panadería',
|
title: 'Configuración de Panadería',
|
||||||
icon: 'settings',
|
icon: 'settings',
|
||||||
requiresAuth: true,
|
requiresAuth: true,
|
||||||
requiredRoles: ['admin'],
|
requiredRoles: ROLE_COMBINATIONS.ADMIN_ACCESS,
|
||||||
showInNavigation: true,
|
showInNavigation: true,
|
||||||
showInBreadcrumbs: true,
|
showInBreadcrumbs: true,
|
||||||
},
|
},
|
||||||
@@ -374,7 +376,7 @@ export const routesConfig: RouteConfig[] = [
|
|||||||
title: 'Gestión de Equipo',
|
title: 'Gestión de Equipo',
|
||||||
icon: 'settings',
|
icon: 'settings',
|
||||||
requiresAuth: true,
|
requiresAuth: true,
|
||||||
requiredRoles: ['admin', 'manager'],
|
requiredRoles: ROLE_COMBINATIONS.ADMIN_ACCESS,
|
||||||
showInNavigation: true,
|
showInNavigation: true,
|
||||||
showInBreadcrumbs: true,
|
showInBreadcrumbs: true,
|
||||||
},
|
},
|
||||||
@@ -385,7 +387,7 @@ export const routesConfig: RouteConfig[] = [
|
|||||||
title: 'Suscripción y Facturación',
|
title: 'Suscripción y Facturación',
|
||||||
icon: 'credit-card',
|
icon: 'credit-card',
|
||||||
requiresAuth: true,
|
requiresAuth: true,
|
||||||
requiredRoles: ['admin', 'owner'],
|
requiredRoles: ROLE_COMBINATIONS.ADMIN_ACCESS,
|
||||||
showInNavigation: true,
|
showInNavigation: true,
|
||||||
showInBreadcrumbs: true,
|
showInBreadcrumbs: true,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { create } from 'zustand';
|
import { create } from 'zustand';
|
||||||
import { persist, createJSONStorage } from 'zustand/middleware';
|
import { persist, createJSONStorage } from 'zustand/middleware';
|
||||||
|
import { GLOBAL_USER_ROLES, type GlobalUserRole } from '../types/roles';
|
||||||
|
|
||||||
export interface User {
|
export interface User {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -13,7 +14,7 @@ export interface User {
|
|||||||
language?: string;
|
language?: string;
|
||||||
timezone?: string;
|
timezone?: string;
|
||||||
tenant_id?: string;
|
tenant_id?: string;
|
||||||
role?: string;
|
role?: GlobalUserRole;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AuthState {
|
export interface AuthState {
|
||||||
@@ -191,15 +192,22 @@ export const useAuthStore = create<AuthState>()(
|
|||||||
set({ isLoading: loading });
|
set({ isLoading: loading });
|
||||||
},
|
},
|
||||||
|
|
||||||
// Permission helpers - Simplified for backend compatibility
|
// Permission helpers - Global user permissions only
|
||||||
hasPermission: (_permission: string): boolean => {
|
hasPermission: (_permission: string): boolean => {
|
||||||
const { user } = get();
|
const { user } = get();
|
||||||
if (!user || !user.is_active) return false;
|
if (!user || !user.is_active) return false;
|
||||||
|
|
||||||
// Admin has all permissions
|
// Super admin and admin have all global permissions
|
||||||
if (user.role === 'admin') return true;
|
if (user.role === GLOBAL_USER_ROLES.SUPER_ADMIN || user.role === GLOBAL_USER_ROLES.ADMIN) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Basic role-based permissions
|
// Manager has limited permissions
|
||||||
|
if (user.role === GLOBAL_USER_ROLES.MANAGER) {
|
||||||
|
return ['user_management', 'system_settings'].includes(_permission);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Regular users have basic permissions
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -212,14 +220,15 @@ export const useAuthStore = create<AuthState>()(
|
|||||||
const { user } = get();
|
const { user } = get();
|
||||||
if (!user || !user.is_active) return false;
|
if (!user || !user.is_active) return false;
|
||||||
|
|
||||||
// Role-based access control
|
// Global role-based access control (system-wide)
|
||||||
switch (user.role) {
|
switch (user.role) {
|
||||||
case 'admin':
|
case GLOBAL_USER_ROLES.SUPER_ADMIN:
|
||||||
|
case GLOBAL_USER_ROLES.ADMIN:
|
||||||
return true;
|
return true;
|
||||||
case 'manager':
|
case GLOBAL_USER_ROLES.MANAGER:
|
||||||
return ['inventory', 'production', 'sales', 'reports'].includes(resource);
|
return ['users', 'system'].includes(resource);
|
||||||
case 'user':
|
case GLOBAL_USER_ROLES.USER:
|
||||||
return ['inventory', 'sales'].includes(resource) && action === 'read';
|
return action === 'read';
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import { create } from 'zustand';
|
import { create } from 'zustand';
|
||||||
import { persist, createJSONStorage } from 'zustand/middleware';
|
import { persist, createJSONStorage } from 'zustand/middleware';
|
||||||
import { tenantService, type TenantResponse } from '../api';
|
import { tenantService, type TenantResponse, type TenantAccessResponse } from '../api';
|
||||||
import { useAuthUser } from './auth.store';
|
import { useAuthUser } from './auth.store';
|
||||||
|
import { TENANT_ROLES, GLOBAL_USER_ROLES } from '../types/roles';
|
||||||
|
|
||||||
export interface TenantState {
|
export interface TenantState {
|
||||||
// State
|
// State
|
||||||
currentTenant: TenantResponse | null;
|
currentTenant: TenantResponse | null;
|
||||||
availableTenants: TenantResponse[] | null;
|
availableTenants: TenantResponse[] | null;
|
||||||
|
currentTenantAccess: TenantAccessResponse | null;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
error: string | null;
|
error: string | null;
|
||||||
|
|
||||||
@@ -14,6 +16,7 @@ export interface TenantState {
|
|||||||
setCurrentTenant: (tenant: TenantResponse) => void;
|
setCurrentTenant: (tenant: TenantResponse) => void;
|
||||||
switchTenant: (tenantId: string) => Promise<boolean>;
|
switchTenant: (tenantId: string) => Promise<boolean>;
|
||||||
loadUserTenants: () => Promise<void>;
|
loadUserTenants: () => Promise<void>;
|
||||||
|
loadCurrentTenantAccess: () => Promise<void>;
|
||||||
clearTenants: () => void;
|
clearTenants: () => void;
|
||||||
clearError: () => void;
|
clearError: () => void;
|
||||||
setLoading: (loading: boolean) => void;
|
setLoading: (loading: boolean) => void;
|
||||||
@@ -29,14 +32,17 @@ export const useTenantStore = create<TenantState>()(
|
|||||||
// Initial state
|
// Initial state
|
||||||
currentTenant: null,
|
currentTenant: null,
|
||||||
availableTenants: null,
|
availableTenants: null,
|
||||||
|
currentTenantAccess: null,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
error: null,
|
error: null,
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
setCurrentTenant: (tenant: TenantResponse) => {
|
setCurrentTenant: (tenant: TenantResponse) => {
|
||||||
set({ currentTenant: tenant });
|
set({ currentTenant: tenant, currentTenantAccess: null });
|
||||||
// Update API client with new tenant ID
|
// Update API client with new tenant ID
|
||||||
tenantService.setCurrentTenant(tenant);
|
tenantService.setCurrentTenant(tenant);
|
||||||
|
// Load tenant access info
|
||||||
|
get().loadCurrentTenantAccess();
|
||||||
},
|
},
|
||||||
|
|
||||||
switchTenant: async (tenantId: string): Promise<boolean> => {
|
switchTenant: async (tenantId: string): Promise<boolean> => {
|
||||||
@@ -116,10 +122,25 @@ export const useTenantStore = create<TenantState>()(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
loadCurrentTenantAccess: async (): Promise<void> => {
|
||||||
|
try {
|
||||||
|
const { currentTenant } = get();
|
||||||
|
if (!currentTenant) return;
|
||||||
|
|
||||||
|
const accessInfo = await tenantService.getCurrentUserTenantAccess(currentTenant.id);
|
||||||
|
set({ currentTenantAccess: accessInfo });
|
||||||
|
} catch (error) {
|
||||||
|
// Don't set error state for access loading failures - just log
|
||||||
|
console.warn('Failed to load tenant access:', error);
|
||||||
|
set({ currentTenantAccess: null });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
clearTenants: () => {
|
clearTenants: () => {
|
||||||
set({
|
set({
|
||||||
currentTenant: null,
|
currentTenant: null,
|
||||||
availableTenants: null,
|
availableTenants: null,
|
||||||
|
currentTenantAccess: null,
|
||||||
error: null,
|
error: null,
|
||||||
});
|
});
|
||||||
tenantService.clearCurrentTenant();
|
tenantService.clearCurrentTenant();
|
||||||
@@ -135,33 +156,34 @@ export const useTenantStore = create<TenantState>()(
|
|||||||
|
|
||||||
// Permission helpers (migrated from BakeryContext)
|
// Permission helpers (migrated from BakeryContext)
|
||||||
hasPermission: (permission: string): boolean => {
|
hasPermission: (permission: string): boolean => {
|
||||||
const { currentTenant } = get();
|
const { currentTenant, currentTenantAccess } = get();
|
||||||
if (!currentTenant) return false;
|
if (!currentTenant || !currentTenantAccess || !currentTenantAccess.has_access) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Get user to determine role within this tenant
|
// Check if user has specific permission in their tenant permissions array
|
||||||
const authState = JSON.parse(localStorage.getItem('auth-storage') || '{}')?.state;
|
if (currentTenantAccess.permissions?.includes(permission)) {
|
||||||
const user = authState?.user;
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Admin role has all permissions
|
// Check if user has broader permissions that include this one
|
||||||
if (user?.role === 'admin') return true;
|
if (currentTenantAccess.permissions?.includes('*') ||
|
||||||
|
currentTenantAccess.permissions?.includes('admin')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Implement proper tenant-based permissions
|
// Role-based fallback for common permissions based on tenant role
|
||||||
// For now, use basic role-based permissions
|
const tenantRole = currentTenantAccess.role;
|
||||||
switch (user?.role) {
|
switch (tenantRole) {
|
||||||
case 'admin':
|
case TENANT_ROLES.OWNER:
|
||||||
|
case TENANT_ROLES.ADMIN:
|
||||||
return true;
|
return true;
|
||||||
case 'manager':
|
case TENANT_ROLES.MEMBER:
|
||||||
return ['inventory', 'production', 'sales', 'reports'].some(resource =>
|
// Members can read and write but not delete or manage users
|
||||||
permission.startsWith(resource)
|
return !permission.includes('delete') && !permission.includes('admin');
|
||||||
);
|
case TENANT_ROLES.VIEWER:
|
||||||
case 'baker':
|
// Viewers can only read
|
||||||
return ['production', 'inventory'].some(resource =>
|
return permission.includes('read') || permission.includes('view');
|
||||||
permission.startsWith(resource)
|
|
||||||
) && !permission.includes(':delete');
|
|
||||||
case 'staff':
|
|
||||||
return ['inventory', 'sales'].some(resource =>
|
|
||||||
permission.startsWith(resource)
|
|
||||||
) && permission.includes(':read');
|
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -185,6 +207,7 @@ export const useTenantStore = create<TenantState>()(
|
|||||||
partialize: (state) => ({
|
partialize: (state) => ({
|
||||||
currentTenant: state.currentTenant,
|
currentTenant: state.currentTenant,
|
||||||
availableTenants: state.availableTenants,
|
availableTenants: state.availableTenants,
|
||||||
|
currentTenantAccess: state.currentTenantAccess,
|
||||||
}),
|
}),
|
||||||
onRehydrateStorage: () => (state) => {
|
onRehydrateStorage: () => (state) => {
|
||||||
// Initialize API client with stored tenant when store rehydrates
|
// Initialize API client with stored tenant when store rehydrates
|
||||||
@@ -201,6 +224,7 @@ export const useTenantStore = create<TenantState>()(
|
|||||||
// Selectors for common use cases
|
// Selectors for common use cases
|
||||||
export const useCurrentTenant = () => useTenantStore((state) => state.currentTenant);
|
export const useCurrentTenant = () => useTenantStore((state) => state.currentTenant);
|
||||||
export const useAvailableTenants = () => useTenantStore((state) => state.availableTenants);
|
export const useAvailableTenants = () => useTenantStore((state) => state.availableTenants);
|
||||||
|
export const useCurrentTenantAccess = () => useTenantStore((state) => state.currentTenantAccess);
|
||||||
export const useTenantLoading = () => useTenantStore((state) => state.isLoading);
|
export const useTenantLoading = () => useTenantStore((state) => state.isLoading);
|
||||||
export const useTenantError = () => useTenantStore((state) => state.error);
|
export const useTenantError = () => useTenantStore((state) => state.error);
|
||||||
|
|
||||||
@@ -209,6 +233,7 @@ export const useTenantActions = () => useTenantStore((state) => ({
|
|||||||
setCurrentTenant: state.setCurrentTenant,
|
setCurrentTenant: state.setCurrentTenant,
|
||||||
switchTenant: state.switchTenant,
|
switchTenant: state.switchTenant,
|
||||||
loadUserTenants: state.loadUserTenants,
|
loadUserTenants: state.loadUserTenants,
|
||||||
|
loadCurrentTenantAccess: state.loadCurrentTenantAccess,
|
||||||
clearTenants: state.clearTenants,
|
clearTenants: state.clearTenants,
|
||||||
clearError: state.clearError,
|
clearError: state.clearError,
|
||||||
setLoading: state.setLoading,
|
setLoading: state.setLoading,
|
||||||
@@ -224,6 +249,7 @@ export const useTenantPermissions = () => useTenantStore((state) => ({
|
|||||||
export const useTenant = () => {
|
export const useTenant = () => {
|
||||||
const currentTenant = useCurrentTenant();
|
const currentTenant = useCurrentTenant();
|
||||||
const availableTenants = useAvailableTenants();
|
const availableTenants = useAvailableTenants();
|
||||||
|
const currentTenantAccess = useCurrentTenantAccess();
|
||||||
const isLoading = useTenantLoading();
|
const isLoading = useTenantLoading();
|
||||||
const error = useTenantError();
|
const error = useTenantError();
|
||||||
const actions = useTenantActions();
|
const actions = useTenantActions();
|
||||||
@@ -232,6 +258,7 @@ export const useTenant = () => {
|
|||||||
return {
|
return {
|
||||||
currentTenant,
|
currentTenant,
|
||||||
availableTenants,
|
availableTenants,
|
||||||
|
currentTenantAccess,
|
||||||
isLoading,
|
isLoading,
|
||||||
error,
|
error,
|
||||||
...actions,
|
...actions,
|
||||||
|
|||||||
83
frontend/src/types/roles.ts
Normal file
83
frontend/src/types/roles.ts
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
/**
|
||||||
|
* Role Types - Must match backend role definitions exactly
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Global User Roles (Auth Service)
|
||||||
|
export const GLOBAL_USER_ROLES = {
|
||||||
|
USER: 'user',
|
||||||
|
ADMIN: 'admin',
|
||||||
|
MANAGER: 'manager',
|
||||||
|
SUPER_ADMIN: 'super_admin',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
// Tenant-Specific Roles (Tenant Service)
|
||||||
|
export const TENANT_ROLES = {
|
||||||
|
OWNER: 'owner',
|
||||||
|
ADMIN: 'admin',
|
||||||
|
MEMBER: 'member',
|
||||||
|
VIEWER: 'viewer',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
// Combined role types
|
||||||
|
export type GlobalUserRole = typeof GLOBAL_USER_ROLES[keyof typeof GLOBAL_USER_ROLES];
|
||||||
|
export type TenantRole = typeof TENANT_ROLES[keyof typeof TENANT_ROLES];
|
||||||
|
export type Role = GlobalUserRole | TenantRole;
|
||||||
|
|
||||||
|
// Role hierarchy for permission checking
|
||||||
|
export const ROLE_HIERARCHY = {
|
||||||
|
// Global roles (highest to lowest)
|
||||||
|
global: [
|
||||||
|
GLOBAL_USER_ROLES.SUPER_ADMIN,
|
||||||
|
GLOBAL_USER_ROLES.ADMIN,
|
||||||
|
GLOBAL_USER_ROLES.MANAGER,
|
||||||
|
GLOBAL_USER_ROLES.USER,
|
||||||
|
],
|
||||||
|
// Tenant roles (highest to lowest)
|
||||||
|
tenant: [
|
||||||
|
TENANT_ROLES.OWNER,
|
||||||
|
TENANT_ROLES.ADMIN,
|
||||||
|
TENANT_ROLES.MEMBER,
|
||||||
|
TENANT_ROLES.VIEWER,
|
||||||
|
],
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
// Permission helper functions
|
||||||
|
export const hasGlobalRole = (userRole: string, requiredRole: GlobalUserRole): boolean => {
|
||||||
|
const userIndex = ROLE_HIERARCHY.global.indexOf(userRole as GlobalUserRole);
|
||||||
|
const requiredIndex = ROLE_HIERARCHY.global.indexOf(requiredRole);
|
||||||
|
return userIndex !== -1 && requiredIndex !== -1 && userIndex <= requiredIndex;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const hasTenantRole = (userRole: string, requiredRole: TenantRole): boolean => {
|
||||||
|
const userIndex = ROLE_HIERARCHY.tenant.indexOf(userRole as TenantRole);
|
||||||
|
const requiredIndex = ROLE_HIERARCHY.tenant.indexOf(requiredRole);
|
||||||
|
return userIndex !== -1 && requiredIndex !== -1 && userIndex <= requiredIndex;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const hasAnyRole = (userRoles: string[], requiredRoles: Role[]): boolean => {
|
||||||
|
return requiredRoles.some(requiredRole => userRoles.includes(requiredRole));
|
||||||
|
};
|
||||||
|
|
||||||
|
// Common role combinations for easy reuse
|
||||||
|
export const ROLE_COMBINATIONS = {
|
||||||
|
// Administrative access (global admin or tenant owner)
|
||||||
|
ADMIN_ACCESS: [GLOBAL_USER_ROLES.ADMIN, GLOBAL_USER_ROLES.SUPER_ADMIN, TENANT_ROLES.OWNER],
|
||||||
|
|
||||||
|
// Management access (admin + manager + tenant admin)
|
||||||
|
MANAGEMENT_ACCESS: [
|
||||||
|
GLOBAL_USER_ROLES.ADMIN,
|
||||||
|
GLOBAL_USER_ROLES.SUPER_ADMIN,
|
||||||
|
GLOBAL_USER_ROLES.MANAGER,
|
||||||
|
TENANT_ROLES.OWNER,
|
||||||
|
TENANT_ROLES.ADMIN,
|
||||||
|
],
|
||||||
|
|
||||||
|
// Owner-only access (super admin or tenant owner)
|
||||||
|
OWNER_ACCESS: [GLOBAL_USER_ROLES.SUPER_ADMIN, TENANT_ROLES.OWNER],
|
||||||
|
|
||||||
|
// Basic access (any authenticated user with any role)
|
||||||
|
BASIC_ACCESS: [
|
||||||
|
...Object.values(GLOBAL_USER_ROLES),
|
||||||
|
...Object.values(TENANT_ROLES),
|
||||||
|
],
|
||||||
|
} as const;
|
||||||
@@ -132,7 +132,7 @@ class SecurityManager:
|
|||||||
if "role" in user_data:
|
if "role" in user_data:
|
||||||
payload["role"] = user_data["role"]
|
payload["role"] = user_data["role"]
|
||||||
else:
|
else:
|
||||||
payload["role"] = "user" # Default role if not specified
|
payload["role"] = "admin" # Default role if not specified
|
||||||
|
|
||||||
logger.debug(f"Creating access token with payload keys: {list(payload.keys())}")
|
logger.debug(f"Creating access token with payload keys: {list(payload.keys())}")
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ class UserRegistration(BaseModel):
|
|||||||
password: str = Field(..., min_length=8, max_length=128)
|
password: str = Field(..., min_length=8, max_length=128)
|
||||||
full_name: str = Field(..., min_length=1, max_length=255)
|
full_name: str = Field(..., min_length=1, max_length=255)
|
||||||
tenant_name: Optional[str] = Field(None, max_length=255)
|
tenant_name: Optional[str] = Field(None, max_length=255)
|
||||||
role: Optional[str] = Field("user", pattern=r'^(user|admin|manager)$')
|
role: Optional[str] = Field("admin", pattern=r'^(user|admin|manager|super_admin)$')
|
||||||
|
|
||||||
class UserLogin(BaseModel):
|
class UserLogin(BaseModel):
|
||||||
"""User login request"""
|
"""User login request"""
|
||||||
@@ -56,7 +56,7 @@ class UserData(BaseModel):
|
|||||||
is_verified: bool
|
is_verified: bool
|
||||||
created_at: str # ISO format datetime string
|
created_at: str # ISO format datetime string
|
||||||
tenant_id: Optional[str] = None
|
tenant_id: Optional[str] = None
|
||||||
role: Optional[str] = "user"
|
role: Optional[str] = "admin"
|
||||||
|
|
||||||
class TokenResponse(BaseModel):
|
class TokenResponse(BaseModel):
|
||||||
"""
|
"""
|
||||||
@@ -101,7 +101,7 @@ class UserResponse(BaseModel):
|
|||||||
language: Optional[str] = None # ✅ Added missing field
|
language: Optional[str] = None # ✅ Added missing field
|
||||||
timezone: Optional[str] = None # ✅ Added missing field
|
timezone: Optional[str] = None # ✅ Added missing field
|
||||||
tenant_id: Optional[str] = None
|
tenant_id: Optional[str] = None
|
||||||
role: Optional[str] = "user"
|
role: Optional[str] = "admin"
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
from_attributes = True # ✅ Enable ORM mode for SQLAlchemy objects
|
from_attributes = True # ✅ Enable ORM mode for SQLAlchemy objects
|
||||||
@@ -189,7 +189,7 @@ class UserContext(BaseModel):
|
|||||||
user_id: str
|
user_id: str
|
||||||
email: str
|
email: str
|
||||||
tenant_id: Optional[str] = None
|
tenant_id: Optional[str] = None
|
||||||
roles: list[str] = ["user"]
|
roles: list[str] = ["admin"]
|
||||||
is_verified: bool = False
|
is_verified: bool = False
|
||||||
|
|
||||||
class TokenClaims(BaseModel):
|
class TokenClaims(BaseModel):
|
||||||
|
|||||||
@@ -55,7 +55,9 @@ class EnhancedAuthService:
|
|||||||
raise ValueError("Password does not meet security requirements")
|
raise ValueError("Password does not meet security requirements")
|
||||||
|
|
||||||
# Create user data
|
# Create user data
|
||||||
user_role = user_data.role if user_data.role else "user"
|
# Default to admin role for first-time registrations during onboarding flow
|
||||||
|
# Users creating their own bakery should have admin privileges
|
||||||
|
user_role = user_data.role if user_data.role else "admin"
|
||||||
hashed_password = SecurityManager.hash_password(user_data.password)
|
hashed_password = SecurityManager.hash_password(user_data.password)
|
||||||
|
|
||||||
create_data = {
|
create_data = {
|
||||||
|
|||||||
@@ -413,7 +413,7 @@ class EnhancedUserService:
|
|||||||
user_repo = UserRepository(User, session)
|
user_repo = UserRepository(User, session)
|
||||||
|
|
||||||
# Validate role
|
# Validate role
|
||||||
valid_roles = ["user", "admin", "super_admin"]
|
valid_roles = ["user", "admin", "manager", "super_admin"]
|
||||||
if new_role not in valid_roles:
|
if new_role not in valid_roles:
|
||||||
raise ValidationError(f"Invalid role. Must be one of: {valid_roles}")
|
raise ValidationError(f"Invalid role. Must be one of: {valid_roles}")
|
||||||
|
|
||||||
|
|||||||
@@ -67,6 +67,32 @@ async def register_bakery_enhanced(
|
|||||||
detail="Bakery registration failed"
|
detail="Bakery registration failed"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@router.get("/tenants/{tenant_id}/my-access", response_model=TenantAccessResponse)
|
||||||
|
async def get_current_user_tenant_access(
|
||||||
|
tenant_id: UUID = Path(..., description="Tenant ID"),
|
||||||
|
current_user: Dict[str, Any] = Depends(get_current_user_dep)
|
||||||
|
):
|
||||||
|
"""Get current user's access to tenant with role and permissions"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Create tenant service directly
|
||||||
|
from app.core.config import settings
|
||||||
|
database_manager = create_database_manager(settings.DATABASE_URL, "tenant-service")
|
||||||
|
tenant_service = EnhancedTenantService(database_manager)
|
||||||
|
|
||||||
|
access_info = await tenant_service.verify_user_access(current_user["user_id"], str(tenant_id))
|
||||||
|
return access_info
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("Current user access verification failed",
|
||||||
|
user_id=current_user["user_id"],
|
||||||
|
tenant_id=str(tenant_id),
|
||||||
|
error=str(e))
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||||
|
detail="Access verification failed"
|
||||||
|
)
|
||||||
|
|
||||||
@router.get("/tenants/{tenant_id}/access/{user_id}", response_model=TenantAccessResponse)
|
@router.get("/tenants/{tenant_id}/access/{user_id}", response_model=TenantAccessResponse)
|
||||||
async def verify_tenant_access_enhanced(
|
async def verify_tenant_access_enhanced(
|
||||||
tenant_id: UUID = Path(..., description="Tenant ID"),
|
tenant_id: UUID = Path(..., description="Tenant ID"),
|
||||||
|
|||||||
Reference in New Issue
Block a user