mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-11-11 16:35:47 +01:00
feat: displaying one-click unsubscribe link in email footer (#8024)
closes #8016
This commit is contained in:
@@ -44,6 +44,7 @@
|
||||
"notif.chat.unsub.info": "This chat notification was sent to you due to your subscription settings.",
|
||||
|
||||
"notif.post.unsub.info": "This post notification was sent to you due to your subscription settings.",
|
||||
"notif.post.unsub.one-click": "Alternatively, unsubscribe from future emails like this, by clicking",
|
||||
|
||||
"notif.cta": "To the forum",
|
||||
"notif.cta-new-reply": "View Post",
|
||||
@@ -54,6 +55,8 @@
|
||||
"test.text1": "This is a test email to verify that the emailer is set up correctly for your NodeBB.",
|
||||
|
||||
"unsub.cta": "Click here to alter those settings",
|
||||
"unsubscribe": "unsubscribe",
|
||||
"unsub.success": "You will no longer receive emails from the <strong>%1</strong> mailing list",
|
||||
|
||||
"banned.subject": "You have been banned from %1",
|
||||
"banned.text1": "The user %1 has been banned from %2.",
|
||||
|
||||
@@ -166,24 +166,11 @@ function addSoundSettings(userData, soundsMapping) {
|
||||
});
|
||||
}
|
||||
|
||||
const unsubscribable = ['digest', 'notification'];
|
||||
const jwtVerifyAsync = util.promisify(function (token, callback) {
|
||||
jwt.verify(token, nconf.get('secret'), (err, payload) => callback(err, payload));
|
||||
});
|
||||
|
||||
settingsController.unsubscribe = async function (req, res) {
|
||||
if (!req.params.token) {
|
||||
return res.sendStatus(404);
|
||||
}
|
||||
let payload;
|
||||
try {
|
||||
payload = await jwtVerifyAsync(req.params.token);
|
||||
if (!payload || (payload.template !== 'notification' && payload.template !== 'digest')) {
|
||||
return res.sendStatus(404);
|
||||
}
|
||||
} catch (err) {
|
||||
return res.sendStatus(403);
|
||||
}
|
||||
try {
|
||||
const doUnsubscribe = async (payload) => {
|
||||
if (payload.template === 'digest') {
|
||||
await Promise.all([
|
||||
user.setSetting(payload.uid, 'dailyDigestFreq', 'off'),
|
||||
@@ -193,6 +180,42 @@ settingsController.unsubscribe = async function (req, res) {
|
||||
const current = await db.getObjectField('user:' + payload.uid + ':settings', 'notificationType_' + payload.type);
|
||||
await user.setSetting(payload.uid, 'notificationType_' + payload.type, (current === 'notificationemail' ? 'notification' : 'none'));
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
settingsController.unsubscribe = async (req, res) => {
|
||||
let payload;
|
||||
try {
|
||||
payload = await jwtVerifyAsync(req.params.token);
|
||||
if (!payload || !unsubscribable.includes(payload.template)) {
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
throw new Error(err);
|
||||
}
|
||||
|
||||
try {
|
||||
await doUnsubscribe(payload);
|
||||
res.render('unsubscribe', {
|
||||
payload: payload,
|
||||
});
|
||||
} catch (err) {
|
||||
throw new Error(err);
|
||||
}
|
||||
};
|
||||
|
||||
settingsController.unsubscribePost = async function (req, res) {
|
||||
let payload;
|
||||
try {
|
||||
payload = await jwtVerifyAsync(req.params.token);
|
||||
if (!payload || !unsubscribable.includes(payload.template)) {
|
||||
return res.sendStatus(404);
|
||||
}
|
||||
} catch (err) {
|
||||
return res.sendStatus(403);
|
||||
}
|
||||
try {
|
||||
await doUnsubscribe(payload);
|
||||
res.sendStatus(200);
|
||||
} catch (err) {
|
||||
winston.error('[settings/unsubscribe] One-click unsubscribe failed with error: ' + err.message);
|
||||
|
||||
@@ -199,11 +199,7 @@ Emailer.send = async function (template, uid, params) {
|
||||
|
||||
Emailer.sendToEmail = async function (template, email, language, params) {
|
||||
const lang = language || meta.config.defaultLang || 'en-GB';
|
||||
|
||||
// Add some default email headers based on local configuration
|
||||
params.headers = { 'List-Id': '<' + [template, params.uid, getHostname()].join('.') + '>',
|
||||
'List-Unsubscribe': '<' + [nconf.get('url'), 'uid', params.uid, 'settings'].join('/') + '>',
|
||||
...params.headers };
|
||||
const unsubscribable = ['digest', 'notification'];
|
||||
|
||||
// Digests and notifications can be one-click unsubbed
|
||||
let payload = {
|
||||
@@ -211,15 +207,22 @@ Emailer.sendToEmail = async function (template, email, language, params) {
|
||||
uid: params.uid,
|
||||
};
|
||||
|
||||
if (template === 'digest' || template === 'notification') {
|
||||
if (unsubscribable.includes(template)) {
|
||||
if (template === 'notification') {
|
||||
payload.type = params.notification.type;
|
||||
}
|
||||
payload = jwt.sign(payload, nconf.get('secret'), {
|
||||
expiresIn: '30d',
|
||||
});
|
||||
params.headers['List-Unsubscribe'] = '<' + [nconf.get('url'), 'email', 'unsubscribe', payload].join('/') + '>';
|
||||
params.headers['List-Unsubscribe-Post'] = 'List-Unsubscribe=One-Click';
|
||||
|
||||
const unsubUrl = [nconf.get('url'), 'email', 'unsubscribe', payload].join('/');
|
||||
params.headers = {
|
||||
'List-Id': '<' + [template, params.uid, getHostname()].join('.') + '>',
|
||||
'List-Unsubscribe': '<' + unsubUrl + '>',
|
||||
'List-Unsubscribe-Post': 'List-Unsubscribe=One-Click',
|
||||
...params.headers,
|
||||
};
|
||||
params.unsubUrl = unsubUrl;
|
||||
}
|
||||
|
||||
const result = await Plugins.fireHook('filter:email.params', {
|
||||
|
||||
@@ -32,8 +32,10 @@ function mainRoutes(app, middleware, controllers) {
|
||||
setupPageRoute(app, '/reset/:code?', middleware, [middleware.delayLoading], controllers.reset);
|
||||
setupPageRoute(app, '/tos', middleware, [], controllers.termsOfUse);
|
||||
|
||||
setupPageRoute(app, '/email/unsubscribe/:token', middleware, [], controllers.accounts.settings.unsubscribe);
|
||||
app.post('/email/unsubscribe/:token', controllers.accounts.settings.unsubscribePost);
|
||||
|
||||
app.post('/compose', middleware.applyCSRF, controllers.composer.post);
|
||||
app.post('/email/unsubscribe/:token', controllers.accounts.settings.unsubscribe);
|
||||
}
|
||||
|
||||
function modRoutes(app, middleware, controllers) {
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
<br><br>
|
||||
<!-- IF showUnsubscribe -->
|
||||
[[email:notif.post.unsub.info]] <a href="{url}/uid/{uid}/settings">[[email:unsub.cta]]</a>.
|
||||
<br />[[email:notif.post.unsub.one-click]] <a href="{unsubUrl}">[[email:unsubscribe]]</a>.
|
||||
<!-- ENDIF showUnsubscribe -->
|
||||
<br><br>
|
||||
</td>
|
||||
|
||||
Reference in New Issue
Block a user