import { PrivilegeChecker } from '../core/PageManager/pageInterface'; import { getUserDataCache } from './userCache'; import type { PermissionContext } from '../hooks/usePermissions'; /** * Privilege Checkers * * Read-only access to user data for privilege checking. * Does not manage user data storage - that's handled by authentication hooks. * * Now supports both client-side checks (roles, localStorage) and backend RBAC integration. */ // Function to get current user privilege from sessionStorage cache const getCurrentUserPrivilege = (): string | null => { const userData = getUserDataCache(); return userData?.privilege || null; }; // Generic privilege checker for localStorage-based data with expiration export const createLocalStoragePrivilegeChecker = ( dataKey: string, timestampKey: string, expirationHours: number = 24 ): PrivilegeChecker => { return (): boolean => { try { const savedData = localStorage.getItem(dataKey); const timestamp = localStorage.getItem(timestampKey); if (savedData && timestamp) { const dataTime = parseInt(timestamp); const now = Date.now(); const hoursDiff = (now - dataTime) / (1000 * 60 * 60); return hoursDiff < expirationHours; } return false; } catch (error) { console.error(`Error checking privilege for ${dataKey}:`, error); return false; } }; }; // Generic privilege checker for user roles/permissions export const createRolePrivilegeChecker = ( requiredRoles: string[], getUserRoles: () => string[] | Promise ): PrivilegeChecker => { return async (): Promise => { try { const userRoles = await getUserRoles(); const hasRequiredRole = requiredRoles.some(role => userRoles.includes(role)); return hasRequiredRole; } catch (error) { console.error('Error checking role privilege:', error); return false; } }; }; // Generic privilege checker for feature flags export const createFeatureFlagChecker = ( featureFlag: string, getFeatureFlags: () => Record | Promise> ): PrivilegeChecker => { return async (): Promise => { try { const flags = await getFeatureFlags(); return flags[featureFlag] === true; } catch (error) { console.error(`Error checking feature flag ${featureFlag}:`, error); return false; } }; }; // Generic privilege checker for authentication status export const createAuthPrivilegeChecker = ( isAuthenticated: () => boolean | Promise ): PrivilegeChecker => { return async (): Promise => { try { return await isAuthenticated(); } catch (error) { console.error('Error checking authentication status:', error); return false; } }; }; // Helper function to create custom privilege checkers export const createCustomPrivilegeChecker = ( checkFunction: () => boolean | Promise ): PrivilegeChecker => { return checkFunction; }; /** * Create a privilege checker that uses backend RBAC permissions * This integrates privilegeCheckers with usePermissions for backend-controlled access * * @param canViewFunction - The canView function from usePermissions hook * @param context - Permission context ('UI', 'DATA', or 'RESOURCE') * @param item - The item/resource path to check permissions for * @returns A PrivilegeChecker function that checks backend RBAC permissions */ export const createRBACPrivilegeChecker = ( canViewFunction: (context: PermissionContext, item: string) => Promise, context: PermissionContext, item: string ): PrivilegeChecker => { return async (): Promise => { try { return await canViewFunction(context, item); } catch (error) { console.error(`Error checking RBAC privilege for ${context}:${item}:`, error); return false; } }; }; /** * Create a privilege checker that combines RBAC with client-side role checks * First checks backend RBAC, then falls back to client-side role check if RBAC allows * * @param canViewFunction - The canView function from usePermissions hook * @param context - Permission context ('UI', 'DATA', or 'RESOURCE') * @param item - The item/resource path to check permissions for * @param requiredRoles - Fallback client-side roles to check if RBAC passes * @returns A PrivilegeChecker function that checks both RBAC and roles */ export const createCombinedPrivilegeChecker = ( canViewFunction: (context: PermissionContext, item: string) => Promise, context: PermissionContext, item: string, requiredRoles: string[] ): PrivilegeChecker => { return async (): Promise => { try { // First check backend RBAC const hasRBACAccess = await canViewFunction(context, item); if (!hasRBACAccess) { return false; } // If RBAC allows, also check client-side roles as additional validation const userPrivilege = getCurrentUserPrivilege(); if (userPrivilege && requiredRoles.includes(userPrivilege)) { return true; } // If no role match, still allow if RBAC said yes (backend is source of truth) return hasRBACAccess; } catch (error) { console.error(`Error checking combined privilege for ${context}:${item}:`, error); return false; } }; }; /** * Helper to create RBAC-based privilege checkers for page data * These checkers will use backend RBAC permissions via usePermissions * * Usage in page data: * import { createRBACPageChecker } from '@/utils/privilegeCheckers'; * * // In PageManager, initialize with canView function: * const rbacCheckers = createRBACPageCheckers(canView); * * // In page data: * privilegeChecker: rbacCheckers.forPage('administration/workflows') */ export const createRBACPageCheckers = ( canViewFunction: (context: PermissionContext, item: string) => Promise ) => { return { /** * Create a privilege checker for a specific page path * Checks backend RBAC permissions for UI context */ forPage: (pagePath: string): PrivilegeChecker => { return createRBACPrivilegeChecker(canViewFunction, 'UI', pagePath); }, /** * Create a privilege checker that combines RBAC with role requirements * First checks backend RBAC, then validates user role */ forPageWithRole: ( pagePath: string, requiredRoles: string[] ): PrivilegeChecker => { return createCombinedPrivilegeChecker(canViewFunction, 'UI', pagePath, requiredRoles); }, /** * Create a privilege checker for a data resource * Checks backend RBAC permissions for DATA context */ forData: (resourcePath: string): PrivilegeChecker => { return createRBACPrivilegeChecker(canViewFunction, 'DATA', resourcePath); }, /** * Create a privilege checker for a UI resource * Checks backend RBAC permissions for UI context */ forUI: (resourcePath: string): PrivilegeChecker => { return createRBACPrivilegeChecker(canViewFunction, 'UI', resourcePath); } }; }; // Predefined privilege checkers for common use cases export const privilegeCheckers = { // Speech signup checker (existing functionality) speechSignup: createLocalStoragePrivilegeChecker( 'speechSignUpData', 'speechSignUpTimestamp', 24 ), // Admin role checker - for admin and sysadmin users adminRole: createRolePrivilegeChecker( ['admin', 'sysadmin'], () => { const userPrivilege = getCurrentUserPrivilege(); return Promise.resolve(userPrivilege ? [userPrivilege] : []); } ), // Sysadmin role checker - for sysadmin only sysadminRole: createRolePrivilegeChecker( ['sysadmin'], () => { const userPrivilege = getCurrentUserPrivilege(); return Promise.resolve(userPrivilege ? [userPrivilege] : []); } ), // Premium user checker premiumUser: createLocalStoragePrivilegeChecker( 'premiumUserData', 'premiumUserTimestamp', 24 * 30 // 30 days ), // Feature flag checker betaFeatures: createFeatureFlagChecker( 'betaFeatures', () => { const flags = JSON.parse(localStorage.getItem('featureFlags') || '{}'); return Promise.resolve(flags); } ), // Authentication checker authenticated: createAuthPrivilegeChecker( () => { const token = localStorage.getItem('authToken'); return Promise.resolve(!!token); } ), // User role checker - for user, admin, and sysadmin access userRole: createRolePrivilegeChecker( ['user', 'admin', 'sysadmin'], () => { const userPrivilege = getCurrentUserPrivilege(); return Promise.resolve(userPrivilege ? [userPrivilege] : []); } ), // Viewer role checker - for viewer, user, admin, and sysadmin access (all levels) viewerRole: createRolePrivilegeChecker( ['viewer', 'user', 'admin', 'sysadmin'], () => { const userPrivilege = getCurrentUserPrivilege(); return Promise.resolve(userPrivilege ? [userPrivilege] : []); } ), // Subscription checker - for paid features hasSubscription: createLocalStoragePrivilegeChecker( 'subscriptionData', 'subscriptionTimestamp', 24 * 7 // 7 days ), // Mandate checker - for users who have submitted their mandate hasMandate: createLocalStoragePrivilegeChecker( 'mandateData', 'mandateTimestamp', 24 * 30 // 30 days ), // Always allow access (for public pages) alwaysAllow: createCustomPrivilegeChecker(() => true), // Never allow access (for disabled features) neverAllow: createCustomPrivilegeChecker(() => false) };