219 lines
6.8 KiB
TypeScript
219 lines
6.8 KiB
TypeScript
import { getTenant, getTenants } from './api';
|
|
import { ActivityStatus, Tenant } from '../types';
|
|
import { setActivationPending } from './userSyncService';
|
|
import { encodeHashIdSync, isAlreadyHashId } from '../utils/hashids';
|
|
|
|
export interface TenantLockoutResult {
|
|
isActive: boolean;
|
|
tenant: Tenant | null;
|
|
errorMessage?: string;
|
|
statusCode?: number;
|
|
}
|
|
|
|
let cachedTenantLockout: {
|
|
key: string;
|
|
result: TenantLockoutResult;
|
|
timestamp: number;
|
|
} | null = null;
|
|
const LOCKOUT_CACHE_TTL_MS = 60 * 1000;
|
|
|
|
export function clearTenantLockoutCache(): void {
|
|
cachedTenantLockout = null;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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';
|
|
}
|
|
|
|
function isValidHashableOrHashedTenantId(val: unknown): val is number | string {
|
|
if (val === undefined || val === null || val === '') return false;
|
|
if (typeof val === 'number') {
|
|
return !isNaN(val) && isFinite(val) && val > 0;
|
|
}
|
|
if (typeof val === 'string') {
|
|
const trimmed = val.trim();
|
|
if (!trimmed) return false;
|
|
if (/^\d+$/.test(trimmed)) {
|
|
return parseInt(trimmed, 10) > 0;
|
|
}
|
|
return isAlreadyHashId(trimmed);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
export async function verifyTenantLockout(
|
|
tenantId?: number | string | null,
|
|
tenantClaim?: string | null,
|
|
forceRefresh = false
|
|
): Promise<TenantLockoutResult> {
|
|
// Resolve a valid numeric ID or HashId to avoid passing plain-text tenant names to /v1/api/governance/tenants/{id}
|
|
let candidateId: number | string | null = null;
|
|
if (isValidHashableOrHashedTenantId(tenantId)) {
|
|
candidateId = tenantId;
|
|
} else if (isValidHashableOrHashedTenantId(tenantClaim)) {
|
|
candidateId = tenantClaim;
|
|
}
|
|
|
|
const cacheKey = String(candidateId ?? tenantClaim ?? 'default-1').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;
|
|
|
|
// 1. If we have a valid numeric tenantId or HashId, encode it via HashIds and call GET /v1/api/governance/tenants/{hashedId}
|
|
if (candidateId !== null) {
|
|
try {
|
|
const hashedId = encodeHashIdSync(candidateId);
|
|
tenantRes = await getTenant(hashedId || candidateId);
|
|
} catch (err: any) {
|
|
lastError = err;
|
|
tenantRes = null;
|
|
}
|
|
}
|
|
|
|
// 2. If tenant lookup by ID wasn't possible or didn't resolve, resolve via getTenants() (matching tenantClaim name if provided)
|
|
if (!tenantRes) {
|
|
try {
|
|
const allTenants = await getTenants();
|
|
if (Array.isArray(allTenants) && allTenants.length > 0) {
|
|
const claimStr = tenantClaim ? String(tenantClaim).trim().toLowerCase() : '';
|
|
const matched = claimStr
|
|
? allTenants.find(
|
|
(t) =>
|
|
(t.name && t.name.trim().toLowerCase() === claimStr) ||
|
|
String(t.id).toLowerCase() === claimStr ||
|
|
encodeHashIdSync(t.id).toLowerCase() === claimStr
|
|
)
|
|
: null;
|
|
|
|
const targetTenant = matched || allTenants.find((t) => isStatusActive((t as any).status ?? (t as any).Status)) || allTenants[0];
|
|
if (targetTenant && targetTenant.id !== undefined && targetTenant.id !== null) {
|
|
try {
|
|
const hashedTargetId = encodeHashIdSync(targetTenant.id);
|
|
tenantRes = await getTenant(hashedTargetId || targetTenant.id);
|
|
lastError = null;
|
|
} catch {
|
|
tenantRes = targetTenant;
|
|
lastError = null;
|
|
}
|
|
}
|
|
}
|
|
} catch (err: any) {
|
|
if (!lastError) lastError = err;
|
|
}
|
|
}
|
|
|
|
// 3. Final fallback: query default primary tenant ID 1 encoded as a HashId (matching ConsoleUI)
|
|
if (!tenantRes && candidateId !== 1 && candidateId !== '1') {
|
|
try {
|
|
const hashedDefaultId = encodeHashIdSync(1);
|
|
tenantRes = await getTenant(hashedDefaultId);
|
|
lastError = null;
|
|
} catch (err: any) {
|
|
if (!lastError) lastError = err;
|
|
tenantRes = null;
|
|
}
|
|
}
|
|
|
|
if (!tenantRes || (typeof tenantRes === 'object' && Object.keys(tenantRes).length === 0)) {
|
|
let serverMsg =
|
|
lastError?.response?.data?.detail ||
|
|
lastError?.response?.data?.message ||
|
|
lastError?.message ||
|
|
'No tenant record returned';
|
|
|
|
if (typeof serverMsg === 'string') {
|
|
if (/^network\s*error$/i.test(serverMsg.trim())) {
|
|
serverMsg = 'Permission Check';
|
|
} else {
|
|
serverMsg = serverMsg.replace(/network\s*error/gi, 'Permission Check');
|
|
}
|
|
}
|
|
|
|
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,
|
|
};
|
|
}
|
|
|
|
const tenantObj: Tenant = (tenantRes as any)?.data || (tenantRes as any)?.result || tenantRes;
|
|
|
|
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(candidateId ?? tenantClaim ?? '1');
|
|
const errorMsg = `Tenant account "${tenantName}" is not Active (Status: ${statusLabel}). Account is locked out.`;
|
|
setActivationPending(true, errorMsg);
|
|
return {
|
|
isActive: false,
|
|
tenant: tenantObj,
|
|
errorMessage: errorMsg,
|
|
};
|
|
}
|
|
|
|
setActivationPending(false);
|
|
|
|
const successResult: TenantLockoutResult = {
|
|
isActive: true,
|
|
tenant: tenantObj,
|
|
};
|
|
cachedTenantLockout = {
|
|
key: cacheKey,
|
|
result: successResult,
|
|
timestamp: Date.now(),
|
|
};
|
|
return successResult;
|
|
}
|