Fixed API calls on governance page

Enforced multitenancy
This commit is contained in:
2026-09-26 10:28:43 +02:00
parent b2688867f3
commit f3a8f78144
16 changed files with 3552 additions and 503 deletions
@@ -15,6 +15,7 @@ import {
import { AccountActivationModal } from '../components/auth/AccountActivationModal';
import { SessionExpiredModal } from '../components/auth/SessionExpiredModal';
import { encodeHashIdSync } from '../services/toolsService';
import { verifyTenantLockout } from '../services/tenantLockoutService';
import {
onSessionExpired,
triggerSessionExpired,
@@ -120,6 +121,79 @@ export function extractTenantId(claims?: UserClaims | null, token?: string | nul
return null;
}
export function extractTenantClaimValues(
claims?: UserClaims | null,
token?: string | null
): {
tenantId: number | string | null;
tenant: string | null;
} {
let tenantId: number | string | null = null;
let tenant: string | null = null;
if (claims) {
const rawId =
claims.tenant_id ??
(claims as any).tenantId ??
claims['tenant_id'] ??
claims['http://schemas.microsoft.com/ws/2008/06/identity/claims/tenantid'];
if (rawId !== undefined && rawId !== null && rawId !== '') {
const parsed = Number(rawId);
tenantId = !isNaN(parsed) && isFinite(parsed) ? parsed : String(rawId).trim();
}
const rawTenant =
claims.tenant ??
claims['tenant'] ??
claims['http://schemas.microsoft.com/ws/2008/06/identity/claims/tenant'] ??
claims.tenant_name ??
(claims as any).tenantName ??
claims.org;
if (rawTenant !== undefined && rawTenant !== null && rawTenant !== '') {
if (typeof rawTenant === 'string') {
tenant = rawTenant.trim();
} else if (typeof rawTenant === 'object' && rawTenant.name) {
tenant = String(rawTenant.name).trim();
}
}
}
if (token && (!tenantId || !tenant)) {
try {
const tokenClaims = parseJwtClaims(token);
if (!tenantId) {
const rawId =
tokenClaims.tenant_id ??
tokenClaims.tenantId ??
tokenClaims['tenant_id'] ??
tokenClaims['http://schemas.microsoft.com/ws/2008/06/identity/claims/tenantid'];
if (rawId !== undefined && rawId !== null && rawId !== '') {
const parsed = Number(rawId);
tenantId = !isNaN(parsed) && isFinite(parsed) ? parsed : String(rawId).trim();
}
}
if (!tenant) {
const rawTenant =
tokenClaims.tenant ??
tokenClaims['tenant'] ??
tokenClaims['http://schemas.microsoft.com/ws/2008/06/identity/claims/tenant'] ??
tokenClaims.tenant_name ??
(tokenClaims as any).tenantName ??
tokenClaims.org;
if (rawTenant !== undefined && rawTenant !== null && rawTenant !== '') {
if (typeof rawTenant === 'string') {
tenant = rawTenant.trim();
} else if (typeof rawTenant === 'object' && rawTenant.name) {
tenant = String(rawTenant.name).trim();
}
}
}
} catch {}
}
return { tenantId, tenant };
}
export function getCurrentUserTenantId(): number | null {
return extractTenantId(
getCurrentUserClaims(),
@@ -283,7 +357,8 @@ export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
localStorage.setItem(LOCAL_STORAGE_SUB_KEY, userSub);
localStorage.setItem(LOCAL_STORAGE_CLAIMS_KEY, JSON.stringify(mergedClaims));
const tenantClaim = (mergedClaims.tenant as string) || (oidcUser.profile?.tenant as string) || undefined;
const tenantClaimValues = extractTenantClaimValues(mergedClaims, token);
const tenantClaim = (mergedClaims.tenant as string) || (oidcUser.profile?.tenant as string) || tenantClaimValues.tenant || undefined;
const parsedTenantId = extractTenantId(mergedClaims, token);
setTenantId(parsedTenantId);
@@ -304,6 +379,20 @@ export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
// Clean up code and state from URL
window.history.replaceState({}, document.title, window.location.pathname);
// Verify tenant lockout at the instance tenant and tenant_id claims are retrieved
const tenantLockoutResult = await verifyTenantLockout(
parsedTenantId ?? tenantClaimValues.tenantId,
tenantClaim
);
if (!tenantLockoutResult.isActive) {
setIsPendingActivation(true);
setIsSynced(false);
setActivationError(tenantLockoutResult.errorMessage || 'Tenant is inactive or not found');
setIsLoading(false);
return;
}
// Synchronize authenticated user profile with backend directory
try {
const syncResult = await syncUserAccount(userSub, token, profile, true);
@@ -367,6 +456,8 @@ export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
const storedTenantId = extractTenantId(parsedClaims, storedToken);
setTenantId(storedTenantId);
const tenantClaimValues = extractTenantClaimValues(parsedClaims, storedToken);
const storedTenantClaim = (parsedClaims.tenant as string) || tenantClaimValues.tenant || undefined;
let resolvedUser: AuthUserProfile | null = null;
if (storedUser) {
@@ -383,6 +474,20 @@ export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
} catch {}
}
// Verify tenant lockout at the instance tenant and tenant_id claims are retrieved
const tenantLockoutResult = await verifyTenantLockout(
storedTenantId ?? tenantClaimValues.tenantId,
storedTenantClaim
);
if (!tenantLockoutResult.isActive) {
setIsPendingActivation(true);
setIsSynced(false);
setActivationError(tenantLockoutResult.errorMessage || 'Tenant is inactive or not found');
setIsLoading(false);
return;
}
// Check if session is already marked pending activation or needs sync check
const wasPending = isActivationPending();
setIsPendingActivation(wasPending);
@@ -435,6 +540,25 @@ export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
const syncUserSession = async (force = true): Promise<UserSyncResult> => {
try {
// Re-verify tenant status via GetTenant as part of the lockout resolution
const tenantClaimValues = extractTenantClaimValues(claims, accessToken);
const currentTenantId = tenantId ?? tenantClaimValues.tenantId;
const currentTenantClaim = (claims.tenant as string) || user?.tenant || tenantClaimValues.tenant || undefined;
const tenantLockoutResult = await verifyTenantLockout(currentTenantId, currentTenantClaim);
if (!tenantLockoutResult.isActive) {
const msg = tenantLockoutResult.errorMessage || 'Tenant is not Active. Lockout active.';
setIsPendingActivation(true);
setIsSynced(false);
setActivationError(msg);
return {
success: false,
isActivationPending: true,
errorMessage: msg,
};
}
// Tenant is verified Active: proceed with user sync
const result = await syncUserAccount(sub, accessToken, user || undefined, force);
if (result.isUnauthorized) {
await logout();