307 lines
10 KiB
TypeScript
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)
|
|
};
|