From df4df2a4aae9f613dba09470bdd7b54845e59ef6 Mon Sep 17 00:00:00 2001 From: Naz Date: Thu, 27 May 2021 13:12:28 +0400 Subject: [PATCH] Added admin email for UpdateChecker's alerts refs https://github.com/TryGhost/Team/issues/726 - When UpdateCheck service sends a notification with "type: 'alert'" an email goes out to admin users with the "message" content of the notification. - This functionality is aimed to handling critical messages like urgent instance updates - Next step will be getting as much of the update check code extracted into a "service" and then moved out of Ghost's codebase --- core/server/update-check.js | 30 ++++++++++++++ .../update-check/update_check_spec.js | 39 +++++++++++++++++++ 2 files changed, 69 insertions(+) diff --git a/core/server/update-check.js b/core/server/update-check.js index c3d447b5f4..84dbcef22a 100644 --- a/core/server/update-check.js +++ b/core/server/update-check.js @@ -16,6 +16,7 @@ const _ = require('lodash'); const url = require('url'); const debug = require('ghost-ignition').debug('update-check'); const api = require('./api').v2; +const GhostMailer = require('./services/mail').GhostMailer; const config = require('../shared/config'); const urlUtils = require('./../shared/url-utils'); const errors = require('@tryghost/errors'); @@ -23,9 +24,12 @@ const i18n = require('../shared/i18n'); const logging = require('../shared/logging'); const request = require('./lib/request'); const ghostVersion = require('./lib/ghost-version'); + const internal = {context: {internal: true}}; const allowedCheckEnvironments = ['development', 'production']; +const ghostMailer = new GhostMailer(); + function nextCheckTimestamp() { const now = Math.round(new Date().getTime() / 1000); return now + (24 * 3600); @@ -67,6 +71,17 @@ async function createCustomNotification(notification) { return; } + const {users} = await api.users.browse(Object.assign({ + limit: 'all', + include: ['roles'] + }, internal)); + + const adminEmails = users + .filter(user => ['Owner', 'Administrator'].includes(user.roles[0].name)) + .map(user => user.email); + + const siteUrl = config.get('url'); + for (const message of notification.messages) { const toAdd = { // @NOTE: the update check service returns "0" or "1" (https://github.com/TryGhost/UpdateCheck/issues/43) @@ -80,6 +95,21 @@ async function createCustomNotification(notification) { message: message.content }; + if (toAdd.type === 'alert') { + for (const email of adminEmails) { + try { + ghostMailer.send({ + to: email, + subject: `Action required: Critical alert from Ghost instance ${siteUrl}`, + html: toAdd.message, + forceTextContent: true + }); + } catch (err) { + logging.err(err); + } + } + } + debug('Add Custom Notification', toAdd); await api.notifications.add({notifications: [toAdd]}, {context: {internal: true}}); } diff --git a/test/regression/update-check/update_check_spec.js b/test/regression/update-check/update_check_spec.js index efbf3f2679..6d841dd2ee 100644 --- a/test/regression/update-check/update_check_spec.js +++ b/test/regression/update-check/update_check_spec.js @@ -9,6 +9,7 @@ const testUtils = require('../../utils'); const configUtils = require('../../utils/configUtils'); const packageInfo = require('../../../package.json'); const api = require('../../../core/server/api').v2; +const mailService = require('../../../core/server/services/mail/'); let updateCheck = rewire('../../../core/server/update-check'); let ghostVersion = rewire('../../../core/server/lib/ghost-version'); @@ -17,6 +18,7 @@ describe('Update Check', function () { beforeEach(function () { updateCheck = rewire('../../../core/server/update-check'); ghostVersion = rewire('../../../core/server/lib/ghost-version'); + sinon.stub(mailService.GhostMailer.prototype, 'send').resolves('Stubed email response'); }); afterEach(function () { @@ -322,6 +324,43 @@ describe('Update Check', function () { }) .catch(done); }); + + it('should send an email for critical notification', async function () { + const createCustomNotification = updateCheck.__get__('createCustomNotification'); + + const notification = { + id: 1, + custom: 1, + messages: [{ + id: uuid.v4(), + version: 'custom1', + content: '

Critical message. Upgrade your site!

', + dismissible: false, + top: true, + type: 'alert' + }] + }; + + await createCustomNotification(notification); + + mailService.GhostMailer.prototype.send.called.should.be.true(); + mailService.GhostMailer.prototype.send.args[0][0].to.should.equal('jbloggs@example.com'); + mailService.GhostMailer.prototype.send.args[0][0].subject.should.equal('Action required: Critical alert from Ghost instance http://127.0.0.1:2369'); + mailService.GhostMailer.prototype.send.args[0][0].html.should.equal('

Critical message. Upgrade your site!

'); + mailService.GhostMailer.prototype.send.args[0][0].forceTextContent.should.equal(true); + + const results = await api.notifications.browse(testUtils.context.internal); + + should.exist(results); + should.exist(results.notifications); + results.notifications.length.should.eql(1); + + const targetNotification = _.find(results.notifications, {id: notification.messages[0].id}); + should.exist(targetNotification); + targetNotification.dismissible.should.eql(notification.messages[0].dismissible); + targetNotification.top.should.eql(notification.messages[0].top); + targetNotification.type.should.eql(notification.messages[0].type); + }); }); describe('fn: updateCheckResponse', function () {