This commit is contained in:
1 parent
172c3a5a0e
commit
922d24cac7
5 files changed
+157
-19
No files matched your search
+12
-6
@@ -209,7 +209,7 @@ function companyNameFromEmail(email) {
|
||||
})[domain] || '';
|
||||
}
|
||||
|
||||
function normalizeClaims(claims) {
|
||||
function normalizeClaims(claims, env) {
|
||||
const profile = claims.profile && typeof claims.profile === 'object' ? claims.profile : {};
|
||||
const custom = claims.customAttributes || claims.custom_attributes || {};
|
||||
const profilePhones = Array.isArray(profile.phones) ? profile.phones : (Array.isArray(claims.phones) ? claims.phones : []);
|
||||
@@ -225,9 +225,15 @@ function normalizeClaims(claims) {
|
||||
const tenantIds = tenantList.map((tenant) => typeof tenant === 'string' ? tenant : (tenant.id || tenant.tenantId || tenant.key || '')).filter(Boolean);
|
||||
if (tenantId && !tenantIds.includes(tenantId)) tenantIds.unshift(tenantId);
|
||||
const primaryTenant = tenantList.find((tenant) => typeof tenant !== 'string' && (tenant.id || tenant.tenantId) === tenantId) || tenantList[0] || {};
|
||||
let organizationRoots = [];
|
||||
try { organizationRoots = JSON.parse(env?.ORG_CONTEXT_TENANT_IDS || '[]'); } catch {}
|
||||
if (!Array.isArray(organizationRoots)) organizationRoots = [];
|
||||
const path = [...(Array.isArray(primaryTenant.ancestors) ? primaryTenant.ancestors : []), primaryTenant].filter((node) => node && typeof node === 'object');
|
||||
const rootIndex = path.findIndex((node) => organizationRoots.includes(node.id) || ['ROOT', 'COMPANY_GROUP'].includes(String(node.type || '').toUpperCase()));
|
||||
const companyTenant = rootIndex >= 0 ? path[rootIndex + 1] : path.find((node) => String(node.type || '').toUpperCase() === 'COMPANY');
|
||||
const tenantName = claims.tenant_name || claims.tenantName || custom.tenant_name || custom.tenantName || (typeof primaryTenant === 'object' ? (primaryTenant.name || primaryTenant.tenantName || primaryTenant.displayName || primaryTenant.label || '') : '');
|
||||
const email = claims.email || profile.email || '';
|
||||
const explicitCompany = custom.company || claims.company || claims.organization || profile.company || claims.affiliation || '';
|
||||
const explicitCompany = (rootIndex >= 0 ? companyTenant?.name : '') || custom.requester_affiliation || custom.company || claims.requester_affiliation || claims.company || claims.organization || profile.requester_affiliation || profile.company || claims.affiliation || companyTenant?.name || '';
|
||||
const company = explicitCompany || companyNameFromEmail(email);
|
||||
const subject = claims.sub || claims.subject || claims.userId || claims.user_id || '';
|
||||
return {
|
||||
@@ -247,9 +253,9 @@ function normalizeClaims(claims) {
|
||||
company,
|
||||
companySource: explicitCompany ? 'sso' : 'email',
|
||||
familyCompany: custom.familyCompany || custom.family_company || claims.familyCompany || claims.affiliation || '',
|
||||
department: custom.team || custom.department || claims.department || profile.department || (!['COMPANY', 'COMPANY_GROUP', 'ROOT'].includes(String(primaryTenant.type || primaryTenant.orgUnitType || '').toUpperCase()) ? primaryTenant.name : '') || '',
|
||||
position: custom.position || claims.position || profile.position || primaryTenant.position || primaryTenant.title || '',
|
||||
grade: custom.grade || claims.grade || profile.grade || primaryTenant.grade || ''
|
||||
department: custom.requester_department || custom.team || custom.department || claims.requester_department || claims.department || profile.requester_department || profile.department || (primaryTenant !== companyTenant && !organizationRoots.includes(primaryTenant.id) && !['COMPANY_GROUP', 'ROOT'].includes(String(primaryTenant.type || primaryTenant.orgUnitType || '').toUpperCase()) ? primaryTenant.name : '') || '',
|
||||
position: custom.requester_position || custom.position || claims.requester_position || claims.position || claims.jobTitle || claims.job_title || profile.requester_position || profile.position || profile.jobTitle || primaryTenant.position || primaryTenant.jobTitle || primaryTenant.title || '',
|
||||
grade: custom.requester_grade || custom.grade || claims.requester_grade || claims.grade || profile.requester_grade || profile.grade || primaryTenant.grade || ''
|
||||
};
|
||||
}
|
||||
|
||||
@@ -311,7 +317,7 @@ async function finishLogin(request, env) {
|
||||
const userInfo = await userResponse.json().catch(() => ({}));
|
||||
if (userResponse.ok) claims = { ...claims, ...userInfo };
|
||||
}
|
||||
const user = normalizeClaims(claims);
|
||||
const user = normalizeClaims(claims, env);
|
||||
if (!user.ssoSubject) return oauthError(env, 'sso_subject_missing', '', 502);
|
||||
const sessionPayload = base64url(JSON.stringify({ user, expiresAt: Date.now() + Number(env.SESSION_TTL_SECONDS || 3600) * 1000 }));
|
||||
const session = await signValue(sessionPayload, env.SESSION_SECRET);
|
||||
|
||||
+13
-7
@@ -6,7 +6,6 @@ const text = (value) => typeof value === 'string' ? value.trim() : '';
|
||||
const truthy = (value) => value === true || value === 1 || value === '1' || value === 'true';
|
||||
const kind = (node) => text(node.orgUnitType || node.type).toUpperCase();
|
||||
const isCompany = (node) => kind(node) === 'COMPANY' || text(node.type).toUpperCase() === 'COMPANY';
|
||||
const isDepartment = (node) => ['DEPARTMENT', 'DIVISION', 'TEAM', 'ORG_UNIT', 'BUSINESS_UNIT'].includes(kind(node)) || text(node.type).toUpperCase() === 'DEPARTMENT';
|
||||
|
||||
export function orgContextConfig(env) {
|
||||
if (!truthy(env.ORG_CONTEXT_ENABLED)) return null;
|
||||
@@ -30,9 +29,10 @@ export function orgContextConfig(env) {
|
||||
return { endpoint: endpoint.toString(), roots, keyId, keySecret, ttl: ttl * 1000 };
|
||||
}
|
||||
|
||||
export function buildOrgDirectory(payloads) {
|
||||
export function buildOrgDirectory(payloads, rootIds = []) {
|
||||
const tenants = new Map();
|
||||
const memberships = new Map();
|
||||
const roots = new Set((rootIds.length ? rootIds : payloads.map((payload) => payload?.scope?.tenantId)).map(text).filter(Boolean).map((id) => id.toLowerCase()));
|
||||
function visit(node, parentId) {
|
||||
if (!node || typeof node !== 'object' || !text(node.id)) throw new Error('org_context_invalid_tenant');
|
||||
const id = text(node.id).toLowerCase();
|
||||
@@ -77,9 +77,14 @@ export function buildOrgDirectory(payloads) {
|
||||
chain.unshift(node);
|
||||
node = tenants.get(node.parentId);
|
||||
}
|
||||
const company = [...chain].reverse().find(isCompany);
|
||||
const department = [...chain].reverse().find(isDepartment);
|
||||
const departmentName = text(department?.name) || text(appointment.member.department);
|
||||
// The configured roots are classification/group tenants. Their direct child
|
||||
// is the real company, even when every descendant has generic COMPANY type.
|
||||
const rootIndex = chain.findIndex((item) => roots.has(item.id) || ['ROOT', 'COMPANY_GROUP'].includes(text(item.type).toUpperCase()));
|
||||
const company = rootIndex >= 0 ? chain[rootIndex + 1] : chain.find(isCompany);
|
||||
const departmentChain = company ? chain.slice(chain.indexOf(company) + 1) : [];
|
||||
const department = departmentChain.at(-1);
|
||||
const memberDepartment = text(appointment.member.department);
|
||||
const departmentName = text(department?.name) || (memberDepartment !== text(company?.name) && !chain.some((item) => roots.has(item.id) && text(item.name) === memberDepartment) ? memberDepartment : '');
|
||||
const value = {
|
||||
userId: appointment.member.id,
|
||||
tenantId: appointment.tenantId,
|
||||
@@ -88,7 +93,7 @@ export function buildOrgDirectory(payloads) {
|
||||
company: text(company?.name),
|
||||
departmentTenantId: department?.id || '',
|
||||
department: departmentName,
|
||||
departmentPath: chain.filter(isDepartment).map((item) => text(item.name)).filter(Boolean).join(' / '),
|
||||
departmentPath: departmentChain.map((item) => text(item.name)).filter(Boolean).join(' / '),
|
||||
isPrimary: truthy(appointment.member.isPrimary),
|
||||
phone: text(appointment.member.phone),
|
||||
position: text(appointment.member.position || appointment.member.jobTitle),
|
||||
@@ -147,12 +152,13 @@ export async function getOrgDirectory(env, { fetchImpl = fetch, now = Date.now }
|
||||
if (text(payload?.scope?.tenantId).toLowerCase() !== config.roots[index]) throw new Error('org_context_scope_mismatch');
|
||||
responses[index] = payload;
|
||||
} catch (error) {
|
||||
if (controller.signal.aborted) throw new Error('org_context_timeout');
|
||||
if (/^org_context_/.test(error?.message || '')) throw error;
|
||||
throw new Error('org_context_request_failed');
|
||||
} finally { clearTimeout(timeout); }
|
||||
}
|
||||
}));
|
||||
const directory = buildOrgDirectory(responses);
|
||||
const directory = buildOrgDirectory(responses, config.roots);
|
||||
if (directory.byUser.size === 0) throw new Error('org_context_members_missing');
|
||||
if (cache.size >= 8) cache.delete(cache.keys().next().value);
|
||||
cache.set(cacheKey, { directory, expiresAt: now() + config.ttl });
|
||||
|
||||
Reference in new issue
Block a user