feat: implemented priviledge checker into real estate pages

This commit is contained in:
Ida Dittrich 2026-01-12 11:44:10 +01:00
parent 239fd328bc
commit be3844f33e
9 changed files with 126 additions and 53 deletions

View file

@ -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
});

View file

@ -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 || ''
};

View file

@ -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
});

View file

@ -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);
}

View file

@ -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',
@ -189,6 +190,14 @@ export const pekTablesPageData: GenericPageData = {
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 () => {
if (import.meta.env.DEV) console.log('PEK Tables page activated');

View file

@ -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,

View file

@ -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;
}

View file

@ -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
});
}

View file

@ -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);
}
),