mirror of
https://github.com/wekan/wekan.git
synced 2026-11-07 11:02:33 -05:00
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.
44 lines
2.1 KiB
JavaScript
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 };
|