maestro/src/engine/browser-session-auth.ts
2026-06-03 05:08:00 +00:00

48 lines
1.5 KiB
TypeScript

// Browser session profile owner enforcement.
//
// Extracted as a pure function so the fail-closed contract can be unit
// tested without spinning up a Worker. The Worker still performs the
// surrounding decrypt flow inline (see src/worker.ts) — only the
// owner-vs-job assertion lives here.
//
// Fail-closed contract: a job must have a non-empty ownerId AND that
// ownerId must equal the profile's ownerId. A null/undefined/empty
// job.ownerId always fails (no implicit "skip the check").
import type { BrowserSessionRepo } from '../db/browser-session-repo.js';
export interface OwnerCheckProfile {
id: number;
ownerId: string;
}
export interface OwnerCheckJob {
id: string;
ownerId: string | null | undefined;
}
/**
* Throw if the profile cannot be used by this job. Audits every failure.
*
* Returns void on success (caller may continue to decrypt). Throws
* Error('Browser session profile owner mismatch') on any rejection.
*/
export function assertProfileOwner(
profile: OwnerCheckProfile,
job: OwnerCheckJob,
sessRepo: Pick<BrowserSessionRepo, 'audit'>,
): void {
if (!job.ownerId || profile.ownerId !== job.ownerId) {
sessRepo.audit({
actorUserId: job.ownerId ?? null,
ownerId: profile.ownerId,
profileId: profile.id,
action: 'use',
result: 'error',
reason: `owner mismatch (job.owner=${job.ownerId ?? 'null'} vs profile.owner=${profile.ownerId})`,
jobId: job.id,
});
throw new Error('Browser session profile owner mismatch');
}
}