feat: implemented priviledge checker into real estate pages
This commit is contained in:
parent
239fd328bc
commit
be3844f33e
9 changed files with 126 additions and 53 deletions
|
|
@ -11,11 +11,10 @@ export interface User {
|
|||
fullName: string;
|
||||
language: string;
|
||||
enabled: boolean;
|
||||
privilege?: string; // Deprecated - use roleLabels instead
|
||||
roleLabels?: string[]; // Array of role labels from backend (e.g., ["user"])
|
||||
authenticationAuthority: string;
|
||||
mandateId: string;
|
||||
[key: string]: any; // Allow additional properties
|
||||
[key: string]: any; // Allow additional properties (may include deprecated 'privilege' from backend)
|
||||
}
|
||||
|
||||
export type UserUpdateData = Partial<Omit<User, 'id' | 'mandateId'>>;
|
||||
|
|
@ -92,7 +91,6 @@ export async function fetchCurrentUser(
|
|||
hasData: !!response,
|
||||
username: response?.username,
|
||||
roleLabels: response?.roleLabels,
|
||||
privilege: response?.privilege,
|
||||
allKeys: response ? Object.keys(response) : [],
|
||||
fullResponse: response
|
||||
});
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ const SidebarUser: React.FC<SidebarUserProps> = ({ isMinimized = false }) => {
|
|||
fullName: cached.fullName || cached.username.split('@')[0] || cached.username,
|
||||
language: cached.language || 'de', // Default language
|
||||
enabled: cached.enabled ?? true, // Assume enabled if logged in
|
||||
privilege: cached.privilege || 'user',
|
||||
roleLabels: cached.roleLabels || [],
|
||||
authenticationAuthority: cached.authenticationAuthority || 'local',
|
||||
mandateId: cached.mandateId || ''
|
||||
};
|
||||
|
|
@ -97,7 +97,7 @@ const SidebarUser: React.FC<SidebarUserProps> = ({ isMinimized = false }) => {
|
|||
fullName: cached.fullName || cached.username.split('@')[0] || cached.username,
|
||||
language: cached.language || 'de',
|
||||
enabled: cached.enabled ?? true,
|
||||
privilege: cached.privilege || 'user',
|
||||
roleLabels: cached.roleLabels || [],
|
||||
authenticationAuthority: cached.authenticationAuthority || 'local',
|
||||
mandateId: cached.mandateId || ''
|
||||
};
|
||||
|
|
|
|||
|
|
@ -68,11 +68,32 @@ const PageManager: React.FC<PageManagerProps> = ({
|
|||
return;
|
||||
}
|
||||
|
||||
// Check page access
|
||||
// Check page access (RBAC + privilegeChecker)
|
||||
console.log('🔍 PageManager: Checking access before rendering:', currentPath);
|
||||
checkPageAccess(pageData).then(hasAccess => {
|
||||
|
||||
// First check client-side privilegeChecker if provided
|
||||
const checkPrivilege = async (): Promise<boolean> => {
|
||||
if (pageData.privilegeChecker) {
|
||||
try {
|
||||
const result = await pageData.privilegeChecker();
|
||||
if (!result) {
|
||||
console.log('⛔ PageManager: Page blocked by privilegeChecker:', currentPath);
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ PageManager: Error checking privilegeChecker:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
Promise.all([checkPrivilege(), checkPageAccess(pageData)]).then(([hasPrivilege, hasRBACAccess]) => {
|
||||
const hasAccess = hasPrivilege && hasRBACAccess;
|
||||
console.log('🔍 PageManager: Access check complete:', {
|
||||
path: currentPath,
|
||||
hasPrivilege,
|
||||
hasRBACAccess,
|
||||
hasAccess
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -123,14 +123,30 @@ export const SidebarProvider: React.FC<SidebarProviderProps> = ({ children }) =>
|
|||
|
||||
// Process parent groups
|
||||
for (const [_parentPath, parentGroup] of parentGroups.entries()) {
|
||||
// Filter subpages by RBAC access
|
||||
// Filter subpages by RBAC access and privilegeChecker
|
||||
const accessibleSubpages = [];
|
||||
for (const subpage of parentGroup.subpages) {
|
||||
try {
|
||||
// Check RBAC access
|
||||
const hasSubpageRBACAccess = await canView('UI', subpage.path);
|
||||
if (hasSubpageRBACAccess) {
|
||||
accessibleSubpages.push(subpage);
|
||||
if (!hasSubpageRBACAccess) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check client-side privilegeChecker if provided
|
||||
if (subpage.privilegeChecker) {
|
||||
try {
|
||||
const hasPrivilege = await subpage.privilegeChecker();
|
||||
if (!hasPrivilege) {
|
||||
continue;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error checking privilegeChecker for subpage ${subpage.path}:`, error);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
accessibleSubpages.push(subpage);
|
||||
} catch (error) {
|
||||
console.error(`Error checking RBAC access for subpage ${subpage.path}:`, error);
|
||||
}
|
||||
|
|
@ -165,8 +181,7 @@ export const SidebarProvider: React.FC<SidebarProviderProps> = ({ children }) =>
|
|||
console.log('👤 SidebarProvider: Current user info:', {
|
||||
username: cachedUser?.username,
|
||||
roleLabels: cachedUser?.roleLabels,
|
||||
roleLabelsLength: Array.isArray(cachedUser?.roleLabels) ? cachedUser.roleLabels.length : 0,
|
||||
privilege: cachedUser?.privilege
|
||||
roleLabelsLength: Array.isArray(cachedUser?.roleLabels) ? cachedUser.roleLabels.length : 0
|
||||
});
|
||||
|
||||
// Process each main page
|
||||
|
|
@ -191,6 +206,20 @@ export const SidebarProvider: React.FC<SidebarProviderProps> = ({ children }) =>
|
|||
console.log('⛔ SidebarProvider: Page hidden due to RBAC:', pageData.path);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check client-side privilegeChecker if provided
|
||||
if (pageData.privilegeChecker) {
|
||||
try {
|
||||
const hasPrivilege = await pageData.privilegeChecker();
|
||||
if (!hasPrivilege) {
|
||||
console.log('⛔ SidebarProvider: Page hidden due to privilegeChecker:', pageData.path);
|
||||
continue;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`❌ SidebarProvider: Error checking privilegeChecker for ${pageData.path}:`, error);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`❌ SidebarProvider: Error checking RBAC access for ${pageData.path}:`, error);
|
||||
continue;
|
||||
|
|
@ -226,12 +255,27 @@ export const SidebarProvider: React.FC<SidebarProviderProps> = ({ children }) =>
|
|||
hasAccess: hasSubpageRBACAccess
|
||||
});
|
||||
|
||||
if (hasSubpageRBACAccess) {
|
||||
accessibleSubpages.push(subpage);
|
||||
console.log('✅ SidebarProvider: Subpage added:', subpage.path);
|
||||
} else {
|
||||
if (!hasSubpageRBACAccess) {
|
||||
console.log('⛔ SidebarProvider: Subpage hidden due to RBAC:', subpage.path);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check client-side privilegeChecker if provided
|
||||
if (subpage.privilegeChecker) {
|
||||
try {
|
||||
const hasPrivilege = await subpage.privilegeChecker();
|
||||
if (!hasPrivilege) {
|
||||
console.log('⛔ SidebarProvider: Subpage hidden due to privilegeChecker:', subpage.path);
|
||||
continue;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`❌ SidebarProvider: Error checking privilegeChecker for subpage ${subpage.path}:`, error);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
accessibleSubpages.push(subpage);
|
||||
console.log('✅ SidebarProvider: Subpage added:', subpage.path);
|
||||
} catch (error) {
|
||||
console.error(`❌ SidebarProvider: Error checking RBAC access for subpage ${subpage.path}:`, error);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { GenericPageData } from '../../pageInterface';
|
||||
import { FaTable, FaPlus } from 'react-icons/fa';
|
||||
import { createProjectsTableHook, createParzellenTableHook } from '../../../../hooks/usePekTables';
|
||||
import { getUserDataCache } from '../../../../utils/userCache';
|
||||
|
||||
export const pekTablesPageData: GenericPageData = {
|
||||
id: 'pek-tables',
|
||||
|
|
@ -188,6 +189,14 @@ export const pekTablesPageData: GenericPageData = {
|
|||
// Sidebar
|
||||
order: 11,
|
||||
showInSidebar: true,
|
||||
|
||||
// Privilege checker: deny access for "user" role
|
||||
privilegeChecker: async () => {
|
||||
const userData = getUserDataCache();
|
||||
const roleLabels = Array.isArray(userData?.roleLabels) ? userData.roleLabels : [];
|
||||
// Deny access if user has "user" role
|
||||
return !roleLabels.includes('user');
|
||||
},
|
||||
|
||||
// Lifecycle hooks
|
||||
onActivate: async () => {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import PekLocationInput from './pek/PekLocationInput';
|
|||
import PekMapView from './pek/PekMapView';
|
||||
import { usePek } from '../../../../hooks/usePek';
|
||||
import PekPageWrapper from './pek/PekPageWrapper';
|
||||
import { getUserDataCache } from '../../../../utils/userCache';
|
||||
|
||||
// Hook factory for PEK page
|
||||
const createPekHook = () => {
|
||||
|
|
@ -102,6 +103,14 @@ export const pekPageData: GenericPageData = {
|
|||
order: 10,
|
||||
showInSidebar: true,
|
||||
|
||||
// Privilege checker: deny access for "user" role
|
||||
privilegeChecker: async () => {
|
||||
const userData = getUserDataCache();
|
||||
const roleLabels = Array.isArray(userData?.roleLabels) ? userData.roleLabels : [];
|
||||
// Deny access if user has "user" role
|
||||
return !roleLabels.includes('user');
|
||||
},
|
||||
|
||||
// Custom component wrapper with PekProvider
|
||||
customComponent: PekPageWrapper,
|
||||
|
||||
|
|
|
|||
|
|
@ -367,6 +367,9 @@ export interface GenericPageData {
|
|||
// Custom component override (optional)
|
||||
customComponent?: React.ComponentType<any>;
|
||||
|
||||
// Privilege checker - if provided, page will only render if checker returns true
|
||||
privilegeChecker?: PrivilegeChecker;
|
||||
|
||||
// Drag and drop configuration
|
||||
dragDropConfig?: DragDropConfig;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,13 +32,11 @@ export function useCurrentUser() {
|
|||
if (cachedUser && cachedUser.username) {
|
||||
// Check if cached user has roleLabels - if empty, refetch from API
|
||||
const hasRoleLabels = Array.isArray(cachedUser.roleLabels) && cachedUser.roleLabels.length > 0;
|
||||
const hasPrivilege = !!cachedUser.privilege;
|
||||
|
||||
if (!hasRoleLabels && !hasPrivilege) {
|
||||
console.warn('⚠️ Cached user data has no roleLabels or privilege, refetching from API:', {
|
||||
if (!hasRoleLabels) {
|
||||
console.warn('⚠️ Cached user data has no roleLabels, refetching from API:', {
|
||||
username: cachedUser.username,
|
||||
roleLabels: cachedUser.roleLabels,
|
||||
privilege: cachedUser.privilege
|
||||
roleLabels: cachedUser.roleLabels
|
||||
});
|
||||
// Clear cache and continue to fetch from API
|
||||
clearUserDataCache();
|
||||
|
|
@ -47,8 +45,7 @@ export function useCurrentUser() {
|
|||
setUser(cachedUser);
|
||||
console.log('✅ Using cached user data from sessionStorage (persists during session):', {
|
||||
username: cachedUser.username,
|
||||
roleLabels: cachedUser.roleLabels,
|
||||
privilege: cachedUser.privilege
|
||||
roleLabels: cachedUser.roleLabels
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
|
@ -85,17 +82,15 @@ export function useCurrentUser() {
|
|||
console.log('📦 User data received from API:', {
|
||||
username: data?.username,
|
||||
roleLabels: data?.roleLabels,
|
||||
privilege: data?.privilege,
|
||||
hasRoleLabels: !!data?.roleLabels,
|
||||
roleLabelsLength: Array.isArray(data?.roleLabels) ? data.roleLabels.length : 0,
|
||||
roleLabelsContent: Array.isArray(data?.roleLabels) ? data.roleLabels : 'not an array',
|
||||
hasPrivilege: !!data?.privilege,
|
||||
allKeys: data ? Object.keys(data) : [],
|
||||
fullData: JSON.stringify(data, null, 2)
|
||||
});
|
||||
|
||||
// Always cache user data - permissions are checked via RBAC API, not client-side
|
||||
// roleLabels/privilege are optional metadata for display/logging purposes
|
||||
// roleLabels are optional metadata for display/logging purposes
|
||||
if (!data || !data.username) {
|
||||
console.error('❌ User data from API is invalid:', {
|
||||
username: data?.username,
|
||||
|
|
@ -107,13 +102,11 @@ export function useCurrentUser() {
|
|||
|
||||
// Check if API returned roleLabels - if not, log warning but still cache
|
||||
const hasRoleLabels = Array.isArray(data.roleLabels) && data.roleLabels.length > 0;
|
||||
const hasPrivilege = !!data.privilege;
|
||||
|
||||
if (!hasRoleLabels && !hasPrivilege) {
|
||||
console.warn('⚠️ User data from API has no roleLabels or privilege - this may cause RBAC issues:', {
|
||||
if (!hasRoleLabels) {
|
||||
console.warn('⚠️ User data from API has no roleLabels - this may cause RBAC issues:', {
|
||||
username: data.username,
|
||||
roleLabels: data.roleLabels,
|
||||
privilege: data.privilege,
|
||||
allKeys: Object.keys(data),
|
||||
fullResponse: JSON.stringify(data, null, 2)
|
||||
});
|
||||
|
|
@ -127,9 +120,7 @@ export function useCurrentUser() {
|
|||
username: data.username,
|
||||
roleLabels: data.roleLabels,
|
||||
roleLabelsLength: Array.isArray(data.roleLabels) ? data.roleLabels.length : 0,
|
||||
privilege: data.privilege,
|
||||
hasRoleLabels,
|
||||
hasPrivilege
|
||||
hasRoleLabels
|
||||
});
|
||||
setUser(data);
|
||||
} catch (error: any) {
|
||||
|
|
@ -302,13 +293,11 @@ export function useCurrentUser() {
|
|||
if (cachedUser && cachedUser.username) {
|
||||
// Check if cached user has roleLabels - if empty, refetch from API
|
||||
const hasRoleLabels = Array.isArray(cachedUser.roleLabels) && cachedUser.roleLabels.length > 0;
|
||||
const hasPrivilege = !!cachedUser.privilege;
|
||||
|
||||
if (!hasRoleLabels && !hasPrivilege) {
|
||||
console.warn('⚠️ Cached user data has no roleLabels or privilege, refetching from API:', {
|
||||
if (!hasRoleLabels) {
|
||||
console.warn('⚠️ Cached user data has no roleLabels, refetching from API:', {
|
||||
username: cachedUser.username,
|
||||
roleLabels: cachedUser.roleLabels,
|
||||
privilege: cachedUser.privilege
|
||||
roleLabels: cachedUser.roleLabels
|
||||
});
|
||||
// Clear cache and refetch
|
||||
clearUserDataCache();
|
||||
|
|
@ -320,8 +309,7 @@ export function useCurrentUser() {
|
|||
setUser(cachedUser);
|
||||
console.log('✅ Using cached user data from sessionStorage on mount (persists during session):', {
|
||||
username: cachedUser.username,
|
||||
roleLabels: cachedUser.roleLabels,
|
||||
privilege: cachedUser.privilege
|
||||
roleLabels: cachedUser.roleLabels
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,10 +11,10 @@ import type { PermissionContext } from '../hooks/usePermissions';
|
|||
* 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 => {
|
||||
// Function to get current user role labels from sessionStorage cache
|
||||
const getCurrentUserRoleLabels = (): string[] => {
|
||||
const userData = getUserDataCache();
|
||||
return userData?.privilege || null;
|
||||
return Array.isArray(userData?.roleLabels) ? userData.roleLabels : [];
|
||||
};
|
||||
|
||||
// Generic privilege checker for localStorage-based data with expiration
|
||||
|
|
@ -148,8 +148,9 @@ export const createCombinedPrivilegeChecker = (
|
|||
}
|
||||
|
||||
// If RBAC allows, also check client-side roles as additional validation
|
||||
const userPrivilege = getCurrentUserPrivilege();
|
||||
if (userPrivilege && requiredRoles.includes(userPrivilege)) {
|
||||
const userRoleLabels = getCurrentUserRoleLabels();
|
||||
const hasRequiredRole = requiredRoles.some(role => userRoleLabels.includes(role));
|
||||
if (hasRequiredRole) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -229,8 +230,8 @@ export const privilegeCheckers = {
|
|||
adminRole: createRolePrivilegeChecker(
|
||||
['admin', 'sysadmin'],
|
||||
() => {
|
||||
const userPrivilege = getCurrentUserPrivilege();
|
||||
return Promise.resolve(userPrivilege ? [userPrivilege] : []);
|
||||
const userRoleLabels = getCurrentUserRoleLabels();
|
||||
return Promise.resolve(userRoleLabels);
|
||||
}
|
||||
),
|
||||
|
||||
|
|
@ -238,8 +239,8 @@ export const privilegeCheckers = {
|
|||
sysadminRole: createRolePrivilegeChecker(
|
||||
['sysadmin'],
|
||||
() => {
|
||||
const userPrivilege = getCurrentUserPrivilege();
|
||||
return Promise.resolve(userPrivilege ? [userPrivilege] : []);
|
||||
const userRoleLabels = getCurrentUserRoleLabels();
|
||||
return Promise.resolve(userRoleLabels);
|
||||
}
|
||||
),
|
||||
|
||||
|
|
@ -271,8 +272,8 @@ export const privilegeCheckers = {
|
|||
userRole: createRolePrivilegeChecker(
|
||||
['user', 'admin', 'sysadmin'],
|
||||
() => {
|
||||
const userPrivilege = getCurrentUserPrivilege();
|
||||
return Promise.resolve(userPrivilege ? [userPrivilege] : []);
|
||||
const userRoleLabels = getCurrentUserRoleLabels();
|
||||
return Promise.resolve(userRoleLabels);
|
||||
}
|
||||
),
|
||||
|
||||
|
|
@ -280,8 +281,8 @@ export const privilegeCheckers = {
|
|||
viewerRole: createRolePrivilegeChecker(
|
||||
['viewer', 'user', 'admin', 'sysadmin'],
|
||||
() => {
|
||||
const userPrivilege = getCurrentUserPrivilege();
|
||||
return Promise.resolve(userPrivilege ? [userPrivilege] : []);
|
||||
const userRoleLabels = getCurrentUserRoleLabels();
|
||||
return Promise.resolve(userRoleLabels);
|
||||
}
|
||||
),
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue