@accounter/server
Version:
Accounter GraphQL server
38 lines • 1.56 kB
JavaScript
/**
* Run `fn` inside a single transaction whose RLS context is pinned to the given
* tenant. `set_config(..., true)` is transaction-local (SET LOCAL), so the
* context is cleared on COMMIT/ROLLBACK and never leaks to the pooled
* connection's next user.
*
* Control-plane and gateway-initiated ingestion present an auth session with an
* empty businessId, so TenantAwareDBClient (which derives the tenant from the
* auth session) cannot be used. The authoritative tenant instead comes from the
* resolved alias / cryptographically-validated grant and is pinned here so the
* `tenant_isolation` RLS policies (USING / WITH CHECK `owner_id =
* get_current_business_id()`) on the email_ingestion_* tables are satisfied —
* these tables use FORCE ROW LEVEL SECURITY, so even the table owner is subject
* to them and the raw pool cannot bypass the policy.
*/
export async function withTenantContext(pool, tenantId, fn) {
const client = await pool.connect();
try {
await client.query('BEGIN');
await client.query("SELECT set_config('app.current_business_id', $1, true)", [tenantId]);
const result = await fn(client);
await client.query('COMMIT');
return result;
}
catch (err) {
try {
await client.query('ROLLBACK');
}
catch {
// Ignore rollback failures (e.g. the connection is already broken).
}
throw err;
}
finally {
client.release();
}
}
//# sourceMappingURL=email-ingestion-tenant-context.helper.js.map