forked from baron/baron-sso
230 lines
5.3 KiB
TypeScript
230 lines
5.3 KiB
TypeScript
export type OrgChartTenantSelection = {
|
|
id: string;
|
|
name: string;
|
|
};
|
|
|
|
export type TenantFilterTarget = {
|
|
id?: string;
|
|
slug?: string;
|
|
type?: string;
|
|
parentId?: string | null;
|
|
name?: string;
|
|
};
|
|
|
|
export type HanmacFamilyUserTarget = {
|
|
companyCode?: string;
|
|
tenantSlug?: string;
|
|
tenant?: TenantFilterTarget;
|
|
joinedTenants?: TenantFilterTarget[];
|
|
metadata?: Record<string, unknown>;
|
|
};
|
|
|
|
type OrgChartPickerMessage = {
|
|
type?: unknown;
|
|
payload?: {
|
|
selections?: Array<{
|
|
type?: unknown;
|
|
id?: unknown;
|
|
name?: unknown;
|
|
}>;
|
|
};
|
|
};
|
|
|
|
type OrgChartTenantPickerOptions = {
|
|
tenantId?: string;
|
|
};
|
|
|
|
type OrgChartLoginOptions = {
|
|
returnTo?: string;
|
|
};
|
|
|
|
function isSystemTenant(tenant: TenantFilterTarget) {
|
|
const slug = tenant.slug?.trim().toLowerCase();
|
|
const type = tenant.type?.trim().toUpperCase();
|
|
|
|
return (
|
|
!tenant.id?.trim() ||
|
|
!tenant.slug?.trim() ||
|
|
type === "SYSTEM" ||
|
|
slug === "system" ||
|
|
slug === "global"
|
|
);
|
|
}
|
|
|
|
function isInTenantSubtree<T extends TenantFilterTarget>(
|
|
tenant: T,
|
|
rootTenantId: string,
|
|
tenantById: Map<string, T>,
|
|
) {
|
|
if (!rootTenantId) {
|
|
return false;
|
|
}
|
|
if (tenant.id === rootTenantId) {
|
|
return true;
|
|
}
|
|
|
|
const visitedTenantIds = new Set<string>();
|
|
let parentId = tenant.parentId ?? "";
|
|
while (parentId) {
|
|
if (parentId === rootTenantId) {
|
|
return true;
|
|
}
|
|
if (visitedTenantIds.has(parentId)) {
|
|
return false;
|
|
}
|
|
visitedTenantIds.add(parentId);
|
|
parentId = tenantById.get(parentId)?.parentId ?? "";
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function resolveHanmacFamilyTenantId<T extends TenantFilterTarget>(
|
|
tenants: T[],
|
|
hanmacFamilyTenantId?: string,
|
|
) {
|
|
const envTenantId = hanmacFamilyTenantId?.trim();
|
|
if (envTenantId) return envTenantId;
|
|
|
|
return (
|
|
tenants.find((tenant) => tenant.slug?.toLowerCase() === "hanmac-family")
|
|
?.id ?? ""
|
|
);
|
|
}
|
|
|
|
export function isHanmacFamilyTenant<T extends TenantFilterTarget>(
|
|
tenant: T | undefined,
|
|
tenants: T[],
|
|
hanmacFamilyTenantId?: string,
|
|
) {
|
|
if (!tenant || !tenant.id) return false;
|
|
|
|
const rootTenantId = resolveHanmacFamilyTenantId(
|
|
tenants,
|
|
hanmacFamilyTenantId,
|
|
);
|
|
if (!rootTenantId) return false;
|
|
|
|
const tenantById = new Map(
|
|
tenants
|
|
.filter((item) => item.id?.trim())
|
|
.map((item) => [item.id as string, item]),
|
|
);
|
|
const target = tenantById.get(tenant.id) ?? tenant;
|
|
|
|
return isInTenantSubtree(target, rootTenantId, tenantById);
|
|
}
|
|
|
|
export function isHanmacFamilyUser<T extends TenantFilterTarget>(
|
|
user: HanmacFamilyUserTarget,
|
|
tenants: T[],
|
|
hanmacFamilyTenantId?: string,
|
|
) {
|
|
const metadata = user.metadata ?? {};
|
|
if (metadata.hanmacFamily === true || metadata.userType === "hanmac") {
|
|
return true;
|
|
}
|
|
|
|
const tenantBySlug = new Map(
|
|
tenants
|
|
.filter((tenant) => tenant.slug?.trim())
|
|
.map((tenant) => [tenant.slug?.toLowerCase() as string, tenant]),
|
|
);
|
|
const tenantCandidates = [
|
|
user.tenant,
|
|
...(user.joinedTenants ?? []),
|
|
tenantBySlug.get(user.companyCode?.toLowerCase() ?? ""),
|
|
tenantBySlug.get(user.tenantSlug?.toLowerCase() ?? ""),
|
|
];
|
|
|
|
return tenantCandidates.some((tenant) =>
|
|
isHanmacFamilyTenant(tenant, tenants, hanmacFamilyTenantId),
|
|
);
|
|
}
|
|
|
|
export function filterNonHanmacFamilyTenants<T extends TenantFilterTarget>(
|
|
tenants: T[],
|
|
hanmacFamilyTenantId?: string,
|
|
) {
|
|
const rootTenantId = resolveHanmacFamilyTenantId(
|
|
tenants,
|
|
hanmacFamilyTenantId,
|
|
);
|
|
const tenantById = new Map(
|
|
tenants
|
|
.filter((tenant) => tenant.id?.trim())
|
|
.map((tenant) => [tenant.id as string, tenant]),
|
|
);
|
|
|
|
return tenants.filter(
|
|
(tenant) =>
|
|
!isSystemTenant(tenant) &&
|
|
!isInTenantSubtree(tenant, rootTenantId, tenantById),
|
|
);
|
|
}
|
|
|
|
export function buildOrgChartTenantPickerUrl(
|
|
baseUrl?: string,
|
|
options: OrgChartTenantPickerOptions = {},
|
|
) {
|
|
const normalizedBase = (baseUrl ?? "").replace(/\/+$/, "");
|
|
const params = new URLSearchParams({
|
|
mode: "single",
|
|
select: "tenant",
|
|
width: "400",
|
|
height: "600",
|
|
});
|
|
const tenantId = options.tenantId?.trim();
|
|
if (tenantId) {
|
|
params.set("tenantId", tenantId);
|
|
}
|
|
|
|
return `${normalizedBase}/embed/picker?${params.toString()}`;
|
|
}
|
|
|
|
export function buildAuthenticatedOrgChartTenantPickerUrl(
|
|
baseUrl?: string,
|
|
options: OrgChartTenantPickerOptions = {},
|
|
) {
|
|
const pickerUrl = buildOrgChartTenantPickerUrl("", options);
|
|
return buildAuthenticatedOrgChartUrl(baseUrl, { returnTo: pickerUrl });
|
|
}
|
|
|
|
export function buildAuthenticatedOrgChartUrl(
|
|
baseUrl?: string,
|
|
options: OrgChartLoginOptions = {},
|
|
) {
|
|
const normalizedBase = (baseUrl ?? "").replace(/\/+$/, "");
|
|
const returnTo = options.returnTo?.trim() || "/chart";
|
|
const params = new URLSearchParams({
|
|
auto: "1",
|
|
returnTo,
|
|
});
|
|
|
|
return `${normalizedBase}/login?${params.toString()}`;
|
|
}
|
|
|
|
export function parseOrgChartTenantSelection(
|
|
message: unknown,
|
|
): OrgChartTenantSelection | null {
|
|
const data = message as OrgChartPickerMessage;
|
|
if (data?.type !== "orgfront:picker:confirm") {
|
|
return null;
|
|
}
|
|
|
|
const selection = data.payload?.selections?.[0];
|
|
if (
|
|
selection?.type !== "tenant" ||
|
|
typeof selection.id !== "string" ||
|
|
typeof selection.name !== "string" ||
|
|
selection.id.trim() === ""
|
|
) {
|
|
return null;
|
|
}
|
|
|
|
return {
|
|
id: selection.id,
|
|
name: selection.name,
|
|
};
|
|
}
|