Files
Lauri Ojansivu 79ac301675 Email bounces with Amazon SES, Problems -> Email, and runtime tests for sending and receiving.
Email that comes back is handled in WeKan instead of landing in the
administrator's own inbox. Admin Panel -> E-mail -> Bounces: hard bounces
(a mistyped invited address), soft bounces, automatic replies and spam
complaints, each with actions done after a count - list it, stop emailing
the address, tell who invited it, delete the invitation, ignore - as
listmonk and Discourse do it, and the list of addresses that bounced, which
an admin clears to email them again. Amazon SES Bounce and Complaint
notifications and mail SES receives arrive through SNS at
/api/inbound-email/ses, signature-checked against Amazon's certificate and
accepted only from the topics the Admin Panel lists. Delivery status
notifications and out-of-office replies arriving by IMAP, a provider
webhook or a Sandstorm grain are bounces too, never cards.

Admin Panel -> Problems -> Email shows what went wrong sending or
receiving: a send the mail server refused or could not reach, an IMAP
mailbox that cannot be read (never with its password), an unreadable
email, an email for a board that no longer takes email. Attempts stay in
Problems -> Security. The inbound email endpoints no longer need the whole
REST API turned on.

Runtime tests run WeKan's own transports against local SMTP and IMAP
servers over TCP, a real Sandstorm Maildir, the webhook over HTTP and
signed SNS messages; UI tests drive the Email and Chat views, the
email-to-card settings and the Bounces page. They found and this fixes:
every whole-message parse failed in the server bundle (nodemailer's
addressparser resolved to its ESM build), the Chat view's drag and drop
never started (dragscroll took the mouse) and dropped a card into the
target's previous list, and the Chat panes did not follow a change to a
right-to-left language.

Thanks to xet7 !

Fixes #794,
Fixes #3948.
2026-10-13 18:27:25 +03:00

44 lines
2.1 KiB
JavaScript

'use strict';
// Admin Panel -> Problems -> Email: what went wrong sending or receiving
// email (#794). One row per KIND of problem, folded like every other stream
// (server/lib/eventLogFold.js), so a mail server that is down for an hour is
// one row with a count, not three thousand.
//
// These are operational problems a real user meets - the mail server refused
// a send, an IMAP password stopped working, an email arrived for a board that
// no longer takes email - so they are not security events. ATTEMPTS (an
// unknown board address, a wrong webhook secret, a flood) are still recorded
// as security events under 'authn.inbound-email'.
//
// Pure: tests/emailToCard.test.cjs checks the events without a server.
const KINDS = Object.freeze({
'send-failed': { category: 'mail-send', bleed: 'MailSendFailed', severity: 'high', action: 'failed' },
'imap-failed': { category: 'mail-receive', bleed: 'MailImapFailed', severity: 'high', action: 'failed' },
'receive-unreadable': { category: 'mail-receive', bleed: 'MailUnreadable', severity: 'medium', action: 'failed' },
'delivery-refused': { category: 'mail-receive', bleed: 'MailRefused', severity: 'low', action: 'refused' },
});
// Anything that looks like a credential is cut out of the detail: an SMTP or
// IMAP error can quote the command that failed, and LOGIN carries the password.
function scrub(text) {
return String(text || '')
.replace(/\b(LOGIN|AUTH(?:ENTICATE)?\s+\S+)\s+\S+(\s+\S+)?/gi, '$1 [hidden]')
.replace(/(pass(?:word)?|secret|token)\s*[=:]\s*\S+/gi, '$1=[hidden]')
.replace(/[\r\n]+/g, ' ')
.slice(0, 500);
}
// The event for one problem. `kind` is a key of KINDS; anything else is not a
// mail problem and returns null, so a caller cannot invent a row.
function mailProblemEvent(kind, { source = 'mail', detail = '', userId } = {}) {
const spec = KINDS[kind];
if (!spec) return null;
const evt = { stream: 'mail', ...spec, source: String(source).slice(0, 100), detail: scrub(detail) };
if (userId) evt.userId = String(userId);
return evt;
}
module.exports = { KINDS, mailProblemEvent, scrub };