1
0
forked from baron/baron-sso
Files
baron-sso/adminfront/src/features/users/orgChartPicker.ts

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