72 lines
2.6 KiB
TypeScript
72 lines
2.6 KiB
TypeScript
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<never> {
|
|
throw new Error(
|
|
'Platform JWT adapter is intentionally pending the Core gateway/JWT integration contract',
|
|
);
|
|
}
|
|
}
|