fix(backend): harden runtime config and verification access

This commit is contained in:
zouantchaw
2026-03-19 16:36:28 +01:00
parent 8d0ef309e6
commit 2f25d10368
15 changed files with 262 additions and 14 deletions

View File

@@ -1,6 +1,6 @@
import { AppError } from '../lib/errors.js';
import { isDatabaseConfigured, query, withTransaction } from './db.js';
import { requireTenantContext } from './actor-context.js';
import { loadActorContext, requireTenantContext } from './actor-context.js';
import { invokeVertexMultimodalModel } from './llm.js';
export const VerificationStatus = Object.freeze({
@@ -95,7 +95,11 @@ async function processVerificationJobInMemory(verificationId) {
}
function accessMode() {
return process.env.VERIFICATION_ACCESS_MODE || 'authenticated';
const mode = `${process.env.VERIFICATION_ACCESS_MODE || 'tenant'}`.trim().toLowerCase();
if (mode === 'owner' || mode === 'tenant' || mode === 'authenticated') {
return mode;
}
return 'tenant';
}
function providerTimeoutMs() {
@@ -156,12 +160,27 @@ function toPublicJob(row) {
};
}
function assertAccess(row, actorUid) {
if (accessMode() === 'authenticated') {
async function assertAccess(row, actorUid) {
if (row.owner_user_id === actorUid) {
return;
}
if (row.owner_user_id !== actorUid) {
throw new AppError('FORBIDDEN', 'Not allowed to access this verification', 403);
const mode = accessMode();
if (mode === 'authenticated') {
return;
}
if (mode === 'owner' || !row.tenant_id) {
throw new AppError('FORBIDDEN', 'Not allowed to access this verification', 403, {
verificationId: row.id,
});
}
const actorContext = await loadActorContext(actorUid);
if (actorContext.tenant?.tenantId !== row.tenant_id) {
throw new AppError('FORBIDDEN', 'Not allowed to access this verification', 403, {
verificationId: row.id,
});
}
}
@@ -614,19 +633,19 @@ export async function createVerificationJob({ actorUid, payload }) {
export async function getVerificationJob(verificationId, actorUid) {
if (useMemoryStore()) {
const job = loadMemoryJob(verificationId);
assertAccess(job, actorUid);
await assertAccess(job, actorUid);
return toPublicJob(job);
}
const job = await loadJob(verificationId);
assertAccess(job, actorUid);
await assertAccess(job, actorUid);
return toPublicJob(job);
}
export async function reviewVerificationJob(verificationId, actorUid, review) {
if (useMemoryStore()) {
const job = loadMemoryJob(verificationId);
assertAccess(job, actorUid);
await assertAccess(job, actorUid);
if (HUMAN_TERMINAL_STATUSES.has(job.status)) {
throw new AppError('CONFLICT', 'Verification already finalized', 409, {
verificationId,
@@ -668,7 +687,7 @@ export async function reviewVerificationJob(verificationId, actorUid, review) {
}
const job = result.rows[0];
assertAccess(job, actorUid);
await assertAccess(job, actorUid);
if (HUMAN_TERMINAL_STATUSES.has(job.status)) {
throw new AppError('CONFLICT', 'Verification already finalized', 409, {
verificationId,
@@ -735,7 +754,7 @@ export async function reviewVerificationJob(verificationId, actorUid, review) {
export async function retryVerificationJob(verificationId, actorUid) {
if (useMemoryStore()) {
const job = loadMemoryJob(verificationId);
assertAccess(job, actorUid);
await assertAccess(job, actorUid);
if (job.status === VerificationStatus.PROCESSING) {
throw new AppError('CONFLICT', 'Cannot retry while verification is processing', 409, {
verificationId,
@@ -774,7 +793,7 @@ export async function retryVerificationJob(verificationId, actorUid) {
}
const job = result.rows[0];
assertAccess(job, actorUid);
await assertAccess(job, actorUid);
if (job.status === VerificationStatus.PROCESSING) {
throw new AppError('CONFLICT', 'Cannot retry while verification is processing', 409, {
verificationId,