@accounter/server
Version:
Accounter GraphQL server
91 lines • 3.59 kB
JavaScript
import { __decorate, __metadata } from "tslib";
import { Injectable, Scope } from 'graphql-modules';
import { sql } from '@pgtyped/runtime';
import { TenantAwareDBClient } from '../../app-providers/tenant-db-client.js';
// Postgres unique_violation — raised by the partial-unique index that allows at
// most one active alias per (case-insensitive) address.
const PG_UNIQUE_VIOLATION = '23505';
const getAliases = sql `
SELECT id, alias, owner_id, is_active, created_at, updated_at
FROM accounter_schema.email_ingestion_alias_routing
WHERE owner_id = ANY($ownerIds)
ORDER BY alias ASC
`;
const insertAlias = sql `
INSERT INTO accounter_schema.email_ingestion_alias_routing (alias, owner_id)
VALUES ($alias, $ownerId)
RETURNING id, alias, owner_id, is_active, created_at, updated_at
`;
const updateAliasActive = sql `
UPDATE accounter_schema.email_ingestion_alias_routing
SET is_active = $isActive
WHERE id = $id
RETURNING id, alias, owner_id, is_active, created_at, updated_at
`;
function isUniqueViolation(err) {
return (typeof err === 'object' &&
err !== null &&
'code' in err &&
err.code === PG_UNIQUE_VIOLATION);
}
/**
* Manages alias→owner routing rows for v2 email ingestion.
*
* Writes go through TenantAwareDBClient so the `tenant_isolation_write` RLS
* policy (`owner_id = get_current_business_id()`) is enforced as defense in
* depth on top of the resolver's membership check. The request must therefore
* be scoped to the target business (X-Business-Scope) — the same convention as
* every other tenant write in the app.
*
* Reads use an explicit `owner_id` scope filter because the table's
* `alias_resolution_select` policy is `USING (TRUE)` (alias resolution must work
* before a tenant context exists), so RLS does not constrain SELECTs here.
*/
let EmailIngestionAliasProvider = class EmailIngestionAliasProvider {
db;
constructor(db) {
this.db = db;
}
async createAlias(alias, ownerId) {
try {
const rows = await insertAlias.run({ alias, ownerId }, this.db);
return { success: true, alias: rows[0] };
}
catch (err) {
if (isUniqueViolation(err)) {
// Generic message: the conflicting active alias may belong to another
// tenant (the global unique index spans tenants); do not leak ownership.
return { success: false, message: `Alias "${alias}" is already in use` };
}
throw err;
}
}
async setAliasActive(id, isActive) {
try {
const rows = await updateAliasActive.run({ id, isActive }, this.db);
if (rows.length === 0) {
return { success: false, message: 'Alias not found or not authorized' };
}
return { success: true, alias: rows[0] };
}
catch (err) {
if (isUniqueViolation(err)) {
return { success: false, message: 'An active alias already exists for this address' };
}
throw err;
}
}
async listAliases(ownerIds) {
if (ownerIds.length === 0) {
return [];
}
const rows = await getAliases.run({ ownerIds: [...ownerIds] }, this.db);
return rows;
}
};
EmailIngestionAliasProvider = __decorate([
Injectable({ scope: Scope.Operation }),
__metadata("design:paramtypes", [TenantAwareDBClient])
], EmailIngestionAliasProvider);
export { EmailIngestionAliasProvider };
//# sourceMappingURL=email-ingestion-alias.provider.js.map