diff --git a/src/docker.js b/src/docker.js index c8f9403d2..c0c9d8476 100644 --- a/src/docker.js +++ b/src/docker.js @@ -30,7 +30,7 @@ exports = module.exports = { createVolume, removeVolume, clearVolume, - update + update, }; const apps = require('./apps.js'), diff --git a/src/mail.js b/src/mail.js index 11e39296f..08df62d1f 100644 --- a/src/mail.js +++ b/src/mail.js @@ -716,29 +716,26 @@ async function configureMail(mailFqdn, mailDomain, serviceConfig) { await shell.promises.exec('startMail', cmd); } -function getMailAuth(callback) { - assert.strictEqual(typeof callback, 'function'); +async function getMailAuth() { + const dockerInspect = util.promisify(docker.inspect); - docker.inspect('mail', function (error, data) { - if (error) return callback(error); + const data = await dockerInspect('mail'); + const ip = safe.query(data, 'NetworkSettings.Networks.cloudron.IPAddress'); + if (!ip) throw new BoxError(BoxError.MAIL_ERROR, 'Error querying mail server IP'); - const ip = safe.query(data, 'NetworkSettings.Networks.cloudron.IPAddress'); - if (!ip) return callback(new BoxError(BoxError.MAIL_ERROR, 'Error querying mail server IP')); + // extract the relay token for auth + const env = safe.query(data, 'Config.Env', null); + if (!env) throw new BoxError(BoxError.MAIL_ERROR, 'Error getting mail env'); + const tmp = env.find(function (e) { return e.indexOf('CLOUDRON_RELAY_TOKEN') === 0; }); + if (!tmp) throw new BoxError(BoxError.MAIL_ERROR, 'Error getting CLOUDRON_RELAY_TOKEN env var'); + const relayToken = tmp.slice('CLOUDRON_RELAY_TOKEN'.length + 1); // +1 for the = sign + if (!relayToken) throw new BoxError(BoxError.MAIL_ERROR, 'Error parsing CLOUDRON_RELAY_TOKEN'); - // extract the relay token for auth - const env = safe.query(data, 'Config.Env', null); - if (!env) return callback(new BoxError(BoxError.MAIL_ERROR, 'Error getting mail env')); - const tmp = env.find(function (e) { return e.indexOf('CLOUDRON_RELAY_TOKEN') === 0; }); - if (!tmp) return callback(new BoxError(BoxError.MAIL_ERROR, 'Error getting CLOUDRON_RELAY_TOKEN env var')); - const relayToken = tmp.slice('CLOUDRON_RELAY_TOKEN'.length + 1); // +1 for the = sign - if (!relayToken) return callback(new BoxError(BoxError.MAIL_ERROR, 'Error parsing CLOUDRON_RELAY_TOKEN')); - - callback(null, { - ip, - port: constants.INTERNAL_SMTP_PORT, - relayToken - }); - }); + return { + ip, + port: constants.INTERNAL_SMTP_PORT, + relayToken + }; } function restartMail(callback) { @@ -1134,7 +1131,7 @@ async function sendTestMail(domain, to) { const result = await getDomain(domain); if (!result) throw new BoxError(BoxError.NOT_FOUND, 'mail domain not found'); - await util.promisify(mailer.sendTestMail)(result.domain); + await mailer.sendTestMail(result.domain); } async function listMailboxes(domain, search, page, perPage) { diff --git a/src/mailer.js b/src/mailer.js index 77555d10f..0632fe19a 100644 --- a/src/mailer.js +++ b/src/mailer.js @@ -25,66 +25,54 @@ const assert = require('assert'), safe = require('safetydance'), settings = require('./settings.js'), translation = require('./translation.js'), - smtpTransport = require('nodemailer-smtp-transport'); - -const NOOP_CALLBACK = function (error) { if (error) debug(error); }; + smtpTransport = require('nodemailer-smtp-transport'), + util = require('util'); const MAIL_TEMPLATES_DIR = path.join(__dirname, 'mail_templates'); // This will collect the most common details required for notification emails -function getMailConfig(callback) { - assert.strictEqual(typeof callback, 'function'); +async function getMailConfig() { + const cloudronName = await settings.getCloudronName(); + const supportConfig = await settings.getSupportConfig(); - settings.getCloudronName(async function (error, cloudronName) { - if (error) debug('Error getting cloudron name: ', error); - - const supportConfig = await settings.getSupportConfig(); - - callback(null, { - cloudronName: cloudronName || '', - notificationFrom: `"${cloudronName}" `, - supportEmail: supportConfig.email - }); - }); + return { + cloudronName, + notificationFrom: `"${cloudronName}" `, + supportEmail: supportConfig.email + }; } -function sendMail(mailOptions, callback) { +async function sendMail(mailOptions) { assert.strictEqual(typeof mailOptions, 'object'); - callback = callback || NOOP_CALLBACK; if (process.env.BOX_ENV === 'test') { exports._mailQueue.push(mailOptions); - return callback(); + return; } - mail.getMailAuth(function (error, data) { - if (error) return callback(error); + const data = await mail.getMailAuth(); - var transport = nodemailer.createTransport(smtpTransport({ - host: data.ip, - port: data.port, - auth: { - user: mailOptions.authUser || `no-reply@${settings.dashboardDomain()}`, - pass: data.relayToken - } - })); + const transport = nodemailer.createTransport(smtpTransport({ + host: data.ip, + port: data.port, + auth: { + user: mailOptions.authUser || `no-reply@${settings.dashboardDomain()}`, + pass: data.relayToken + } + })); - transport.sendMail(mailOptions, function (error) { - if (error) return callback(new BoxError(BoxError.EXTERNAL_ERROR, error)); - - debug(`Email "${mailOptions.subject}" sent to ${mailOptions.to}`); - - callback(null); - }); - }); + const transportSendMail = util.promisify(transport.sendMail); + const [error] = await safe(transportSendMail(mailOptions)); + if (error) throw new BoxError(BoxError.EXTERNAL_ERROR, error); + debug(`Email "${mailOptions.subject}" sent to ${mailOptions.to}`); } function render(templateFile, params, translationAssets) { assert.strictEqual(typeof templateFile, 'string'); assert.strictEqual(typeof params, 'object'); - var content = null; - var raw = safe.fs.readFileSync(path.join(MAIL_TEMPLATES_DIR, templateFile), 'utf8'); + let content = null; + let raw = safe.fs.readFileSync(path.join(MAIL_TEMPLATES_DIR, templateFile), 'utf8'); if (raw === null) { debug(`Error loading ${templateFile}`); return ''; @@ -101,40 +89,35 @@ function render(templateFile, params, translationAssets) { return content; } -function sendInvite(user, invitor, inviteLink) { +async function sendInvite(user, invitor, inviteLink) { assert.strictEqual(typeof user, 'object'); assert.strictEqual(typeof invitor, 'object'); assert.strictEqual(typeof inviteLink, 'string'); - debug('Sending invite mail'); + const mailConfig = await getMailConfig(); + const translationAssets = await translation.getTranslations(); - getMailConfig(async function (error, mailConfig) { - if (error) return debug('Error getting mail details:', error); + const templateData = { + user: user.displayName || user.username || user.email, + webadminUrl: settings.dashboardOrigin(), + inviteLink: inviteLink, + invitor: invitor ? invitor.email : null, + cloudronName: mailConfig.cloudronName, + cloudronAvatarUrl: settings.dashboardOrigin() + '/api/v1/cloudron/avatar' + }; - const translationAssets = await translation.getTranslations(); + const mailOptions = { + from: mailConfig.notificationFrom, + to: user.fallbackEmail, + subject: ejs.render(translation.translate('{{ welcomeEmail.subject }}', translationAssets.translations || {}, translationAssets.fallback || {}), { cloudron: mailConfig.cloudronName }), + text: render('welcome_user-text.ejs', templateData, translationAssets), + html: render('welcome_user-html.ejs', templateData, translationAssets) + }; - const templateData = { - user: user.displayName || user.username || user.email, - webadminUrl: settings.dashboardOrigin(), - inviteLink: inviteLink, - invitor: invitor ? invitor.email : null, - cloudronName: mailConfig.cloudronName, - cloudronAvatarUrl: settings.dashboardOrigin() + '/api/v1/cloudron/avatar' - }; - - const mailOptions = { - from: mailConfig.notificationFrom, - to: user.fallbackEmail, - subject: ejs.render(translation.translate('{{ welcomeEmail.subject }}', translationAssets.translations || {}, translationAssets.fallback || {}), { cloudron: mailConfig.cloudronName }), - text: render('welcome_user-text.ejs', templateData, translationAssets), - html: render('welcome_user-html.ejs', templateData, translationAssets) - }; - - sendMail(mailOptions); - }); + await sendMail(mailOptions); } -function sendNewLoginLocation(user, loginLocation) { +async function sendNewLoginLocation(user, loginLocation) { assert.strictEqual(typeof user, 'object'); assert.strictEqual(typeof loginLocation, 'object'); @@ -145,120 +128,102 @@ function sendNewLoginLocation(user, loginLocation) { assert.strictEqual(typeof country, 'string'); assert.strictEqual(typeof city, 'string'); - debug('Sending new login location mail'); + const mailConfig = await getMailConfig(); + const translationAssets = await translation.getTranslations(); - getMailConfig(async function (error, mailConfig) { - if (error) return debug('Error getting mail details:', error); + const templateData = { + user: user.displayName || user.username || user.email, + ip, + userAgent: userAgent || 'unknown', + country, + city, + cloudronName: mailConfig.cloudronName, + cloudronAvatarUrl: settings.dashboardOrigin() + '/api/v1/cloudron/avatar' + }; - const translationAssets = await translation.getTranslations(); + const mailOptions = { + from: mailConfig.notificationFrom, + to: user.fallbackEmail, + subject: ejs.render(translation.translate('{{ newLoginEmail.subject }}', translationAssets.translations || {}, translationAssets.fallback || {}), { cloudron: mailConfig.cloudronName }), + text: render('new_login_location-text.ejs', templateData, translationAssets), + html: render('new_login_location-html.ejs', templateData, translationAssets) + }; - const templateData = { - user: user.displayName || user.username || user.email, - ip, - userAgent: userAgent || 'unknown', - country, - city, - cloudronName: mailConfig.cloudronName, - cloudronAvatarUrl: settings.dashboardOrigin() + '/api/v1/cloudron/avatar' - }; - - const mailOptions = { - from: mailConfig.notificationFrom, - to: user.fallbackEmail, - subject: ejs.render(translation.translate('{{ newLoginEmail.subject }}', translationAssets.translations || {}, translationAssets.fallback || {}), { cloudron: mailConfig.cloudronName }), - text: render('new_login_location-text.ejs', templateData, translationAssets), - html: render('new_login_location-html.ejs', templateData, translationAssets) - }; - - sendMail(mailOptions); - }); + await sendMail(mailOptions); } -function passwordReset(user) { +async function passwordReset(user) { assert.strictEqual(typeof user, 'object'); - debug('Sending mail for password reset for user %s.', user.email, user.id); + const mailConfig = await getMailConfig(); + const translationAssets = await translation.getTranslations(); - getMailConfig(async function (error, mailConfig) { - if (error) return debug('Error getting mail details:', error); + const templateData = { + user: user.displayName || user.username || user.email, + resetLink: `${settings.dashboardOrigin()}/login.html?resetToken=${user.resetToken}`, + cloudronName: mailConfig.cloudronName, + cloudronAvatarUrl: settings.dashboardOrigin() + '/api/v1/cloudron/avatar' + }; - const translationAssets = await translation.getTranslations(); + const mailOptions = { + from: mailConfig.notificationFrom, + to: user.fallbackEmail, + subject: ejs.render(translation.translate('{{ passwordResetEmail.subject }}', translationAssets.translations || {}, translationAssets.fallback || {}), { cloudron: mailConfig.cloudronName }), + text: render('password_reset-text.ejs', templateData, translationAssets), + html: render('password_reset-html.ejs', templateData, translationAssets) + }; - const templateData = { - user: user.displayName || user.username || user.email, - resetLink: `${settings.dashboardOrigin()}/login.html?resetToken=${user.resetToken}`, - cloudronName: mailConfig.cloudronName, - cloudronAvatarUrl: settings.dashboardOrigin() + '/api/v1/cloudron/avatar' - }; - - const mailOptions = { - from: mailConfig.notificationFrom, - to: user.fallbackEmail, - subject: ejs.render(translation.translate('{{ passwordResetEmail.subject }}', translationAssets.translations || {}, translationAssets.fallback || {}), { cloudron: mailConfig.cloudronName }), - text: render('password_reset-text.ejs', templateData, translationAssets), - html: render('password_reset-html.ejs', templateData, translationAssets) - }; - - sendMail(mailOptions); - }); + await sendMail(mailOptions); } -function backupFailed(mailTo, errorMessage, logUrl, callback) { +async function backupFailed(mailTo, errorMessage, logUrl) { assert.strictEqual(typeof mailTo, 'string'); assert.strictEqual(typeof errorMessage, 'string'); assert.strictEqual(typeof logUrl, 'string'); - assert(typeof callback === 'undefined' || typeof callback ==='function'); - getMailConfig(function (error, mailConfig) { - if (error) return debug('Error getting mail details:', error); + const mailConfig = await getMailConfig(); - var mailOptions = { - from: mailConfig.notificationFrom, - to: mailTo, - subject: `[${mailConfig.cloudronName}] Failed to backup`, - text: render('backup_failed.ejs', { cloudronName: mailConfig.cloudronName, message: errorMessage, logUrl, format: 'text' }) - }; + const mailOptions = { + from: mailConfig.notificationFrom, + to: mailTo, + subject: `[${mailConfig.cloudronName}] Failed to backup`, + text: render('backup_failed.ejs', { cloudronName: mailConfig.cloudronName, message: errorMessage, logUrl, format: 'text' }) + }; - sendMail(mailOptions, callback); - }); + await sendMail(mailOptions); } -function certificateRenewalError(mailTo, domain, message, callback) { +async function certificateRenewalError(mailTo, domain, message) { assert.strictEqual(typeof mailTo, 'string'); assert.strictEqual(typeof domain, 'string'); assert.strictEqual(typeof message, 'string'); - assert(typeof callback === 'undefined' || typeof callback ==='function'); - getMailConfig(function (error, mailConfig) { - if (error) return debug('Error getting mail details:', error); + const mailConfig = await getMailConfig(); - const mailOptions = { - from: mailConfig.notificationFrom, - to: mailTo, - subject: `[${mailConfig.cloudronName}] Certificate renewal error`, - text: render('certificate_renewal_error.ejs', { domain: domain, message: message, format: 'text' }) - }; + const mailOptions = { + from: mailConfig.notificationFrom, + to: mailTo, + subject: `[${mailConfig.cloudronName}] Certificate renewal error`, + text: render('certificate_renewal_error.ejs', { domain: domain, message: message, format: 'text' }) + }; - sendMail(mailOptions, callback); - }); + await sendMail(mailOptions); } -function sendTestMail(domain, email, callback) { +async function sendTestMail(domain, email, callback) { assert.strictEqual(typeof domain, 'string'); assert.strictEqual(typeof email, 'string'); assert.strictEqual(typeof callback, 'function'); - getMailConfig(function (error, mailConfig) { - if (error) return debug('Error getting mail details:', error); + const mailConfig = await getMailConfig(); - var mailOptions = { - authUser: `no-reply@${domain}`, - from: `"${mailConfig.cloudronName}" `, - to: email, - subject: `[${mailConfig.cloudronName}] Test Email`, - text: render('test.ejs', { cloudronName: mailConfig.cloudronName, format: 'text'}) - }; + const mailOptions = { + authUser: `no-reply@${domain}`, + from: `"${mailConfig.cloudronName}" `, + to: email, + subject: `[${mailConfig.cloudronName}] Test Email`, + text: render('test.ejs', { cloudronName: mailConfig.cloudronName, format: 'text'}) + }; - sendMail(mailOptions, callback); - }); + await sendMail(mailOptions, callback); } diff --git a/src/notifications.js b/src/notifications.js index 84c5f4e4a..ad7ba2fb5 100644 --- a/src/notifications.js +++ b/src/notifications.js @@ -183,7 +183,7 @@ async function certificateRenewalError(eventId, vhost, errorMessage) { const admins = await users.getAdmins(); for (const admin of admins) { - mailer.certificateRenewalError(admin.email, vhost, errorMessage); + await mailer.certificateRenewalError(admin.email, vhost, errorMessage); } } @@ -205,7 +205,7 @@ async function backupFailed(eventId, taskId, errorMessage) { const superadmins = await users.getSuperadmins(); for (const superadmin of superadmins) { - mailer.backupFailed(superadmin.email, errorMessage, `${settings.dashboardOrigin()}/logs.html?taskId=${taskId}`); + await mailer.backupFailed(superadmin.email, errorMessage, `${settings.dashboardOrigin()}/logs.html?taskId=${taskId}`); } } diff --git a/src/routes/branding.js b/src/routes/branding.js index 71e4390d3..4e96568e6 100644 --- a/src/routes/branding.js +++ b/src/routes/branding.js @@ -7,7 +7,7 @@ exports = module.exports = { getCloudronAvatar }; -var assert = require('assert'), +const assert = require('assert'), BoxError = require('../boxerror.js'), HttpError = require('connect-lastmile').HttpError, HttpSuccess = require('connect-lastmile').HttpSuccess, @@ -15,44 +15,40 @@ var assert = require('assert'), settings = require('../settings.js'), _ = require('underscore'); -function getFooter(req, res, next) { - settings.getFooter(function (error, footer) { - if (error) return next(BoxError.toHttpError(error)); +async function getFooter(req, res, next) { + const [error, footer] = await safe(settings.getFooter()); + if (error) return next(BoxError.toHttpError(error)); - next(new HttpSuccess(200, { footer })); - }); + next(new HttpSuccess(200, { footer })); } -function setFooter(req, res, next) { +async function setFooter(req, res, next) { assert.strictEqual(typeof req.body, 'object'); if (typeof req.body.footer !== 'string') return next(new HttpError(400, 'footer is required')); - settings.setFooter(req.body.footer, function (error) { - if (error) return next(BoxError.toHttpError(error)); + const [error] = await safe(settings.setFooter(req.body.footer)); + if (error) return next(BoxError.toHttpError(error)); - next(new HttpSuccess(200, {})); - }); + next(new HttpSuccess(200, {})); } -function setCloudronName(req, res, next) { +async function setCloudronName(req, res, next) { assert.strictEqual(typeof req.body, 'object'); if (typeof req.body.name !== 'string') return next(new HttpError(400, 'name is required')); - settings.setCloudronName(req.body.name, function (error) { - if (error) return next(BoxError.toHttpError(error)); + const [error] = await safe(settings.setCloudronName(req.body.name)); + if (error) return next(BoxError.toHttpError(error)); - next(new HttpSuccess(202, {})); - }); + next(new HttpSuccess(202, {})); } -function getCloudronName(req, res, next) { - settings.getCloudronName(function (error, name) { - if (error) return next(BoxError.toHttpError(error)); +async function getCloudronName(req, res, next) { + const [error, name] = await safe(settings.getCloudronName()); + if (error) return next(BoxError.toHttpError(error)); - next(new HttpSuccess(200, { name: name })); - }); + next(new HttpSuccess(200, { name })); } async function setAppstoreListingConfig(req, res, next) { diff --git a/src/routes/cloudron.js b/src/routes/cloudron.js index ade446c14..049af0dda 100644 --- a/src/routes/cloudron.js +++ b/src/routes/cloudron.js @@ -63,7 +63,7 @@ async function login(req, res, next) { eventlog.add(eventlog.ACTION_USER_LOGIN, auditSource, { userId: req.user.id, user: users.removePrivateFields(req.user) }); - await safe(users.notifyLoginLocation(req.user, ip, userAgent, auditSource)); + safe(users.notifyLoginLocation(req.user, ip, userAgent, auditSource)); next(new HttpSuccess(200, token)); } diff --git a/src/routes/mail.js b/src/routes/mail.js index 20495a8e3..442c5555e 100644 --- a/src/routes/mail.js +++ b/src/routes/mail.js @@ -120,17 +120,16 @@ async function setMailEnabled(req, res, next) { next(new HttpSuccess(202)); } -function sendTestMail(req, res, next) { +async function sendTestMail(req, res, next) { assert.strictEqual(typeof req.params.domain, 'string'); assert.strictEqual(typeof req.body, 'object'); if (!req.body.to || typeof req.body.to !== 'string') return next(new HttpError(400, 'to must be a non-empty string')); - mail.sendTestMail(req.params.domain, req.body.to, function (error) { - if (error) return next(BoxError.toHttpError(error)); + const [error] = await safe(mail.sendTestMail(req.params.domain, req.body.to)); + if (error) return next(BoxError.toHttpError(error)); - next(new HttpSuccess(202)); - }); + next(new HttpSuccess(202)); } async function listMailboxes(req, res, next) { diff --git a/src/settings.js b/src/settings.js index fb9f55b67..0242a2053 100644 --- a/src/settings.js +++ b/src/settings.js @@ -310,34 +310,23 @@ function getTimeZone(callback) { }); } -function getCloudronName(callback) { - assert.strictEqual(typeof callback, 'function'); - - settingsdb.get(exports.CLOUDRON_NAME_KEY, function (error, name) { - if (error && error.reason === BoxError.NOT_FOUND) return callback(null, gDefaults[exports.CLOUDRON_NAME_KEY]); - if (error) return callback(error); - - callback(null, name); - }); +async function getCloudronName() { + const name = await get(exports.CLOUDRON_NAME_KEY); + if (name === null) return gDefaults[exports.CLOUDRON_NAME_KEY]; + return name; } -function setCloudronName(name, callback) { +async function setCloudronName(name) { assert.strictEqual(typeof name, 'string'); - assert.strictEqual(typeof callback, 'function'); - if (!name) return callback(new BoxError(BoxError.BAD_FIELD, 'name is empty', { field: 'name' })); + if (!name) throw new BoxError(BoxError.BAD_FIELD, 'name is empty', { field: 'name' }); // some arbitrary restrictions (for sake of ui layout) // if this is changed, adjust dashboard/branding.html - if (name.length > 64) return callback(new BoxError(BoxError.BAD_FIELD, 'name cannot exceed 64 characters', { field: 'name' })); + if (name.length > 64) throw new BoxError(BoxError.BAD_FIELD, 'name cannot exceed 64 characters', { field: 'name' }); - settingsdb.set(exports.CLOUDRON_NAME_KEY, name, function (error) { - if (error) return callback(error); - - notifyChange(exports.CLOUDRON_NAME_KEY, name); - - return callback(null); - }); + await set(exports.CLOUDRON_NAME_KEY, name); + notifyChange(exports.CLOUDRON_NAME_KEY, name); } async function getCloudronAvatar() { @@ -831,28 +820,17 @@ function setApiServerOrigin(origin, callback) { }); } -function getFooter(callback) { - assert.strictEqual(typeof callback, 'function'); - - settingsdb.get(exports.FOOTER_KEY, function (error, value) { - if (error && error.reason === BoxError.NOT_FOUND) return callback(null, gDefaults[exports.FOOTER_KEY]); - if (error) return callback(error); - - callback(null, value); - }); +async function getFooter() { + const value = await get(exports.FOOTER_KEY); + if (value === null) return gDefaults[exports.FOOTER_KEY]; + return value; } -function setFooter(footer, callback) { +async function setFooter(footer) { assert.strictEqual(typeof footer, 'string'); - assert.strictEqual(typeof callback, 'function'); - settingsdb.set(exports.FOOTER_KEY, footer, function (error) { - if (error) return callback(error); - - notifyChange(exports.FOOTER_KEY, footer); - - callback(null); - }); + await set(exports.FOOTER_KEY, footer); + notifyChange(exports.FOOTER_KEY, footer); } function provider() { return gCache.provider; } diff --git a/src/test/settings-test.js b/src/test/settings-test.js index a477d4ea2..39d141a9e 100644 --- a/src/test/settings-test.js +++ b/src/test/settings-test.js @@ -62,12 +62,9 @@ describe('Settings', function () { }); }); - it ('can get default cloudron name', function (done) { - settings.getCloudronName(function (error, name) { - expect(error).to.be(null); - expect(name).to.be('Cloudron'); - done(); - }); + it ('can get default cloudron name', async function () { + const name = await settings.getCloudronName(); + expect(name).to.be('Cloudron'); }); it('can get default cloudron avatar', async function () { diff --git a/src/users.js b/src/users.js index 573897801..7847af371 100644 --- a/src/users.js +++ b/src/users.js @@ -556,7 +556,7 @@ async function sendPasswordResetByIdentifier(identifier, auditSource) { await update(user, { resetToken, resetTokenCreationTime }, auditSource); - mailer.passwordReset(user); + await mailer.passwordReset(user); } async function notifyLoginLocation(user, ip, userAgent, auditSource) { @@ -596,7 +596,7 @@ async function notifyLoginLocation(user, ip, userAgent, auditSource) { await update(user, { loginLocations }, auditSource); - mailer.sendNewLoginLocation(user, newLoginLocation); + await mailer.sendNewLoginLocation(user, newLoginLocation); } async function setPassword(user, newPassword, auditSource) { @@ -677,7 +677,7 @@ async function sendInvite(user, options) { const directoryConfig = await settings.getDirectoryConfig(); - mailer.sendInvite(user, options.invitor || null, inviteLink(user, directoryConfig)); + await mailer.sendInvite(user, options.invitor || null, inviteLink(user, directoryConfig)); } async function setupAccount(user, data, auditSource) {