import { timingSafeEqual } from 'node:crypto'; import type { AppConfig } from '../config.js'; import type { IdentityPort } from '../core/ports.js'; import { parseUlid, type Ulid } from '../core/ids.js'; export class StandaloneIdentityAdapter implements IdentityPort { constructor(private readonly config: AppConfig) {} async resolve(token: string | undefined, correlationId: Ulid) { const permissionsByAccess = { employee: ['staff:attendance:read_own', 'staff:shift:read', 'staff:payslip:read_own'], manager: [ 'staff:employee:read', 'staff:employee:write', 'staff:attendance:read_own', 'staff:attendance:manage', 'staff:attendance:approve', 'staff:shift:read', 'staff:shift:confirm', 'staff:org_unit:read', 'staff:org_unit:write', 'staff:metric:read', 'staff:metric:write', 'staff:pay_rule:read', 'staff:pay_rule:write', 'staff:payroll:read', 'staff:payroll:calculate', 'staff:payroll:approve', 'staff:payout:write', ], terminal: ['staff:attendance:clock'], } as const; let access = this.config.STANDALONE_ACCESS; let actorId = this.config.STANDALONE_ACTOR_ID; if (this.config.STANDALONE_MANAGER_TOKEN && this.config.STANDALONE_TERMINAL_TOKEN) { if (secureTokenEquals(token, this.config.STANDALONE_MANAGER_TOKEN)) { access = 'manager'; } else if (secureTokenEquals(token, this.config.STANDALONE_TERMINAL_TOKEN)) { access = 'terminal'; actorId = this.config.STANDALONE_TERMINAL_ACTOR_ID ?? actorId; } else { throw new Error('Invalid standalone access token'); } } else if (access === 'manager') { throw new Error('Standalone access tokens are not configured'); } return { tenantId: parseUlid(this.config.STANDALONE_TENANT_ID), actorId: parseUlid(actorId), permissions: new Set(['staff:health:read', ...permissionsByAccess[access]]), correlationId, }; } } function secureTokenEquals(candidate: string | undefined, expected: string): boolean { if (!candidate) return false; const candidateBuffer = Buffer.from(candidate, 'utf8'); const expectedBuffer = Buffer.from(expected, 'utf8'); return ( candidateBuffer.length === expectedBuffer.length && timingSafeEqual(candidateBuffer, expectedBuffer) ); } export class PlatformIdentityAdapter implements IdentityPort { async resolve(): Promise { throw new Error( 'Platform JWT adapter is intentionally pending the Core gateway/JWT integration contract', ); } }