UNPKG

@accounter/server

Version:
91 lines 5.09 kB
import { GraphQLError } from 'graphql'; import { isSelfIssuedSenderEvidence } from '../helpers/email-ingestion-issuer.helper.js'; import { EmailIngestionControlProvider } from '../providers/email-ingestion-control.provider.js'; const GRANT_TTL_MS = 5 * 60 * 1000; // 5 minutes const requestIngestControl = async (_parent, { input }, { injector }) => { const control = injector.get(EmailIngestionControlProvider); try { const aliasResult = await control.resolveAlias(input.recipientAlias); if (!aliasResult.found) { return { __typename: 'CommonError', message: `${aliasResult.reason}: ${input.recipientAlias}`, }; } // Recognize the issuing business from sender evidence, then bind it into the // grant so the ingest step can attribute documents without trusting input. // Tries every candidate address (incl. quoted forwarded-header addresses) so // manually forwarded mail still resolves to the real issuer. const { businessId, config } = await control.recognizeBusinessFromEvidence(aliasResult.tenantId, input.senderEvidence ?? undefined); // A positive *external* recognition wins over the self-issued heuristic: if a // real counterparty business (anything other than the tenant itself) matched a // sender-evidence address, attribute the documents to it and skip the // self-issued check entirely. Without this, a supplier invoice that reaches the // tenant through its own forwarding group — which rewrites the quoted `From` to // a forwarder address and so collapses the single-address self-issued heuristic // onto that forwarder — would be wrongly bound to the tenant and dropped at // ingest, even though recognition had already identified the real supplier. const externalBusinessId = businessId && businessId !== aliasResult.tenantId ? businessId : null; // Self-issued detection (fallback, only when no external issuer was found): // confirmation emails for invoices the tenant issued through its own invoicing // platform (e.g. Morning/greeninvoice). Binding the tenant's own business as the // issuer lets the ingest step recognize it and skip it (see // EmailIngestionIngestProvider.performIngest). Mirrors the legacy gmail-listener // self-issued skip. The recipient alias is passed as the tenant's own inbound // address so a mailing-list forward that rewrites `From` into that alias is // still recognized as self — keeping the check tenant-agnostic. const selfIssued = externalBusinessId === null && isSelfIssuedSenderEvidence(input.senderEvidence ?? undefined, [input.recipientAlias]); const issuingBusinessId = externalBusinessId ?? (selfIssued ? aliasResult.tenantId : businessId); const expiresAt = new Date(Date.now() + GRANT_TTL_MS); const grant = await control.issueGrant({ tenantId: aliasResult.tenantId, messageId: input.messageId, rawMessageHash: input.rawMessageHash, expiresAt, correlationId: input.correlationId ?? undefined, businessId: issuingBusinessId, }); return { __typename: 'IngestControlDecision', id: grant.decisionId, tenantId: grant.tenantId, decisionId: grant.decisionId, auditId: grant.auditId, grant: { id: grant.jti, jti: grant.jti, tenantId: grant.tenantId, action: grant.action, expiresAt: grant.expiresAt.toISOString(), }, // null signals "no business recognized" → gateway applies default // treatment. We return config only for a recognized external business. // When none is recognized the email is not inserted at ingest — a // self-issued/tenant-matched email is QUARANTINED (recorded and // reprocessable, never silently dropped; see // EmailIngestionIngestProvider.performIngest) — so we return no treatment // config either, sparing the gateway needless document work (link-fetch / // body→PDF) for a document that will not be inserted. businessEmailConfig: externalBusinessId ? { businessId: externalBusinessId, internalEmailLinks: config.internalEmailLinks ?? null, emailBody: config.emailBody ?? null, attachments: config.attachments ?? null, } : null, }; } catch (err) { throw new GraphQLError('Failed to process ingest control request', { extensions: { code: 'INTERNAL_SERVER_ERROR', cause: err }, }); } }; export const emailIngestionControlResolver = { Mutation: { requestIngestControl, }, }; //# sourceMappingURL=email-ingestion-control.resolver.js.map