1const __authParityEvent = Symbol("verified-invocation-event"); 2// Flexylead authorization adapter for Functions' reserved invocation context. 3// Business identity is never inferred from event.auth, actor flags or headers. 4function requireTrustedPrincipal(event, context) { 5 const principal = event?.requestContext?.authorizer?.principal; 6 const identity = context?.invocation; 7 const tenant = String(process.env.FLEXYLEAD_AUTH_ORGANIZATION_SLUG || 'default'); 8 if (!principal || identity?.kind !== 'authenticated' 9 || principal.issuer !== `apteva:auth:${tenant}` 10 || principal.claims?.tenant_id !== tenant 11 || !principal.project_id || principal.project_id !== identity.project_id 12 || principal.subject !== identity.subject || principal.issuer !== identity.issuer 13 || !/^[1-9]\d*$/.test(String(principal.subject))) { 14 throw __authParityDenied('Authenticated Function context required.', 401); 15 } 16 return principal; 17} 18 19async function trustedActor(event, context, claimed = {}, { required = false } = {}) { 20 if (!event?.requestContext?.authorizer && context?.invocation?.kind !== 'authenticated') { 21 if (required) throw __authParityDenied('Authenticated Function context required.', 401); 22 return null; // Existing explicitly authenticated service workflows remain separate. 23 } 24 const principal = requireTrustedPrincipal(event, context); 25 const organizationId = Number(process.env.FLEXYLEAD_AUTH_ORGANIZATION_ID || 1); 26 const payload = await context.call('auth', 'auth_users_get_context', { 27 organization_id: organizationId, id: Number(principal.subject), 28 }); 29 const user = payload?.user || {}; 30 const id = String(user.id ?? ''); 31 const email = String(user.email ?? '').trim().toLowerCase(); 32 if (id !== String(principal.subject) || !email || user.status !== 'active' 33 || (user.organization_id != null && Number(user.organization_id) !== organizationId)) { 34 throw __authParityDenied('User is inactive or outside the authenticated organization.'); 35 } 36 if ((claimed?.auth_user_id && String(claimed.auth_user_id).trim() !== id) 37 || (claimed?.email && String(claimed.email).trim().toLowerCase() !== email)) { 38 throw __authParityDenied('Command actor does not match the authenticated user.'); 39 } 40 const names = value => (Array.isArray(value) ? value : []).map(String); 41 const roles = names(payload?.authorization?.roles); 42 return { id, auth_user_id: id, email, organization_id: String(organizationId), 43 roles, role: roles[0] || '', permissions: names(payload?.authorization?.permissions) }; 44} 45 46function __authParityDenied(message, statusCode = 403) { 47 const error = new Error(message); error.statusCode = statusCode; return error; 48} 49 50const AUDIT_PERMISSION = 'commercial:administration:manage'; 51const ADMIN_ROLES = new Set(['admin', 'owner', 'super_admin', 'project_admin']); 52 53export default async function handler(event = {}, context = null) { 54 context = { ...context, [__authParityEvent]: event }; 55 56 if (!context?.call) throw new Error('app_context_unavailable'); 57 const body = event?.body && typeof event.body === 'object' ? event.body : event; 58 assertAuditor(await trustedActor(event, context, body?.actor, { required: true })); 59 const checks = await Promise.all([ 60 runCheck(context, 'duplicate_active_opportunities', `