Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 10 additions & 13 deletions app/emailer.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,17 @@ module.exports = {
Promise.reject("smtp not configured.") :
transporter.verify().then(_ => config.smtp.host || "???");
},
// returns a promise after sending the email and logging the trailing arguments
// returns an empty promise after sending the email.
// see https://nodemailer.com/message/
sendMail(email, ...logArgs) {
return transporter.sendMail(email).then(info => {
const date = dt.getNow().toString();
const logMessage = `Sent email ${date}:\n` + JSON.stringify(logArgs, null, " ");
console.log(logMessage);
return Promise.resolve(true); // don't log to the file for now; conflicts with php/node paths
// tbd: would it be better to log to console only, and configure docker with "local"
// it does compression, and auto rotation.
// https://docs.docker.com/config/containers/logging/configure/
// const logFile = config.email.logfile;
// return !logFile ? Promise.resolve(true) :
// fsp.writeFile(config.email.logfile, logMessage+"\n", {flag: 'a'});
sendMail(email) {
return transporter.sendMail(email).then(_ => {
return Promise.resolve(true);
});
},

// returns true if the passed email address was on the block list.
isDeadLetter(address) {
const parts = (process.env.DEAD_LETTERS || "").split(";").filter(Boolean);
return parts.some(p => address.endsWith(p));
}
};
17 changes: 12 additions & 5 deletions app/endpoints/manage_event.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,10 @@ function updateEvent(evt, values, statusList) {
// promises a sent email
// evt is a CalEvent; dailies is an array of CalDaily.
function sendConfirmationEmail(evt, dailies) {
const isDeadLetter = emailer.isDeadLetter(evt.email);

const url = config.site.url('addevent', `edit-${evt.id}-${evt.password}`);
const subject = `Shift2Bikes Secret URL for ${evt.title}`;
const subject = !isDeadLetter ? `Shift2Bikes Secret URL for ${evt.title}` : `Shift2Bikes Blocked ${evt.title}`;
console.debug("sending confirmation for", url);

const dates = dailies
Expand All @@ -157,20 +159,25 @@ function sendConfirmationEmail(evt, dailies) {
help: config.site.helpPage(),
support: support.address || support, // a string or object
});
console.debug("confirmation email body:\n", body);
return emailer.sendMail({
subject,
text: body,
to: {
to: !isDeadLetter ? {
name: evt.name,
address: evt.email,
},
} : config.email.moderator,
from: config.email.sender,
replyTo: config.email.support,
bcc: config.email.moderator, // backup copy for debugging and/or moderating
// html
// attachments
}, evt.name, evt.email, evt.title, url);
}).then(_ => {
// log after sending:
const date = dt.getNow().toString();
const header = JSON.stringify([evt.name, evt.email, evt.title, url], null, " ");
const logMessage = [date, header, body].join("\n");
console.log(!isDeadLetter ? "Sent email" : "** Blocked ** email", logMessage);
});
}

// read json into a javascript object.
Expand Down
12 changes: 12 additions & 0 deletions app/test/manage_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const { CalDaily } = require("../models/calDaily");
const { describe, it, beforeEach, afterEach } = require("node:test");
const assert = require("node:assert/strict");
const request = require('supertest');
const emailer = require("../emailer");
//
const manage_api = '/api/manage_event.php';

Expand Down Expand Up @@ -69,6 +70,17 @@ describe("managing events", () => {
// console.log(res.body);
});
});
it("tests dead letters", () => {
process.env.DEAD_LETTERS = "example.com";
assert.equal(emailer.isDeadLetter("bob@example.com"), true);
assert.equal(emailer.isDeadLetter("bob@example.net"), false);
assert.equal(emailer.isDeadLetter("bob@example.org"), false);
process.env.DEAD_LETTERS = "example.net;.org";
assert.equal(emailer.isDeadLetter("bob@example.com"), false);
assert.equal(emailer.isDeadLetter("bob@example.net"), true);
assert.equal(emailer.isDeadLetter("bob@shifty.org"), true);
delete process.env.DEAD_LETTERS;
});
it("fail creation when missing required fields", () => {
// each time substitute a field value that should fail
const pairs = [
Expand Down
1 change: 1 addition & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ services:
- SMTP_HOST
- SMTP_USER
- SMTP_PASS
- DEAD_LETTERS
# by default the node image will run "node"
# https://github.com/nodejs/docker-node/blob/main/docker-entrypoint.sh
# override to run npm start; which auto installs from package-lock.json
Expand Down