forked from baron/baron-sso
270 lines
6.6 KiB
TypeScript
270 lines
6.6 KiB
TypeScript
export type OrgChartTenantSelection = {
|
|
id: string;
|
|
name: string;
|
|
};
|
|
|
|
export type TenantFilterTarget = {
|
|
id?: string;
|
|
tenantId?: string;
|
|
slug?: string;
|
|
tenantSlug?: string;
|
|
type?: string;
|
|
parentId?: string | null;
|
|
name?: string;
|
|
tenantName?: 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 = {
|
|
includeInternal?: boolean;
|
|
tenantId?: string;
|
|
};
|
|
|
|
type OrgChartLoginOptions = {
|
|
includeInternal?: boolean;
|
|
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 metadataAppointments = Array.isArray(
|
|
user.metadata?.additionalAppointments,
|
|
)
|
|
? user.metadata.additionalAppointments
|
|
.map((appointment) => appointment as TenantFilterTarget)
|
|
.filter(
|
|
(appointment) =>
|
|
typeof appointment.tenantId === "string" ||
|
|
typeof appointment.id === "string" ||
|
|
typeof appointment.tenantSlug === "string" ||
|
|
typeof appointment.slug === "string",
|
|
)
|
|
.map((appointment) => ({
|
|
id: appointment.id ?? appointment.tenantId,
|
|
slug: appointment.slug ?? appointment.tenantSlug,
|
|
parentId: appointment.parentId,
|
|
type: appointment.type,
|
|
name: appointment.name ?? appointment.tenantName,
|
|
}))
|
|
: [];
|
|
const tenantBySlug = new Map(
|
|
tenants
|
|
.filter((tenant) => tenant.slug?.trim())
|
|
.map((tenant) => [tenant.slug?.toLowerCase() as string, tenant]),
|
|
);
|
|
const tenantById = new Map(
|
|
tenants
|
|
.filter((tenant) => tenant.id?.trim())
|
|
.map((tenant) => [tenant.id as string, tenant]),
|
|
);
|
|
const tenantCandidates = [
|
|
user.tenant,
|
|
...(user.joinedTenants ?? []),
|
|
...metadataAppointments,
|
|
...metadataAppointments.map((appointment) =>
|
|
tenantById.get(appointment.id ?? ""),
|
|
),
|
|
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);
|
|
}
|
|
if (options.includeInternal) {
|
|
params.set("includeInternal", "true");
|
|
}
|
|
|
|
return `${normalizedBase}/embed/picker?${params.toString()}`;
|
|
}
|
|
|
|
export function buildAuthenticatedOrgChartTenantPickerUrl(
|
|
baseUrl?: string,
|
|
options: OrgChartTenantPickerOptions = {},
|
|
) {
|
|
const pickerUrl = buildOrgChartTenantPickerUrl("", {
|
|
includeInternal: true,
|
|
...options,
|
|
});
|
|
return buildAuthenticatedOrgChartUrl(baseUrl, { returnTo: pickerUrl });
|
|
}
|
|
|
|
export function buildAuthenticatedOrgChartUrl(
|
|
baseUrl?: string,
|
|
options: OrgChartLoginOptions = { includeInternal: true },
|
|
) {
|
|
const normalizedBase = (baseUrl ?? "").replace(/\/+$/, "");
|
|
let returnTo = options.returnTo?.trim() || "/chart";
|
|
if (options.includeInternal && returnTo.startsWith("/chart")) {
|
|
const [path, query = ""] = returnTo.split("?", 2);
|
|
const params = new URLSearchParams(query);
|
|
params.set("includeInternal", "true");
|
|
returnTo = `${path}?${params.toString()}`;
|
|
}
|
|
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,
|
|
};
|
|
}
|