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