@accounter/server
Version:
Accounter GraphQL server
91 lines • 5.09 kB
JavaScript
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