frontend_nyla/src/utils/privilegeCheckers.ts

307 lines
10 KiB
TypeScript

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<string[]>
): PrivilegeChecker => {
return async (): Promise<boolean> => {
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<string, boolean> | Promise<Record<string, boolean>>
): PrivilegeChecker => {
return async (): Promise<boolean> => {
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<boolean>
): PrivilegeChecker => {
return async (): Promise<boolean> => {
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<boolean>
): 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<boolean>,
context: PermissionContext,
item: string
): PrivilegeChecker => {
return async (): Promise<boolean> => {
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<boolean>,
context: PermissionContext,
item: string,
requiredRoles: string[]
): PrivilegeChecker => {
return async (): Promise<boolean> => {
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<boolean>
) => {
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)
};