184 lines
5.5 KiB
TypeScript
184 lines
5.5 KiB
TypeScript
import { getTenant } from './api';
|
|
import { ActivityStatus, Tenant } from '../types';
|
|
import { setActivationPending } from './userSyncService';
|
|
|
|
export interface TenantLockoutResult {
|
|
isActive: boolean;
|
|
tenant: Tenant | null;
|
|
errorMessage?: string;
|
|
statusCode?: number;
|
|
}
|
|
|
|
// In-memory cache for fast tenant status checks (avoids repeated API round trips during navigations)
|
|
let cachedTenantLockout: {
|
|
key: string;
|
|
result: TenantLockoutResult;
|
|
timestamp: number;
|
|
} | null = null;
|
|
const LOCKOUT_CACHE_TTL_MS = 60 * 1000; // 60 seconds
|
|
|
|
export function clearTenantLockoutCache(): void {
|
|
cachedTenantLockout = null;
|
|
}
|
|
|
|
/**
|
|
* Checks whether a given status value corresponds to ActivityStatus.Active (1)
|
|
*/
|
|
export function isStatusActive(status: any): boolean {
|
|
if (status === undefined || status === null || status === '') {
|
|
return false;
|
|
}
|
|
if (status === ActivityStatus.Active || status === 1 || status === '1') {
|
|
return true;
|
|
}
|
|
if (typeof status === 'string' && status.trim().toLowerCase() === 'active') {
|
|
return true;
|
|
}
|
|
if (status === true) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Formats a human-readable status label from numeric or string status
|
|
*/
|
|
export function getStatusLabel(status: any): string {
|
|
if (status === ActivityStatus.Active || status === 1 || String(status).toLowerCase() === 'active') {
|
|
return 'Active';
|
|
}
|
|
if (status === ActivityStatus.Inactive || status === 2 || String(status).toLowerCase() === 'inactive') {
|
|
return 'Inactive';
|
|
}
|
|
if (status === ActivityStatus.Disabled || status === 3 || String(status).toLowerCase() === 'disabled') {
|
|
return 'Disabled';
|
|
}
|
|
if (String(status).toLowerCase() === 'suspended') {
|
|
return 'Suspended';
|
|
}
|
|
if (status === 0 || String(status).toLowerCase() === 'pending') {
|
|
return 'Pending';
|
|
}
|
|
if (status !== undefined && status !== null && status !== '') {
|
|
return String(status);
|
|
}
|
|
return 'Not Specified';
|
|
}
|
|
|
|
/**
|
|
* Calls the GetTenant endpoint for the provided tenant identifier.
|
|
*
|
|
* Rules:
|
|
* - If nothing is returned (empty response, 404, or failure) OR
|
|
* - There is a response object and its Status is not Active
|
|
* Then lockout is triggered and the existing lockout modal is shown.
|
|
*/
|
|
export async function verifyTenantLockout(
|
|
tenantId?: number | string | null,
|
|
tenantClaim?: string | null,
|
|
forceRefresh = false
|
|
): Promise<TenantLockoutResult> {
|
|
const primaryId =
|
|
tenantId !== undefined && tenantId !== null && tenantId !== ''
|
|
? tenantId
|
|
: tenantClaim !== undefined && tenantClaim !== null && tenantClaim !== ''
|
|
? tenantClaim
|
|
: null;
|
|
|
|
if (primaryId === null || primaryId === undefined || primaryId === '') {
|
|
const errorMsg =
|
|
'Tenant identification claims (tenant / tenant_id) not found in credentials. Access is restricted.';
|
|
setActivationPending(true, errorMsg);
|
|
return {
|
|
isActive: false,
|
|
tenant: null,
|
|
errorMessage: errorMsg,
|
|
};
|
|
}
|
|
|
|
const cacheKey = String(primaryId).trim().toLowerCase();
|
|
if (!forceRefresh && cachedTenantLockout && cachedTenantLockout.key === cacheKey) {
|
|
if (
|
|
cachedTenantLockout.result.isActive &&
|
|
Date.now() - cachedTenantLockout.timestamp < LOCKOUT_CACHE_TTL_MS
|
|
) {
|
|
return cachedTenantLockout.result;
|
|
}
|
|
}
|
|
|
|
let tenantRes: Tenant | null = null;
|
|
let lastError: any = null;
|
|
|
|
try {
|
|
tenantRes = await getTenant(primaryId);
|
|
} catch (err: any) {
|
|
lastError = err;
|
|
tenantRes = null;
|
|
}
|
|
|
|
// If primary identifier returned nothing, and tenantClaim provides a different candidate, attempt fallback
|
|
if (!tenantRes && tenantClaim && String(tenantClaim) !== String(primaryId)) {
|
|
try {
|
|
tenantRes = await getTenant(tenantClaim);
|
|
lastError = null;
|
|
} catch (err: any) {
|
|
lastError = err;
|
|
tenantRes = null;
|
|
}
|
|
}
|
|
|
|
// Condition 1: If nothing is returned
|
|
if (!tenantRes || (typeof tenantRes === 'object' && Object.keys(tenantRes).length === 0)) {
|
|
const serverMsg =
|
|
lastError?.response?.data?.detail ||
|
|
lastError?.response?.data?.message ||
|
|
lastError?.message ||
|
|
'No tenant record returned';
|
|
|
|
const errorMsg = `Tenant record could not be retrieved (${serverMsg}). Account is locked out.`;
|
|
setActivationPending(true, errorMsg);
|
|
return {
|
|
isActive: false,
|
|
tenant: null,
|
|
errorMessage: errorMsg,
|
|
statusCode: lastError?.response?.status,
|
|
};
|
|
}
|
|
|
|
// Handle wrapped response structures: { data: { ... } } or { result: { ... } }
|
|
const tenantObj: Tenant = (tenantRes as any)?.data || (tenantRes as any)?.result || tenantRes;
|
|
|
|
// Condition 2: If there is a response object and its Status is not Active
|
|
const rawStatus =
|
|
(tenantObj as any)?.status ??
|
|
(tenantObj as any)?.Status ??
|
|
(tenantObj as any)?.activityStatus ??
|
|
(tenantObj as any)?.ActivityStatus;
|
|
|
|
const active = isStatusActive(rawStatus);
|
|
|
|
if (!active) {
|
|
const statusLabel = getStatusLabel(rawStatus);
|
|
const tenantName = tenantObj?.name || String(primaryId);
|
|
const errorMsg = `Tenant account "${tenantName}" is not Active (Status: ${statusLabel}). Account is locked out.`;
|
|
setActivationPending(true, errorMsg);
|
|
return {
|
|
isActive: false,
|
|
tenant: tenantObj,
|
|
errorMessage: errorMsg,
|
|
};
|
|
}
|
|
|
|
// Success: Response object returned and Status is Active
|
|
const successResult: TenantLockoutResult = {
|
|
isActive: true,
|
|
tenant: tenantObj,
|
|
};
|
|
cachedTenantLockout = {
|
|
key: cacheKey,
|
|
result: successResult,
|
|
timestamp: Date.now(),
|
|
};
|
|
return successResult;
|
|
}
|