diff --git a/src/mailer.js b/src/mailer.js index 41c165dc1..07f8f10a5 100644 --- a/src/mailer.js +++ b/src/mailer.js @@ -202,6 +202,29 @@ function mailUserEventToAdmins(user, event) { }); } +function mailUserEvent(mailTo, user, event) { + assert.strictEqual(typeof mailTo, 'string'); + assert.strictEqual(typeof user, 'object'); + assert.strictEqual(typeof event, 'string'); + + // skip sending mail to the same user + if (mailTo === user.email) return; + + getMailConfig(function (error, mailConfig) { + if (error) return debug('Error getting mail details:', error); + + var mailOptions = { + from: mailConfig.notificationFrom, + to: mailTo, + subject: util.format('[%s] %s %s', mailConfig.cloudronName, user.username || user.fallbackEmail || user.email, event), + text: render('user_event.ejs', { user: user, event: event, format: 'text' }), + }; + + enqueue(mailOptions); + }); +} + + function sendInvite(user, invitor) { assert.strictEqual(typeof user, 'object'); assert(typeof invitor === 'object'); @@ -238,11 +261,11 @@ function sendInvite(user, invitor) { }); } -function userAdded(emailTo, user) { - assert.strictEqual(typeof emailTo, 'string'); +function userAdded(mailTo, user) { + assert.strictEqual(typeof mailTo, 'string'); assert.strictEqual(typeof user, 'object'); - debug(`userAdded: Sending mail for added users ${user.fallbackEmail} to ${emailTo}`); + debug(`userAdded: Sending mail for added users ${user.fallbackEmail} to ${mailTo}`); getMailConfig(function (error, mailConfig) { if (error) return debug('Error getting mail details:', error); @@ -261,7 +284,7 @@ function userAdded(emailTo, user) { var mailOptions = { from: mailConfig.notificationFrom, - to: emailTo, + to: mailTo, subject: util.format('[%s] User %s added', mailConfig.cloudronName, user.fallbackEmail), text: render('user_added.ejs', templateDataText), html: render('user_added.ejs', templateDataHTML) @@ -271,21 +294,23 @@ function userAdded(emailTo, user) { }); } -function userRemoved(user) { +function userRemoved(mailTo, user) { + assert.strictEqual(typeof mailTo, 'string'); assert.strictEqual(typeof user, 'object'); - debug('Sending mail for userRemoved.', user.id, user.email); + debug('Sending mail for userRemoved.', user.id, user.username, user.email); - mailUserEventToAdmins(user, 'was removed'); + mailUserEvent(mailTo, user, 'was removed'); } -function adminChanged(user, admin) { +function adminChanged(mailTo, user, isAdmin) { + assert.strictEqual(typeof mailTo, 'string'); assert.strictEqual(typeof user, 'object'); - assert.strictEqual(typeof admin, 'boolean'); + assert.strictEqual(typeof isAdmin, 'boolean'); debug('Sending mail for adminChanged'); - mailUserEventToAdmins(user, admin ? 'is now an admin' : 'is no more an admin'); + mailUserEvent(mailTo, user, isAdmin ? 'is now an admin' : 'is no more an admin'); } function passwordReset(user) { @@ -321,7 +346,8 @@ function passwordReset(user) { }); } -function appDied(app) { +function appDied(mailTo, app) { + assert.strictEqual(typeof mailTo, 'string'); assert.strictEqual(typeof app, 'object'); debug('Sending mail for app %s @ %s died', app.id, app.fqdn); @@ -331,7 +357,7 @@ function appDied(app) { var mailOptions = { from: mailConfig.notificationFrom, - to: config.provider() === 'caas' ? 'support@cloudron.io' : mailConfig.adminEmails.join(', '), + to: mailTo, subject: util.format('[%s] App %s is down', mailConfig.cloudronName, app.fqdn), text: render('app_down.ejs', { title: app.manifest.title, appFqdn: app.fqdn, format: 'text' }) }; @@ -499,7 +525,8 @@ function certificateRenewalError(domain, message) { }); } -function oomEvent(program, context) { +function oomEvent(mailTo, program, context) { + assert.strictEqual(typeof mailTo, 'string'); assert.strictEqual(typeof program, 'string'); assert.strictEqual(typeof context, 'string'); @@ -508,7 +535,7 @@ function oomEvent(program, context) { var mailOptions = { from: mailConfig.notificationFrom, - to: config.provider() === 'caas' ? 'support@cloudron.io' : mailConfig.adminEmails.join(', '), + to: mailTo, subject: util.format('[%s] %s exited unexpectedly', mailConfig.cloudronName, program), text: render('oom_event.ejs', { cloudronName: mailConfig.cloudronName, program: program, context: context, format: 'text' }) }; diff --git a/src/notifications.js b/src/notifications.js index 6d7da78ca..6b29c6bcc 100644 --- a/src/notifications.js +++ b/src/notifications.js @@ -10,12 +10,15 @@ exports = module.exports = { // specialized notifications userAdded: userAdded, + userRemoved: userRemoved, + adminChanged: adminChanged, oomEvent: oomEvent, appDied: appDied }; var assert = require('assert'), async = require('async'), + config = require('./config.js'), DatabaseError = require('./databaseerror.js'), debug = require('debug')('box:notifications'), mailer = require('./mailer.js'), @@ -136,6 +139,31 @@ function userAdded(user, callback) { }, callback); } +function userRemoved(user, callback) { + assert.strictEqual(typeof user, 'object'); + assert(typeof callback === 'undefined' || typeof callback === 'function'); + + callback = callback || NOOP_CALLBACK; + + actionForAllAdmins(function (admin, callback) { + mailer.userRemoved(admin.email, user); + add(admin.id, 'User removed', `User ${user.username || user.email || user.fallbackEmail} was removed`, '', callback); + }, callback); +} + +function adminChanged(user, isAdmin, callback) { + assert.strictEqual(typeof user, 'object'); + assert.strictEqual(typeof isAdmin, 'boolean'); + assert(typeof callback === 'undefined' || typeof callback === 'function'); + + callback = callback || NOOP_CALLBACK; + + actionForAllAdmins(function (admin, callback) { + mailer.adminChanged(admin.email, user, isAdmin); + add(admin.id, 'Admin status change', `User ${user.username || user.email || user.fallbackEmail} ${isAdmin ? 'is now an admin' : 'is no more an admin'}`, '/#/users', callback); + }, callback); +} + function oomEvent(program, context, callback) { assert.strictEqual(typeof program, 'string'); assert.strictEqual(typeof context, 'object'); @@ -143,8 +171,11 @@ function oomEvent(program, context, callback) { callback = callback || NOOP_CALLBACK; + // also send us a notification mail + if (config.provider() === 'caas') mailer.oomEvent('support@cloudron.io', program, JSON.stringify(context, null, 4)); + actionForAllAdmins(function (admin, callback) { - mailer.oomEvent(program, JSON.stringify(context, null, 4)); + mailer.oomEvent(admin.email, program, JSON.stringify(context, null, 4)); var message; if (context.app) message = `The application ${context.app.manifest.title} with id ${context.app.id} ran out of memory.`; @@ -160,8 +191,11 @@ function appDied(app, callback) { callback = callback || NOOP_CALLBACK; + // also send us a notification mail + if (config.provider() === 'caas') mailer.appDied('support@cloudron.io', app); + actionForAllAdmins(function (admin, callback) { - mailer.appDied(app); + mailer.appDied(admin.email, app); add(admin.id, `App ${app.fqdn} died`, `The application ${app.manifest.title} installed at ${app.fqdn} is not responding.`, '/#/apps', callback); }, callback); } diff --git a/src/users.js b/src/users.js index fa26e666f..052defbe8 100644 --- a/src/users.js +++ b/src/users.js @@ -299,7 +299,7 @@ function removeUser(userId, auditSource, callback) { callback(); - mailer.userRemoved(user); + notifications.userRemoved(user); }); }); } @@ -416,7 +416,7 @@ function updateUser(userId, data, auditSource, callback) { eventlog.add(eventlog.ACTION_USER_UPDATE, auditSource, { userId: userId, user: removePrivateFields(result) }); - if ((result.admin && !oldUser.admin) || (!result.admin && oldUser.admin)) mailer.adminChanged(result, result.admin); + if ((result.admin && !oldUser.admin) || (!result.admin && oldUser.admin)) notifications.adminChanged(result, result.admin); }); }); });