feat: initial public release (MAESTRO)
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
// 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');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user