diff --git a/src/apps.js b/src/apps.js index ffddc03db..cf2d99cd6 100644 --- a/src/apps.js +++ b/src/apps.js @@ -1078,7 +1078,9 @@ function setMailbox(app, data, auditSource, callback) { if (!hasMailAddon(app.manifest)) return callback(new BoxError(BoxError.BAD_FIELD, 'App does not use mail addons')); - mail.getDomain(mailboxDomain, function (error) { + const getDomainFunc = util.callbackify(mail.getDomain); + + getDomainFunc(mailboxDomain, function (error) { if (error) return callback(error); if (mailboxName) { diff --git a/src/ldap.js b/src/ldap.js index ca374d395..90c7268f3 100644 --- a/src/ldap.js +++ b/src/ldap.js @@ -19,7 +19,8 @@ const assert = require('assert'), mailboxdb = require('./mailboxdb.js'), safe = require('safetydance'), services = require('./services.js'), - users = require('./users.js'); + users = require('./users.js'), + util = require('util'); var gServer = null; @@ -534,7 +535,9 @@ function authenticateUserMailbox(req, res, next) { var parts = email.split('@'); if (parts.length !== 2) return next(new ldap.NoSuchObjectError(req.dn.toString())); - mail.getDomain(parts[1], function (error, domain) { + const getDomainFunc = util.callbackify(mail.getDomain); + + getDomainFunc(parts[1], function (error, domain) { if (error && error.reason === BoxError.NOT_FOUND) return next(new ldap.NoSuchObjectError(req.dn.toString())); if (error) return next(new ldap.OperationsError(error.message)); @@ -671,7 +674,9 @@ function authenticateMailAddon(req, res, next) { const addonId = req.dn.rdns[1].attrs.ou.value.toLowerCase(); // 'sendmail' or 'recvmail' if (addonId !== 'sendmail' && addonId !== 'recvmail') return next(new ldap.OperationsError('Invalid DN')); - mail.getDomain(parts[1], function (error, domain) { + const getDomainFunc = util.callbackify(mail.getDomain); + + getDomainFunc(parts[1], function (error, domain) { if (error && error.reason === BoxError.NOT_FOUND) return next(new ldap.NoSuchObjectError(req.dn.toString())); if (error) return next(new ldap.OperationsError(error.message)); diff --git a/src/mail.js b/src/mail.js index 2e6be1a28..6bbe0834c 100644 --- a/src/mail.js +++ b/src/mail.js @@ -8,7 +8,7 @@ exports = module.exports = { setLocation, // triggers the change task changeLocation, // does the actual changing - getDomains, + listDomains, getDomain, clearDomains, @@ -66,6 +66,7 @@ const assert = require('assert'), BoxError = require('./boxerror.js'), cloudron = require('./cloudron.js'), constants = require('./constants.js'), + database = require('./database.js'), debug = require('debug')('box:mail'), dns = require('./native-dns.js'), docker = require('./docker.js'), @@ -74,7 +75,6 @@ const assert = require('assert'), hat = require('./hat.js'), infra = require('./infra_version.js'), mailboxdb = require('./mailboxdb.js'), - maildb = require('./maildb.js'), mailer = require('./mailer.js'), net = require('net'), nodemailer = require('nodemailer'), @@ -91,6 +91,7 @@ const assert = require('assert'), system = require('./system.js'), tasks = require('./tasks.js'), users = require('./users.js'), + util = require('util'), validator = require('validator'), _ = require('underscore'); @@ -98,6 +99,24 @@ const DNS_OPTIONS = { timeout: 5000 }; var NOOP_CALLBACK = function (error) { if (error) debug(error); }; const REMOVE_MAILBOX = path.join(__dirname, 'scripts/rmmailbox.sh'); +const MAILDB_FIELDS = [ 'domain', 'enabled', 'mailFromValidation', 'catchAllJson', 'relayJson', 'dkimSelector', 'bannerJson' ].join(','); + +function postProcess(data) { + data.enabled = !!data.enabled; // int to boolean + data.mailFromValidation = !!data.mailFromValidation; // int to boolean + + data.catchAll = safe.JSON.parse(data.catchAllJson) || [ ]; + delete data.catchAllJson; + + data.relay = safe.JSON.parse(data.relayJson) || { provider: 'cloudron-smtp' }; + delete data.relayJson; + + data.banner = safe.JSON.parse(data.bannerJson) || { text: null, html: null }; + delete data.bannerJson; + + return data; +} + function validateName(name) { assert.strictEqual(typeof name, 'string'); @@ -183,19 +202,17 @@ function checkSmtpRelay(relay, callback) { }); } -function verifyRelay(relay, callback) { +async function verifyRelay(relay) { assert.strictEqual(typeof relay, 'object'); - assert.strictEqual(typeof callback, 'function'); // we used to verify cloudron-smtp with checkOutboundPort25 but that is unreliable given that we just // randomly select some smtp server - if (relay.provider === 'cloudron-smtp' || relay.provider === 'noop') return callback(); + if (relay.provider === 'cloudron-smtp' || relay.provider === 'noop') return; - checkSmtpRelay(relay, function (error) { - if (error) return callback(new BoxError(BoxError.BAD_FIELD, error.message)); + const checkSmtpRelayAsync = util.promisify(checkSmtpRelay); - callback(); - }); + const [error] = await safe(checkSmtpRelayAsync(relay)); + if (error) throw new BoxError(BoxError.BAD_FIELD, error.message); } function checkDkim(mailDomain, callback) { @@ -478,7 +495,7 @@ function getStatus(domain, callback) { function recordResult(what, func) { return function (callback) { func(function (error, result) { - if (error) debug('Ignored error - ' + what + ':', error); + if (error) debug(`Ignored error - ${what} : ${error.message}`); safe.set(results, what, result || {}); @@ -488,9 +505,11 @@ function getStatus(domain, callback) { } const mailFqdn = settings.mailFqdn(); + const getDomainFunc = util.callbackify(getDomain); - getDomain(domain, function (error, mailDomain) { + getDomainFunc(domain, function (error, mailDomain) { if (error) return callback(error); + if (!mailDomain) return callback(new BoxError(BoxError.NOT_FOUND, 'mail domain not found')); let checks = []; if (mailDomain.enabled) { @@ -572,7 +591,9 @@ function createMailConfig(mailFqdn, mailDomain, callback) { debug('createMailConfig: generating mail config'); - getDomains(function (error, mailDomains) { + const listDomainsFunc = util.callbackify(listDomains); + + listDomainsFunc(function (error, mailDomains) { if (error) return callback(error); const mailOutDomains = mailDomains.filter(d => d.relay.provider !== 'noop').map(d => d.domain).join(','); @@ -750,25 +771,42 @@ function handleCertChanged(callback) { restartMailIfActivated(callback); } -function getDomain(domain, callback) { +async function getDomain(domain) { assert.strictEqual(typeof domain, 'string'); - assert.strictEqual(typeof callback, 'function'); - maildb.get(domain, function (error, result) { - if (error) return callback(error); - - return callback(null, result); - }); + const result = await database.query(`SELECT ${MAILDB_FIELDS} FROM mail WHERE domain = ?`, [ domain ]); + if (result.length === 0) return null; + return postProcess(result[0]); } -function getDomains(callback) { - assert.strictEqual(typeof callback, 'function'); +async function updateDomain(domain, data) { + assert.strictEqual(typeof domain, 'string'); + assert.strictEqual(typeof data, 'object'); - maildb.list(function (error, results) { - if (error) return callback(error); + const args = [ ]; + const fields = [ ]; + for (var k in data) { + if (k === 'catchAll' || k === 'banner') { + fields.push(`${k}Json = ?`); + args.push(JSON.stringify(data[k])); + } else if (k === 'relay') { + fields.push('relayJson = ?'); + args.push(JSON.stringify(data[k])); + } else { + fields.push(k + ' = ?'); + args.push(data[k]); + } + } + args.push(domain); - return callback(null, results); - }); + const result = await database.query('UPDATE mail SET ' + fields.join(', ') + ' WHERE domain=?', args); + if (result.affectedRows !== 1) throw new BoxError(BoxError.NOT_FOUND, 'Mail domain not found'); +} + +async function listDomains() { + const results = await database.query(`SELECT ${MAILDB_FIELDS} FROM mail ORDER BY domain`); + results.forEach(function (result) { postProcess(result); }); + return results; } // https://agari.zendesk.com/hc/en-us/articles/202952749-How-long-can-my-SPF-record-be- @@ -868,8 +906,11 @@ function upsertDnsRecords(domain, mailFqdn, callback) { debug(`upsertDnsRecords: updating mail dns records of domain ${domain} and mail fqdn ${mailFqdn}`); - maildb.get(domain, function (error, mailDomain) { + const getDomainFunc = util.callbackify(getDomain); + + getDomainFunc(domain, function (error, mailDomain) { if (error) return callback(error); + if (!mailDomain) return callback(new BoxError(BoxError.NOT_FOUND, 'mail domain not found')); error = ensureDkimKeySync(mailDomain); if (error) return callback(error); @@ -1012,14 +1053,8 @@ function onDomainRemoved(domain, callback) { restartMail(callback); } -function clearDomains(callback) { - assert.strictEqual(typeof callback, 'function'); - - maildb.clear(function (error) { - if (error) return callback(error); - - callback(); - }); +async function clearDomains() { + await database.query('DELETE FROM mail', []); } // remove all fields that should never be sent out via REST API @@ -1032,91 +1067,64 @@ function removePrivateFields(domain) { return result; } -function setMailFromValidation(domain, enabled, callback) { +async function setMailFromValidation(domain, enabled) { assert.strictEqual(typeof domain, 'string'); assert.strictEqual(typeof enabled, 'boolean'); - assert.strictEqual(typeof callback, 'function'); - maildb.update(domain, { mailFromValidation: enabled }, function (error) { - if (error) return callback(error); + await updateDomain(domain, { mailFromValidation: enabled }); - restartMail(NOOP_CALLBACK); // have to restart mail container since haraka cannot watch symlinked config files (mail.ini) - - callback(null); - }); + restartMail(NOOP_CALLBACK); // have to restart mail container since haraka cannot watch symlinked config files (mail.ini) } -function setBanner(domain, banner, callback) { +async function setBanner(domain, banner) { assert.strictEqual(typeof domain, 'string'); assert.strictEqual(typeof banner, 'object'); - assert.strictEqual(typeof callback, 'function'); - maildb.update(domain, { banner }, function (error) { - if (error) return callback(error); + await updateDomain(domain, { banner }); - restartMail(NOOP_CALLBACK); - - callback(null); - }); + restartMail(NOOP_CALLBACK); } -function setCatchAllAddress(domain, addresses, callback) { +async function setCatchAllAddress(domain, addresses) { assert.strictEqual(typeof domain, 'string'); assert(Array.isArray(addresses)); - assert.strictEqual(typeof callback, 'function'); - maildb.update(domain, { catchAll: addresses }, function (error) { - if (error) return callback(error); + await updateDomain(domain, { catchAll: addresses }); - restartMail(NOOP_CALLBACK); // have to restart mail container since haraka cannot watch symlinked config files (mail.ini) - - callback(null); - }); + restartMail(NOOP_CALLBACK); // have to restart mail container since haraka cannot watch symlinked config files (mail.ini) } -function setMailRelay(domain, relay, callback) { +async function setMailRelay(domain, relay, options) { assert.strictEqual(typeof domain, 'string'); assert.strictEqual(typeof relay, 'object'); - assert.strictEqual(typeof callback, 'function'); + assert.strictEqual(typeof options, 'object'); - getDomain(domain, function (error, result) { - if (error) return callback(error); + const result = await getDomain(domain); + if (!domain) throw new BoxError(BoxError.NOT_FOUND, 'Mail domain not found'); - // inject current username/password - if (result.relay.provider === relay.provider) { - if (relay.username === constants.SECRET_PLACEHOLDER) relay.username = result.relay.username; - if (relay.password === constants.SECRET_PLACEHOLDER) relay.password = result.relay.password; - } + // inject current username/password + if (result.relay.provider === relay.provider) { + if (relay.username === constants.SECRET_PLACEHOLDER) relay.username = result.relay.username; + if (relay.password === constants.SECRET_PLACEHOLDER) relay.password = result.relay.password; + } - verifyRelay(relay, function (error) { - if (error) return callback(error); + if (!options.skipVerify) await verifyRelay(relay); - maildb.update(domain, { relay: relay }, function (error) { - if (error) return callback(error); + await updateDomain(domain, { relay: relay }); - restartMail(NOOP_CALLBACK); - - callback(null); - }); - }); - }); + restartMail(NOOP_CALLBACK); } -function setMailEnabled(domain, enabled, auditSource, callback) { +async function setMailEnabled(domain, enabled, auditSource) { assert.strictEqual(typeof domain, 'string'); assert.strictEqual(typeof enabled, 'boolean'); assert.strictEqual(typeof auditSource, 'object'); - assert.strictEqual(typeof callback, 'function'); - maildb.update(domain, { enabled: enabled }, function (error) { - if (error) return callback(error); + await updateDomain(domain, { enabled: enabled }); - restartMail(NOOP_CALLBACK); + restartMail(NOOP_CALLBACK); - eventlog.add(enabled ? eventlog.ACTION_MAIL_ENABLED : eventlog.ACTION_MAIL_DISABLED, auditSource, { domain }); - - callback(null); - }); + await eventlog.add(enabled ? eventlog.ACTION_MAIL_ENABLED : eventlog.ACTION_MAIL_DISABLED, auditSource, { domain }); } function sendTestMail(domain, to, callback) { @@ -1124,8 +1132,11 @@ function sendTestMail(domain, to, callback) { assert.strictEqual(typeof to, 'string'); assert.strictEqual(typeof callback, 'function'); - getDomain(domain, function (error, result) { + const getDomainFunc = util.callbackify(getDomain); + + getDomainFunc(domain, function (error, result) { if (error) return callback(error); + if (!result) return callback(new BoxError(BoxError.NOT_FOUND, 'mail domain not found')); mailer.sendTestMail(result.domain, to, function (error) { if (error) return callback(error); @@ -1431,7 +1442,9 @@ function resolveList(listName, listDomain, callback) { assert.strictEqual(typeof listDomain, 'string'); assert.strictEqual(typeof callback, 'function'); - getDomains(function (error, mailDomains) { + const listDomainsFunc = util.callbackify(listDomains); + + listDomainsFunc(function (error, mailDomains) { if (error) return callback(error); const mailInDomains = mailDomains.filter(function (d) { return d.enabled; }).map(function (d) { return d.domain; }).join(','); diff --git a/src/maildb.js b/src/maildb.js deleted file mode 100644 index 30a9f05c0..000000000 --- a/src/maildb.js +++ /dev/null @@ -1,99 +0,0 @@ -'use strict'; - -exports = module.exports = { - get, - list, - update, - - clear, - - TYPE_USER: 'user', - TYPE_APP: 'app', - TYPE_GROUP: 'group' -}; - -var assert = require('assert'), - BoxError = require('./boxerror.js'), - database = require('./database.js'), - safe = require('safetydance'); - -var MAILDB_FIELDS = [ 'domain', 'enabled', 'mailFromValidation', 'catchAllJson', 'relayJson', 'dkimSelector', 'bannerJson' ].join(','); - -function postProcess(data) { - data.enabled = !!data.enabled; // int to boolean - data.mailFromValidation = !!data.mailFromValidation; // int to boolean - - data.catchAll = safe.JSON.parse(data.catchAllJson) || [ ]; - delete data.catchAllJson; - - data.relay = safe.JSON.parse(data.relayJson) || { provider: 'cloudron-smtp' }; - delete data.relayJson; - - data.banner = safe.JSON.parse(data.bannerJson) || { text: null, html: null }; - delete data.bannerJson; - - return data; -} - -function clear(callback) { - assert.strictEqual(typeof callback, 'function'); - - // using TRUNCATE makes it fail foreign key check - database.query('DELETE FROM mail', [], function (error) { - if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error)); - callback(null); - }); -} - -function get(domain, callback) { - assert.strictEqual(typeof domain, 'string'); - assert.strictEqual(typeof callback, 'function'); - - database.query('SELECT ' + MAILDB_FIELDS + ' FROM mail WHERE domain = ?', [ domain ], function (error, results) { - if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error)); - if (results.length === 0) return callback(new BoxError(BoxError.NOT_FOUND, 'Mail domain not found')); - - callback(null, postProcess(results[0])); - }); -} - -function list(callback) { - assert.strictEqual(typeof callback, 'function'); - - database.query('SELECT ' + MAILDB_FIELDS + ' FROM mail ORDER BY domain', function (error, results) { - if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error)); - - results.forEach(function (result) { postProcess(result); }); - - callback(null, results); - }); -} - -function update(domain, data, callback) { - assert.strictEqual(typeof domain, 'string'); - assert.strictEqual(typeof data, 'object'); - assert.strictEqual(typeof callback, 'function'); - - var args = [ ]; - var fields = [ ]; - for (var k in data) { - if (k === 'catchAll' || k === 'banner') { - fields.push(`${k}Json = ?`); - args.push(JSON.stringify(data[k])); - } else if (k === 'relay') { - fields.push('relayJson = ?'); - args.push(JSON.stringify(data[k])); - } else { - fields.push(k + ' = ?'); - args.push(data[k]); - } - } - args.push(domain); - - database.query('UPDATE mail SET ' + fields.join(', ') + ' WHERE domain=?', args, function (error, result) { - if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error)); - if (result.affectedRows !== 1) return callback(new BoxError(BoxError.NOT_FOUND, 'Mail domain not found')); - - callback(null); - }); -} diff --git a/src/routes/mail.js b/src/routes/mail.js index 16e91f65b..49143a5d1 100644 --- a/src/routes/mail.js +++ b/src/routes/mail.js @@ -31,21 +31,22 @@ exports = module.exports = { getMailboxCount }; -var assert = require('assert'), +const assert = require('assert'), auditSource = require('../auditsource.js'), BoxError = require('../boxerror.js'), mail = require('../mail.js'), HttpError = require('connect-lastmile').HttpError, - HttpSuccess = require('connect-lastmile').HttpSuccess; + HttpSuccess = require('connect-lastmile').HttpSuccess, + safe = require('safetydance'); -function getDomain(req, res, next) { +async function getDomain(req, res, next) { assert.strictEqual(typeof req.params.domain, 'string'); - mail.getDomain(req.params.domain, function (error, result) { - if (error) return next(BoxError.toHttpError(error)); + const [error, result] = await safe(mail.getDomain(req.params.domain)); + if (error) return next(BoxError.toHttpError(error)); + if (!result) return next(new HttpError(404, 'Mail domain not found')); - next(new HttpSuccess(200, mail.removePrivateFields(result))); - }); + next(new HttpSuccess(200, mail.removePrivateFields(result))); } function getStatus(req, res, next) { @@ -61,38 +62,36 @@ function getStatus(req, res, next) { }); } -function setMailFromValidation(req, res, next) { +async function setMailFromValidation(req, res, next) { assert.strictEqual(typeof req.params.domain, 'string'); assert.strictEqual(typeof req.body, 'object'); if (typeof req.body.enabled !== 'boolean') return next(new HttpError(400, 'enabled is required')); - mail.setMailFromValidation(req.params.domain, req.body.enabled, function (error) { - if (error) return next(BoxError.toHttpError(error)); + const [error] = await safe(mail.setMailFromValidation(req.params.domain, req.body.enabled)); + if (error) return next(BoxError.toHttpError(error)); - next(new HttpSuccess(202)); - }); + next(new HttpSuccess(202)); } -function setCatchAllAddress(req, res, next) { +async function setCatchAllAddress(req, res, next) { assert.strictEqual(typeof req.params.domain, 'string'); assert.strictEqual(typeof req.body, 'object'); if (!req.body.addresses) return next(new HttpError(400, 'addresses is required')); if (!Array.isArray(req.body.addresses)) return next(new HttpError(400, 'addresses must be an array of strings')); - for (var i = 0; i < req.body.addresses.length; i++) { + for (let i = 0; i < req.body.addresses.length; i++) { if (typeof req.body.addresses[i] !== 'string') return next(new HttpError(400, 'addresses must be an array of strings')); } - mail.setCatchAllAddress(req.params.domain, req.body.addresses, function (error) { - if (error) return next(BoxError.toHttpError(error)); + const [error] = await safe(mail.setCatchAllAddress(req.params.domain, req.body.addresses)); + if (error) return next(BoxError.toHttpError(error)); - next(new HttpSuccess(202)); - }); + next(new HttpSuccess(202)); } -function setMailRelay(req, res, next) { +async function setMailRelay(req, res, next) { assert.strictEqual(typeof req.params.domain, 'string'); assert.strictEqual(typeof req.body, 'object'); @@ -103,24 +102,22 @@ function setMailRelay(req, res, next) { if ('password' in req.body && typeof req.body.password !== 'string') return next(new HttpError(400, 'password must be a string')); if ('acceptSelfSignedCerts' in req.body && typeof req.body.acceptSelfSignedCerts !== 'boolean') return next(new HttpError(400, 'acceptSelfSignedCerts must be a boolean')); - mail.setMailRelay(req.params.domain, req.body, function (error) { - if (error) return next(BoxError.toHttpError(error)); + const [error] = await safe(mail.setMailRelay(req.params.domain, req.body, { skipVerify: false })); + if (error) return next(BoxError.toHttpError(error)); - next(new HttpSuccess(202)); - }); + next(new HttpSuccess(202)); } -function setMailEnabled(req, res, next) { +async function setMailEnabled(req, res, next) { assert.strictEqual(typeof req.params.domain, 'string'); assert.strictEqual(typeof req.body, 'object'); if (typeof req.body.enabled !== 'boolean') return next(new HttpError(400, 'enabled is required')); - mail.setMailEnabled(req.params.domain, !!req.body.enabled, auditSource.fromRequest(req), function (error) { - if (error) return next(BoxError.toHttpError(error)); + const [error] = await safe(mail.setMailEnabled(req.params.domain, !!req.body.enabled, auditSource.fromRequest(req))); + if (error) return next(BoxError.toHttpError(error)); - next(new HttpSuccess(202)); - }); + next(new HttpSuccess(202)); } function sendTestMail(req, res, next) { @@ -249,18 +246,17 @@ function setAliases(req, res, next) { }); } -function setBanner(req, res, next) { +async function setBanner(req, res, next) { assert.strictEqual(typeof req.params.domain, 'string'); assert.strictEqual(typeof req.body, 'object'); if (typeof req.body.text !== 'string') return res.status(400).send({ message: 'text must be a string' }); if ('html' in req.body && typeof req.body.html !== 'string') return res.status(400).send({ message: 'html must be a string' }); - mail.setBanner(req.params.domain, { text: req.body.text, html: req.body.html || null }, function (error) { - if (error) return next(BoxError.toHttpError(error)); + const [error] = await safe(mail.setBanner(req.params.domain, { text: req.body.text, html: req.body.html || null })); + if (error) return next(BoxError.toHttpError(error)); - next(new HttpSuccess(202)); - }); + next(new HttpSuccess(202)); } function getLists(req, res, next) { diff --git a/src/routes/provision.js b/src/routes/provision.js index 79bbbc661..636c4dd55 100644 --- a/src/routes/provision.js +++ b/src/routes/provision.js @@ -9,7 +9,7 @@ exports = module.exports = { setupTokenAuth }; -var assert = require('assert'), +const assert = require('assert'), auditSource = require('../auditsource.js'), BoxError = require('../boxerror.js'), debug = require('debug')('box:routes/setup'), diff --git a/src/routes/test/common.js b/src/routes/test/common.js index b8dd34c67..af5795b17 100644 --- a/src/routes/test/common.js +++ b/src/routes/test/common.js @@ -34,8 +34,8 @@ exports = module.exports = { }, MOCK_API_SERVER_ORIGIN: 'http://localhost:6060', - DASHBOARD_DOMAIN: 'test.example.com', - DASHBOARD_FQDN: 'my.test.example.com', + dashboardDomain: 'test.example.com', + dashboardFqdn: 'my.test.example.com', serverUrl: `http://localhost:${constants.PORT}`, }; @@ -48,7 +48,17 @@ function setup(done) { database._clear.bind(null), settings._setApiServerOrigin.bind(null, exports.MOCK_API_SERVER_ORIGIN), - settings.setDashboardLocation.bind(null, exports.DASHBOARD_DOMAIN, exports.DASHBOARD_FQDN), + + function setup(callback) { + superagent.post(`${serverUrl}/api/v1/cloudron/setup`) + .send({ dnsConfig: { provider: 'noop', domain: exports.dashboardDomain, config: {}, tlsConfig: { provider: 'fallback' } } }) + .end(function (error, result) { + expect(result).to.be.ok(); + expect(result.statusCode).to.eql(200); + + setTimeout(callback, 2000); + }); + }, function createAdmin(callback) { superagent.post(`${serverUrl}/api/v1/cloudron/activate`) diff --git a/src/routes/test/mail-test.js b/src/routes/test/mail-test.js index 03156b9ff..7822ac4cc 100644 --- a/src/routes/test/mail-test.js +++ b/src/routes/test/mail-test.js @@ -5,157 +5,50 @@ /* global before:false */ /* global after:false */ -var async = require('async'), - constants = require('../../constants.js'), - crypto = require('crypto'), - database = require('../../database.js'), +const common = require('./common.js'), expect = require('expect.js'), mail = require('../../mail.js'), - maildb = require('../../maildb.js'), - server = require('../../server.js'), settings = require('../../settings.js'), superagent = require('superagent'), - userdb = require('../../userdb.js'), _ = require('underscore'); -var SERVER_URL = 'http://localhost:' + constants.PORT; - -const DASHBOARD_DOMAIN = { - domain: 'admin.com', - zoneName: 'admin.com', - config: {}, - provider: 'noop', - fallbackCertificate: null, - tlsConfig: { provider: 'fallback' } -}; - -const DOMAIN_0 = { - domain: 'example-mail-test.com', - zoneName: 'example-mail-test.com', - config: {}, - provider: 'noop', - fallbackCertificate: null, - tlsConfig: { provider: 'fallback' } -}; -const USERNAME = 'superadmin', PASSWORD = 'Foobar?1337', EMAIL ='silly@me.com', MAILBOX_NAME = 'superman'; -const LIST_NAME = 'devs'; -var token = null; -var userId = ''; - -function setup(done) { - async.series([ - server.start.bind(null), - database._clear.bind(null), - - function dnsSetup(callback) { - superagent.post(SERVER_URL + '/api/v1/cloudron/setup') - .send({ dnsConfig: { provider: DASHBOARD_DOMAIN.provider, domain: DASHBOARD_DOMAIN.domain, config: DASHBOARD_DOMAIN.config, tlsConfig: { provider: 'fallback' } } }) - .end(function (error, result) { - expect(result).to.be.ok(); - expect(result.statusCode).to.eql(200); - - callback(); - }); - }, - - function waitForSetup(done) { - async.retry({ times: 5, interval: 4000 }, function (retryCallback) { - superagent.get(SERVER_URL + '/api/v1/cloudron/status') - .end(function (error, result) { - if (!result || result.statusCode !== 200) return retryCallback(new Error('Bad result')); - - if (!result.body.setup.active && result.body.setup.errorMessage === '' && result.body.adminFqdn) return retryCallback(); - - retryCallback(new Error('Not done yet: ' + JSON.stringify(result.body))); - }); - }, done); - }, - - function createAdmin(callback) { - superagent.post(SERVER_URL + '/api/v1/cloudron/activate') - .query({ setupToken: 'somesetuptoken' }) - .send({ username: USERNAME, password: PASSWORD, email: EMAIL }) - .end(function (error, result) { - expect(error).to.not.be.ok(); - expect(result).to.be.ok(); - expect(result.statusCode).to.eql(201); - - // stash token for further use - token = result.body.token; - - callback(); - }); - }, - - function createDomain(callback) { - superagent.post(SERVER_URL + '/api/v1/domains') - .query({ access_token: token }) - .send(DOMAIN_0) - .end(function (error, result) { - expect(result.statusCode).to.equal(201); - - callback(); - }); - }, - - function getUserId(callback) { - userdb.getByUsername(USERNAME, function (error, result) { - expect(error).to.not.be.ok(); - - userId = result.id; - - callback(); - }); - } - ], done); -} - -function cleanup(done) { - database._clear(function (error) { - expect(!error).to.be.ok(); - - server.stop(done); - }); -} - describe('Mail API', function () { + const { setup, cleanup, serverUrl, owner, dashboardDomain } = common; + before(setup); after(cleanup); describe('crud', function () { - it('cannot get non-existing domain', function (done) { - superagent.get(SERVER_URL + '/api/v1/mail/doesnotexist.com') - .query({ access_token: token }) - .end(function (err, res) { - expect(res.statusCode).to.equal(404); - done(); - }); + it('cannot get non-existing domain', async function () { + const response = await superagent.get(`${serverUrl}/api/v1/mail/doesnotexist.com`) + .query({ access_token: owner.token }) + .ok(() => true); + + expect(response.statusCode).to.equal(404); }); - it('can get domain', function (done) { - superagent.get(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain) - .query({ access_token: token }) - .end(function (err, res) { - expect(res.statusCode).to.equal(200); - expect(res.body.domain).to.equal(DOMAIN_0.domain); - expect(res.body.enabled).to.equal(false); - expect(res.body.mailFromValidation).to.equal(true); - expect(res.body.catchAll).to.be.an(Array); - expect(res.body.catchAll.length).to.equal(0); - expect(res.body.relay).to.be.an('object'); - expect(res.body.relay.provider).to.equal('cloudron-smtp'); - done(); - }); + it('can get domain', async function () { + const response = await superagent.get(`${serverUrl}/api/v1/mail/${dashboardDomain}`) + .query({ access_token: owner.token }); + + expect(response.statusCode).to.equal(200); + expect(response.body.domain).to.equal(dashboardDomain); + expect(response.body.enabled).to.equal(false); + expect(response.body.mailFromValidation).to.equal(true); + expect(response.body.catchAll).to.be.an(Array); + expect(response.body.catchAll.length).to.equal(0); + expect(response.body.relay).to.be.an('object'); + expect(response.body.relay.provider).to.equal('cloudron-smtp'); }); }); describe('status', function () { - var resolve = null; - var dnsAnswerQueue = []; - var dkimDomain, spfDomain, mxDomain, dmarcDomain; + let resolve = null; + let dnsAnswerQueue = []; + let dkimDomain, spfDomain, mxDomain, dmarcDomain; before(function (done) { - var dns = require('../../native-dns.js'); + const dns = require('../../native-dns.js'); // replace dns resolveTxt() resolve = dns.resolve; @@ -170,17 +63,16 @@ describe('Mail API', function () { callback(null, dnsAnswerQueue[hostname][type]); }; - const suffix = crypto.createHash('sha256').update(settings.dashboardDomain()).digest('hex').substr(0, 6); - dkimDomain = `cloudron-${suffix}._domainkey.${DOMAIN_0.domain}`; - spfDomain = DOMAIN_0.domain; - mxDomain = DOMAIN_0.domain; - dmarcDomain = '_dmarc.' + DOMAIN_0.domain; + dkimDomain = `cloudron._domainkey.${dashboardDomain}`; // no suffix for provisioned domains + spfDomain = dashboardDomain; + mxDomain = dashboardDomain; + dmarcDomain = '_dmarc.' + dashboardDomain; - superagent.post(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain + '/enable') - .query({ access_token: token }) + superagent.post(`${serverUrl}/api/v1/mail/${dashboardDomain}/enable`) + .query({ access_token: owner.token }) .send({ enabled: true }) - .end(function (err, res) { - expect(res.statusCode).to.equal(202); + .end(function (err, response) { + expect(response.statusCode).to.equal(202); done(); }); @@ -194,13 +86,11 @@ describe('Mail API', function () { done(); }); - it('does not fail when dns errors', function (done) { - superagent.get(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain + '/status') - .query({ access_token: token }) - .end(function (err, res) { - expect(res.statusCode).to.equal(200); - done(); - }); + it('does not fail when dns errors', async function () { + const response = await superagent.get(`${serverUrl}/api/v1/mail/${dashboardDomain}/status`) + .query({ access_token: owner.token }); + + expect(response.statusCode).to.equal(200); }); function clearDnsAnswerQueue() { @@ -211,51 +101,49 @@ describe('Mail API', function () { dnsAnswerQueue[dmarcDomain] = { }; } - it('succeeds with dns errors', function (done) { + it('succeeds with dns errors', async function () { clearDnsAnswerQueue(); - superagent.get(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain + '/status') - .query({ access_token: token }) - .end(function (err, res) { - expect(res.statusCode).to.equal(200); + const response = await superagent.get(`${serverUrl}/api/v1/mail/${dashboardDomain}` + '/status') + .query({ access_token: owner.token }); + expect(response.statusCode).to.equal(200); - expect(res.body.dns.dkim).to.be.an('object'); - expect(res.body.dns.dkim.domain).to.eql(dkimDomain); - expect(res.body.dns.dkim.type).to.eql('TXT'); - expect(res.body.dns.dkim.value).to.eql(null); - expect(res.body.dns.dkim.expected).to.eql('v=DKIM1; t=s; p=' + mail._readDkimPublicKeySync(DOMAIN_0.domain)); - expect(res.body.dns.dkim.status).to.eql(false); + console.dir(response.body); - expect(res.body.dns.spf).to.be.an('object'); - expect(res.body.dns.spf.domain).to.eql(spfDomain); - expect(res.body.dns.spf.type).to.eql('TXT'); - expect(res.body.dns.spf.value).to.eql(null); - expect(res.body.dns.spf.expected).to.eql('v=spf1 a:' + settings.dashboardFqdn() + ' ~all'); - expect(res.body.dns.spf.status).to.eql(false); + expect(response.body.dns.dkim).to.be.an('object'); + expect(response.body.dns.dkim.domain).to.eql(dkimDomain); + expect(response.body.dns.dkim.type).to.eql('TXT'); + expect(response.body.dns.dkim.value).to.eql(null); + expect(response.body.dns.dkim.expected).to.eql('v=DKIM1; t=s; p=' + mail._readDkimPublicKeySync(dashboardDomain)); + expect(response.body.dns.dkim.status).to.eql(false); - expect(res.body.dns.dmarc).to.be.an('object'); - expect(res.body.dns.dmarc.type).to.eql('TXT'); - expect(res.body.dns.dmarc.value).to.eql(null); - expect(res.body.dns.dmarc.expected).to.eql('v=DMARC1; p=reject; pct=100'); - expect(res.body.dns.dmarc.status).to.eql(false); + expect(response.body.dns.spf).to.be.an('object'); + expect(response.body.dns.spf.domain).to.eql(spfDomain); + expect(response.body.dns.spf.type).to.eql('TXT'); + expect(response.body.dns.spf.value).to.eql(null); + expect(response.body.dns.spf.expected).to.eql('v=spf1 a:' + settings.dashboardFqdn() + ' ~all'); + expect(response.body.dns.spf.status).to.eql(false); - expect(res.body.dns.mx).to.be.an('object'); - expect(res.body.dns.mx.type).to.eql('MX'); - expect(res.body.dns.mx.value).to.eql(null); - expect(res.body.dns.mx.expected).to.eql('10 ' + settings.mailFqdn() + '.'); - expect(res.body.dns.mx.status).to.eql(false); + expect(response.body.dns.dmarc).to.be.an('object'); + expect(response.body.dns.dmarc.type).to.eql('TXT'); + expect(response.body.dns.dmarc.value).to.eql(null); + expect(response.body.dns.dmarc.expected).to.eql('v=DMARC1; p=reject; pct=100'); + expect(response.body.dns.dmarc.status).to.eql(false); - expect(res.body.dns.ptr).to.be.an('object'); - expect(res.body.dns.ptr.type).to.eql('PTR'); - // expect(res.body.ptr.value).to.eql(null); this will be anything random - expect(res.body.dns.ptr.expected).to.eql(settings.mailFqdn()); - expect(res.body.dns.ptr.status).to.eql(false); + expect(response.body.dns.mx).to.be.an('object'); + expect(response.body.dns.mx.type).to.eql('MX'); + expect(response.body.dns.mx.value).to.eql(null); + expect(response.body.dns.mx.expected).to.eql('10 ' + settings.mailFqdn() + '.'); + expect(response.body.dns.mx.status).to.eql(false); - done(); - }); + expect(response.body.dns.ptr).to.be.an('object'); + expect(response.body.dns.ptr.type).to.eql('PTR'); + // expect(response.body.ptr.value).to.eql(null); this will be anything random + expect(response.body.dns.ptr.expected).to.eql(settings.mailFqdn()); + expect(response.body.dns.ptr.status).to.eql(false); }); - it('succeeds with "undefined" spf, dkim, dmarc, mx, ptr records', function (done) { + it('succeeds with "undefined" spf, dkim, dmarc, mx, ptr records', async function () { clearDnsAnswerQueue(); dnsAnswerQueue[dkimDomain].TXT = null; @@ -263,594 +151,527 @@ describe('Mail API', function () { dnsAnswerQueue[mxDomain].MX = null; dnsAnswerQueue[dmarcDomain].TXT = null; - superagent.get(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain + '/status') - .query({ access_token: token }) - .end(function (err, res) { - expect(res.statusCode).to.equal(200); + const response = await superagent.get(`${serverUrl}/api/v1/mail/${dashboardDomain}` + '/status') + .query({ access_token: owner.token }); - expect(res.body.dns.spf).to.be.an('object'); - expect(res.body.dns.spf.expected).to.eql('v=spf1 a:' + settings.dashboardFqdn() + ' ~all'); - expect(res.body.dns.spf.status).to.eql(false); - expect(res.body.dns.spf.value).to.eql(null); + expect(response.statusCode).to.equal(200); - expect(res.body.dns.dkim).to.be.an('object'); - expect(res.body.dns.dkim.expected).to.eql('v=DKIM1; t=s; p=' + mail._readDkimPublicKeySync(DOMAIN_0.domain)); - expect(res.body.dns.dkim.status).to.eql(false); - expect(res.body.dns.dkim.value).to.eql(null); + expect(response.body.dns.spf).to.be.an('object'); + expect(response.body.dns.spf.expected).to.eql('v=spf1 a:' + settings.dashboardFqdn() + ' ~all'); + expect(response.body.dns.spf.status).to.eql(false); + expect(response.body.dns.spf.value).to.eql(null); - expect(res.body.dns.dmarc).to.be.an('object'); - expect(res.body.dns.dmarc.expected).to.eql('v=DMARC1; p=reject; pct=100'); - expect(res.body.dns.dmarc.status).to.eql(false); - expect(res.body.dns.dmarc.value).to.eql(null); + expect(response.body.dns.dkim).to.be.an('object'); + expect(response.body.dns.dkim.expected).to.eql('v=DKIM1; t=s; p=' + mail._readDkimPublicKeySync(dashboardDomain)); + expect(response.body.dns.dkim.status).to.eql(false); + expect(response.body.dns.dkim.value).to.eql(null); - expect(res.body.dns.mx).to.be.an('object'); - expect(res.body.dns.mx.status).to.eql(false); - expect(res.body.dns.mx.expected).to.eql('10 ' + settings.mailFqdn() + '.'); - expect(res.body.dns.mx.value).to.eql(null); + expect(response.body.dns.dmarc).to.be.an('object'); + expect(response.body.dns.dmarc.expected).to.eql('v=DMARC1; p=reject; pct=100'); + expect(response.body.dns.dmarc.status).to.eql(false); + expect(response.body.dns.dmarc.value).to.eql(null); - expect(res.body.dns.ptr).to.be.an('object'); - expect(res.body.dns.ptr.expected).to.eql(settings.mailFqdn()); - expect(res.body.dns.ptr.status).to.eql(false); - // expect(res.body.ptr.value).to.eql(null); this will be anything random + expect(response.body.dns.mx).to.be.an('object'); + expect(response.body.dns.mx.status).to.eql(false); + expect(response.body.dns.mx.expected).to.eql('10 ' + settings.mailFqdn() + '.'); + expect(response.body.dns.mx.value).to.eql(null); - done(); - }); + expect(response.body.dns.ptr).to.be.an('object'); + expect(response.body.dns.ptr.expected).to.eql(settings.mailFqdn()); + expect(response.body.dns.ptr.status).to.eql(false); + // expect(response.body.ptr.value).to.eql(null); this will be anything random }); - it('succeeds with all different spf, dkim, dmarc, mx, ptr records', function (done) { + it('succeeds with all different spf, dkim, dmarc, mx, ptr records', async function () { clearDnsAnswerQueue(); dnsAnswerQueue[mxDomain].MX = [ { priority: '20', exchange: settings.mailFqdn() }, { priority: '10', exchange: 'some.other.server' } ]; dnsAnswerQueue[dmarcDomain].TXT = [['v=DMARC2; p=reject; pct=100']]; - dnsAnswerQueue[dkimDomain].TXT = [['v=DKIM2; t=s; p=' + mail._readDkimPublicKeySync(DOMAIN_0.domain)]]; + dnsAnswerQueue[dkimDomain].TXT = [['v=DKIM2; t=s; p=' + mail._readDkimPublicKeySync(dashboardDomain)]]; dnsAnswerQueue[spfDomain].TXT = [['v=spf1 a:random.com ~all']]; - superagent.get(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain + '/status') - .query({ access_token: token }) - .end(function (err, res) { - expect(res.statusCode).to.equal(200); + const response = await superagent.get(`${serverUrl}/api/v1/mail/${dashboardDomain}` + '/status') + .query({ access_token: owner.token }); - expect(res.body.dns.spf).to.be.an('object'); - expect(res.body.dns.spf.expected).to.eql('v=spf1 a:' + settings.dashboardFqdn() + ' a:random.com ~all'); - expect(res.body.dns.spf.status).to.eql(false); - expect(res.body.dns.spf.value).to.eql('v=spf1 a:random.com ~all'); + expect(response.statusCode).to.equal(200); - expect(res.body.dns.dkim).to.be.an('object'); - expect(res.body.dns.dkim.expected).to.eql('v=DKIM1; t=s; p=' + mail._readDkimPublicKeySync(DOMAIN_0.domain)); - expect(res.body.dns.dkim.status).to.eql(true); // as long as p= matches we are good - expect(res.body.dns.dkim.value).to.eql('v=DKIM2; t=s; p=' + mail._readDkimPublicKeySync(DOMAIN_0.domain)); + expect(response.body.dns.spf).to.be.an('object'); + expect(response.body.dns.spf.expected).to.eql('v=spf1 a:' + settings.dashboardFqdn() + ' a:random.com ~all'); + expect(response.body.dns.spf.status).to.eql(false); + expect(response.body.dns.spf.value).to.eql('v=spf1 a:random.com ~all'); - expect(res.body.dns.dmarc).to.be.an('object'); - expect(res.body.dns.dmarc.expected).to.eql('v=DMARC1; p=reject; pct=100'); - expect(res.body.dns.dmarc.status).to.eql(false); - expect(res.body.dns.dmarc.value).to.eql('v=DMARC2; p=reject; pct=100'); + expect(response.body.dns.dkim).to.be.an('object'); + expect(response.body.dns.dkim.expected).to.eql('v=DKIM1; t=s; p=' + mail._readDkimPublicKeySync(dashboardDomain)); + expect(response.body.dns.dkim.status).to.eql(true); // as long as p= matches we are good + expect(response.body.dns.dkim.value).to.eql('v=DKIM2; t=s; p=' + mail._readDkimPublicKeySync(dashboardDomain)); - expect(res.body.dns.mx).to.be.an('object'); - expect(res.body.dns.mx.status).to.eql(true); - expect(res.body.dns.mx.expected).to.eql('10 ' + settings.mailFqdn() + '.'); - expect(res.body.dns.mx.value).to.eql('20 ' + settings.mailFqdn() + '. 10 some.other.server.'); + expect(response.body.dns.dmarc).to.be.an('object'); + expect(response.body.dns.dmarc.expected).to.eql('v=DMARC1; p=reject; pct=100'); + expect(response.body.dns.dmarc.status).to.eql(false); + expect(response.body.dns.dmarc.value).to.eql('v=DMARC2; p=reject; pct=100'); - expect(res.body.dns.ptr).to.be.an('object'); - expect(res.body.dns.ptr.expected).to.eql(settings.mailFqdn()); - expect(res.body.dns.ptr.status).to.eql(false); - // expect(res.body.ptr.value).to.eql(null); this will be anything random + expect(response.body.dns.mx).to.be.an('object'); + expect(response.body.dns.mx.status).to.eql(true); + expect(response.body.dns.mx.expected).to.eql('10 ' + settings.mailFqdn() + '.'); + expect(response.body.dns.mx.value).to.eql('20 ' + settings.mailFqdn() + '. 10 some.other.server.'); - expect(res.body.relay).to.be.an('object'); + expect(response.body.dns.ptr).to.be.an('object'); + expect(response.body.dns.ptr.expected).to.eql(settings.mailFqdn()); + expect(response.body.dns.ptr.status).to.eql(false); + // expect(response.body.ptr.value).to.eql(null); this will be anything random - done(); - }); + expect(response.body.relay).to.be.an('object'); }); - it('succeeds with existing embedded spf', function (done) { + it('succeeds with existing embedded spf', async function () { clearDnsAnswerQueue(); dnsAnswerQueue[spfDomain].TXT = [['v=spf1 a:example.com a:' + settings.mailFqdn() + ' ~all']]; - superagent.get(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain + '/status') - .query({ access_token: token }) - .end(function (err, res) { - expect(res.statusCode).to.equal(200); + const response = await superagent.get(`${serverUrl}/api/v1/mail/${dashboardDomain}` + '/status') + .query({ access_token: owner.token }); - expect(res.body.dns.spf).to.be.an('object'); - expect(res.body.dns.spf.domain).to.eql(spfDomain); - expect(res.body.dns.spf.type).to.eql('TXT'); - expect(res.body.dns.spf.value).to.eql('v=spf1 a:example.com a:' + settings.mailFqdn() + ' ~all'); - expect(res.body.dns.spf.expected).to.eql('v=spf1 a:example.com a:' + settings.mailFqdn() + ' ~all'); - expect(res.body.dns.spf.status).to.eql(true); + expect(response.statusCode).to.equal(200); - done(); - }); + expect(response.body.dns.spf).to.be.an('object'); + expect(response.body.dns.spf.domain).to.eql(spfDomain); + expect(response.body.dns.spf.type).to.eql('TXT'); + expect(response.body.dns.spf.value).to.eql('v=spf1 a:example.com a:' + settings.mailFqdn() + ' ~all'); + expect(response.body.dns.spf.expected).to.eql('v=spf1 a:example.com a:' + settings.mailFqdn() + ' ~all'); + expect(response.body.dns.spf.status).to.eql(true); }); - it('succeeds with modified DMARC1 values', function (done) { + it('succeeds with modified DMARC1 values', async function () { clearDnsAnswerQueue(); dnsAnswerQueue[dmarcDomain].TXT = [['v=DMARC1; p=reject; rua=mailto:rua@example.com; pct=100']]; - superagent.get(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain + '/status') - .query({ access_token: token }) - .end(function (err, res) { - expect(res.statusCode).to.equal(200); + const response = await superagent.get(`${serverUrl}/api/v1/mail/${dashboardDomain}` + '/status') + .query({ access_token: owner.token }); - expect(res.body.dns.dmarc).to.be.an('object'); - expect(res.body.dns.dmarc.expected).to.eql('v=DMARC1; p=reject; pct=100'); - expect(res.body.dns.dmarc.status).to.eql(true); - expect(res.body.dns.dmarc.value).to.eql('v=DMARC1; p=reject; rua=mailto:rua@example.com; pct=100'); + expect(response.statusCode).to.equal(200); - done(); - }); + expect(response.body.dns.dmarc).to.be.an('object'); + expect(response.body.dns.dmarc.expected).to.eql('v=DMARC1; p=reject; pct=100'); + expect(response.body.dns.dmarc.status).to.eql(true); + expect(response.body.dns.dmarc.value).to.eql('v=DMARC1; p=reject; rua=mailto:rua@example.com; pct=100'); }); - it('succeeds with all correct records', function (done) { + it('succeeds with all correct records', async function () { clearDnsAnswerQueue(); dnsAnswerQueue[mxDomain].MX = [ { priority: '10', exchange: settings.mailFqdn() } ]; dnsAnswerQueue[dmarcDomain].TXT = [['v=DMARC1; p=reject; pct=100']]; - dnsAnswerQueue[dkimDomain].TXT = [['v=DKIM1; t=s; p=', mail._readDkimPublicKeySync(DOMAIN_0.domain) ]]; + dnsAnswerQueue[dkimDomain].TXT = [['v=DKIM1; t=s; p=', mail._readDkimPublicKeySync(dashboardDomain) ]]; dnsAnswerQueue[spfDomain].TXT = [['v=spf1 a:' + settings.dashboardFqdn() + ' ~all']]; - superagent.get(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain + '/status') - .query({ access_token: token }) - .end(function (err, res) { - expect(res.statusCode).to.equal(200); + const response = await superagent.get(`${serverUrl}/api/v1/mail/${dashboardDomain}` + '/status') + .query({ access_token: owner.token }); - expect(res.body.dns.dkim).to.be.an('object'); - expect(res.body.dns.dkim.domain).to.eql(dkimDomain); - expect(res.body.dns.dkim.type).to.eql('TXT'); - expect(res.body.dns.dkim.value).to.eql('v=DKIM1; t=s; p=' + mail._readDkimPublicKeySync(DOMAIN_0.domain)); - expect(res.body.dns.dkim.expected).to.eql('v=DKIM1; t=s; p=' + mail._readDkimPublicKeySync(DOMAIN_0.domain)); - expect(res.body.dns.dkim.status).to.eql(true); + expect(response.statusCode).to.equal(200); - expect(res.body.dns.spf).to.be.an('object'); - expect(res.body.dns.spf.domain).to.eql(spfDomain); - expect(res.body.dns.spf.type).to.eql('TXT'); - expect(res.body.dns.spf.value).to.eql('v=spf1 a:' + settings.dashboardFqdn() + ' ~all'); - expect(res.body.dns.spf.expected).to.eql('v=spf1 a:' + settings.dashboardFqdn() + ' ~all'); - expect(res.body.dns.spf.status).to.eql(true); + expect(response.body.dns.dkim).to.be.an('object'); + expect(response.body.dns.dkim.domain).to.eql(dkimDomain); + expect(response.body.dns.dkim.type).to.eql('TXT'); + expect(response.body.dns.dkim.value).to.eql('v=DKIM1; t=s; p=' + mail._readDkimPublicKeySync(dashboardDomain)); + expect(response.body.dns.dkim.expected).to.eql('v=DKIM1; t=s; p=' + mail._readDkimPublicKeySync(dashboardDomain)); + expect(response.body.dns.dkim.status).to.eql(true); - expect(res.body.dns.dmarc).to.be.an('object'); - expect(res.body.dns.dmarc.expected).to.eql('v=DMARC1; p=reject; pct=100'); - expect(res.body.dns.dmarc.status).to.eql(true); - expect(res.body.dns.dmarc.value).to.eql('v=DMARC1; p=reject; pct=100'); + expect(response.body.dns.spf).to.be.an('object'); + expect(response.body.dns.spf.domain).to.eql(spfDomain); + expect(response.body.dns.spf.type).to.eql('TXT'); + expect(response.body.dns.spf.value).to.eql('v=spf1 a:' + settings.dashboardFqdn() + ' ~all'); + expect(response.body.dns.spf.expected).to.eql('v=spf1 a:' + settings.dashboardFqdn() + ' ~all'); + expect(response.body.dns.spf.status).to.eql(true); - expect(res.body.dns.mx).to.be.an('object'); - expect(res.body.dns.mx.status).to.eql(true); - expect(res.body.dns.mx.expected).to.eql('10 ' + settings.mailFqdn() + '.'); - expect(res.body.dns.mx.value).to.eql('10 ' + settings.mailFqdn() + '.'); + expect(response.body.dns.dmarc).to.be.an('object'); + expect(response.body.dns.dmarc.expected).to.eql('v=DMARC1; p=reject; pct=100'); + expect(response.body.dns.dmarc.status).to.eql(true); + expect(response.body.dns.dmarc.value).to.eql('v=DMARC1; p=reject; pct=100'); - done(); - }); + expect(response.body.dns.mx).to.be.an('object'); + expect(response.body.dns.mx.status).to.eql(true); + expect(response.body.dns.mx.expected).to.eql('10 ' + settings.mailFqdn() + '.'); + expect(response.body.dns.mx.value).to.eql('10 ' + settings.mailFqdn() + '.'); }); }); describe('mail from validation', function () { - it('get mail from validation succeeds', function (done) { - superagent.get(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain) - .query({ access_token: token }) - .end(function (err, res) { - expect(res.statusCode).to.equal(200); - expect(res.body.mailFromValidation).to.eql(true); - done(); - }); + it('get mail from validation succeeds', async function () { + const response = await superagent.get(`${serverUrl}/api/v1/mail/${dashboardDomain}`) + .query({ access_token: owner.token }); + + expect(response.statusCode).to.equal(200); + expect(response.body.mailFromValidation).to.eql(true); }); - it('cannot set without enabled field', function (done) { - superagent.post(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain + '/mail_from_validation') - .query({ access_token: token }) + it('cannot set without enabled field', async function () { + const response = await superagent.post(`${serverUrl}/api/v1/mail/${dashboardDomain}/mail_from_validation`) + .query({ access_token: owner.token }) .send({ }) - .end(function (err, res) { - expect(res.statusCode).to.equal(400); - done(); - }); + .ok(() => true); + + expect(response.statusCode).to.equal(400); }); - it('can set with enabled field', function (done) { - superagent.post(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain + '/mail_from_validation') - .query({ access_token: token }) - .send({ enabled: false }) - .end(function (err, res) { - expect(res.statusCode).to.equal(202); - done(); - }); + it('can set with enabled field', async function () { + const response = await superagent.post(`${serverUrl}/api/v1/mail/${dashboardDomain}/mail_from_validation`) + .query({ access_token: owner.token }) + .send({ enabled: false }); + + expect(response.statusCode).to.equal(202); }); }); describe('catch_all', function () { - it('get catch_all succeeds', function (done) { - superagent.get(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain) - .query({ access_token: token }) - .end(function (err, res) { - expect(res.statusCode).to.equal(200); - expect(res.body.catchAll).to.eql([ ]); - done(); - }); + it('get catch_all succeeds', async function () { + const response = await superagent.get(`${serverUrl}/api/v1/mail/${dashboardDomain}`) + .query({ access_token: owner.token }); + expect(response.statusCode).to.equal(200); + expect(response.body.catchAll).to.eql([]); }); - it('cannot set without addresses field', function (done) { - superagent.post(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain + '/catch_all') - .query({ access_token: token }) - .end(function (err, res) { - expect(res.statusCode).to.equal(400); - done(); - }); + it('cannot set without addresses field', async function () { + const response = await superagent.post(`${serverUrl}/api/v1/mail/${dashboardDomain}/catch_all`) + .query({ access_token: owner.token }) + .send({ }) + .ok(() => true); + + expect(response.statusCode).to.equal(400); }); - it('cannot set with bad addresses field', function (done) { - superagent.post(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain + '/catch_all') - .query({ access_token: token }) + it('cannot set with bad addresses field', async function () { + const response = await superagent.post(`${serverUrl}/api/v1/mail/${dashboardDomain}/catch_all`) + .query({ access_token: owner.token }) .send({ addresses: [ 'user1', 123 ] }) - .end(function (err, res) { - expect(res.statusCode).to.equal(400); - done(); - }); + .ok(() => true); + + expect(response.statusCode).to.equal(400); }); - it('set succeeds', function (done) { - superagent.post(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain + '/catch_all') - .query({ access_token: token }) - .send({ addresses: [ 'user1' ] }) - .end(function (err, res) { - expect(res.statusCode).to.equal(202); - done(); - }); + it('set succeeds', async function () { + const response = await superagent.post(`${serverUrl}/api/v1/mail/${dashboardDomain}/catch_all`) + .query({ access_token: owner.token }) + .send({ addresses: [ 'user1' ] }); + + expect(response.statusCode).to.equal(202); }); - it('get succeeds', function (done) { - superagent.get(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain) - .query({ access_token: token }) - .end(function (err, res) { - expect(res.statusCode).to.equal(200); - expect(res.body.catchAll).to.eql([ 'user1' ]); - done(); - }); + it('get succeeds', async function () { + const response = await superagent.get(`${serverUrl}/api/v1/mail/${dashboardDomain}`) + .query({ access_token: owner.token }); + + expect(response.statusCode).to.equal(200); + expect(response.body.catchAll).to.eql([ 'user1' ]); }); }); describe('mail relay', function () { - it('get mail relay succeeds', function (done) { - superagent.get(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain) - .query({ access_token: token }) - .end(function (err, res) { - expect(res.statusCode).to.equal(200); - expect(res.body.relay).to.eql({ provider: 'cloudron-smtp' }); - done(); - }); + it('get mail relay succeeds', async function () { + const response = await superagent.get(`${serverUrl}/api/v1/mail/${dashboardDomain}`) + .query({ access_token: owner.token }); + expect(response.statusCode).to.equal(200); + expect(response.body.relay).to.eql({ provider: 'cloudron-smtp' }); }); - it('cannot set without provider field', function (done) { - superagent.post(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain + '/relay') - .query({ access_token: token }) + it('cannot set without provider field', async function () { + const response = await superagent.post(`${serverUrl}/api/v1/mail/${dashboardDomain}/relay`) + .query({ access_token: owner.token }) .send({ }) - .end(function (err, res) { - expect(res.statusCode).to.equal(400); - done(); - }); + .ok(() => true); + + expect(response.statusCode).to.equal(400); }); - it('cannot set with bad host', function (done) { - superagent.post(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain + '/relay') - .query({ access_token: token }) + it('cannot set with bad host', async function () { + const response = await superagent.post(`${serverUrl}/api/v1/mail/${dashboardDomain}/relay`) + .query({ access_token: owner.token }) .send({ provider: 'external-smtp', host: true }) - .end(function (err, res) { - expect(res.statusCode).to.equal(400); - done(); - }); + .ok(() => true); + + expect(response.statusCode).to.equal(400); }); - it('set fails because mail server is unreachable', function (done) { - superagent.post(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain + '/relay') - .query({ access_token: token }) + it('set fails because mail server is unreachable', async function () { + const response = await superagent.post(`${serverUrl}/api/v1/mail/${dashboardDomain}/relay`) + .query({ access_token: owner.token }) .send({ provider: 'external-smtp', host: 'host', port: 25, username: 'u', password: 'p', tls: true }) - .end(function (err, res) { - expect(res.statusCode).to.equal(400); - done(); - }); + .ok(() => true); + + expect(response.statusCode).to.equal(400); }); - it('get succeeds', function (done) { - var relay = { provider: 'external-smtp', host: 'host', port: 25, username: 'u', password: 'p', tls: true }; + it('get succeeds', async function () { + const relay = { provider: 'external-smtp', host: 'host', port: 25, username: 'u', password: 'p', tls: true }; - maildb.update(DOMAIN_0.domain, { relay: relay }, function (error) { // skip the mail server verify() - expect(error).to.not.be.ok(); + await mail.setMailRelay(dashboardDomain, relay, { skipVerify: true }); - superagent.get(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain) - .query({ access_token: token }) - .end(function (err, res) { - expect(res.statusCode).to.equal(200); - expect(_.omit(res.body.relay, 'password')).to.eql(_.omit(relay, 'password')); - done(); - }); - }); + const response = await superagent.get(`${serverUrl}/api/v1/mail/${dashboardDomain}`) + .query({ access_token: owner.token }); + + expect(response.statusCode).to.equal(200); + expect(_.omit(response.body.relay, 'password')).to.eql(_.omit(relay, 'password')); }); }); describe('mailboxes', function () { - it('add succeeds', function (done) { - superagent.post(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain + '/mailboxes') - .send({ name: MAILBOX_NAME, ownerId: userId, ownerType: 'user', active: true }) - .query({ access_token: token }) - .end(function (err, res) { - expect(res.statusCode).to.equal(201); - done(); - }); + const MAILBOX_NAME = 'support'; + + it('add succeeds', async function () { + const response = await superagent.post(`${serverUrl}/api/v1/mail/${dashboardDomain}/mailboxes`) + .send({ name: MAILBOX_NAME, ownerId: owner.id, ownerType: 'user', active: true }) + .query({ access_token: owner.token }); + + expect(response.statusCode).to.equal(201); }); - it('cannot add again', function (done) { - superagent.post(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain + '/mailboxes') - .send({ name: MAILBOX_NAME, ownerId: userId, ownerType: 'user', active: true }) - .query({ access_token: token }) - .end(function (err, res) { - expect(res.statusCode).to.equal(409); - done(); - }); + it('cannot add again', async function () { + const response = await superagent.post(`${serverUrl}/api/v1/mail/${dashboardDomain}/mailboxes`) + .send({ name: MAILBOX_NAME, ownerId: owner.id, ownerType: 'user', active: true }) + .query({ access_token: owner.token }) + .ok(() => true); + + expect(response.statusCode).to.equal(409); }); - it('get fails if not exist', function (done) { - superagent.get(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain + '/mailboxes/' + 'someuserdoesnotexist') - .query({ access_token: token }) - .end(function (err, res) { - expect(res.statusCode).to.equal(404); - done(); - }); + it('get fails if not exist', async function () { + const response = await superagent.get(`${serverUrl}/api/v1/mail/${dashboardDomain}/mailboxes/someuserdoesnotexist`) + .query({ access_token: owner.token }) + .ok(() => true); + + expect(response.statusCode).to.equal(404); }); - it('get succeeds', function (done) { - superagent.get(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain + '/mailboxes/' + MAILBOX_NAME) - .query({ access_token: token }) - .end(function (err, res) { - expect(res.statusCode).to.equal(200); - expect(res.body.mailbox).to.be.an('object'); - expect(res.body.mailbox.name).to.equal(MAILBOX_NAME); - expect(res.body.mailbox.ownerId).to.equal(userId); - expect(res.body.mailbox.ownerType).to.equal('user'); - expect(res.body.mailbox.aliasName).to.equal(null); - expect(res.body.mailbox.aliasDomain).to.equal(null); - expect(res.body.mailbox.domain).to.equal(DOMAIN_0.domain); - done(); - }); + it('get succeeds', async function () { + const response = await superagent.get(`${serverUrl}/api/v1/mail/${dashboardDomain}/mailboxes/${MAILBOX_NAME}`) + .query({ access_token: owner.token }); + + expect(response.statusCode).to.equal(200); + expect(response.body.mailbox).to.be.an('object'); + expect(response.body.mailbox.name).to.equal(MAILBOX_NAME); + expect(response.body.mailbox.ownerId).to.equal(owner.id); + expect(response.body.mailbox.ownerType).to.equal('user'); + expect(response.body.mailbox.aliasName).to.equal(null); + expect(response.body.mailbox.aliasDomain).to.equal(null); + expect(response.body.mailbox.domain).to.equal(dashboardDomain); }); - it('listing succeeds', function (done) { - superagent.get(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain + '/mailboxes') - .query({ access_token: token }) - .end(function (err, res) { - expect(res.statusCode).to.equal(200); - expect(res.body.mailboxes.length).to.eql(1); - expect(res.body.mailboxes[0]).to.be.an('object'); - expect(res.body.mailboxes[0].name).to.equal(MAILBOX_NAME); - expect(res.body.mailboxes[0].ownerId).to.equal(userId); - expect(res.body.mailboxes[0].ownerType).to.equal('user'); - expect(res.body.mailboxes[0].aliases).to.eql([]); - expect(res.body.mailboxes[0].domain).to.equal(DOMAIN_0.domain); - done(); - }); + it('listing succeeds', async function () { + const response = await superagent.get(`${serverUrl}/api/v1/mail/${dashboardDomain}/mailboxes`) + .query({ access_token: owner.token }); + + expect(response.statusCode).to.equal(200); + expect(response.body.mailboxes.length).to.eql(1); + expect(response.body.mailboxes[0]).to.be.an('object'); + expect(response.body.mailboxes[0].name).to.equal(MAILBOX_NAME); + expect(response.body.mailboxes[0].ownerId).to.equal(owner.id); + expect(response.body.mailboxes[0].ownerType).to.equal('user'); + expect(response.body.mailboxes[0].aliases).to.eql([]); + expect(response.body.mailboxes[0].domain).to.equal(dashboardDomain); }); - it('disable fails even if not exist', function (done) { - superagent.del(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain + '/mailboxes/' + 'someuserdoesnotexist') + it('disable fails even if not exist', async function () { + const response = await superagent.del(`${serverUrl}/api/v1/mail/${dashboardDomain}/mailboxes/someuserdoesnotexist`) .send({ deleteMails: false }) - .query({ access_token: token }) - .end(function (err, res) { - expect(res.statusCode).to.equal(404); - done(); - }); + .query({ access_token: owner.token }) + .ok(() => true); + + expect(response.statusCode).to.equal(404); }); - it('disable succeeds', function (done) { - superagent.del(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain + '/mailboxes/' + MAILBOX_NAME) + it('disable succeeds', async function () { + const response = await superagent.del(`${serverUrl}/api/v1/mail/${dashboardDomain}/mailboxes/${MAILBOX_NAME}`) .send({ deleteMails: false }) - .query({ access_token: token }) - .end(function (err, res) { - expect(res.statusCode).to.equal(201); - superagent.get(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain + '/mailboxes/' + MAILBOX_NAME) - .query({ access_token: token }) - .end(function (err, res) { - expect(res.statusCode).to.equal(404); - done(); - }); - }); + .query({ access_token: owner.token }); + + expect(response.statusCode).to.equal(201); + + const response2 = await superagent.get(`${serverUrl}/api/v1/mail/${dashboardDomain}` + '/mailboxes/' + MAILBOX_NAME) + .query({ access_token: owner.token }) + .ok(() => true); + expect(response2.statusCode).to.equal(404); }); }); describe('aliases', function () { + const MAILBOX_NAME = 'support'; + after(function (done) { - mail._removeMailboxes(DOMAIN_0.domain, function (error) { + mail._removeMailboxes(dashboardDomain, function (error) { if (error) return done(error); done(); }); }); - it('add the mailbox', function (done) { - superagent.post(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain + '/mailboxes') - .send({ name: MAILBOX_NAME, ownerId: userId, ownerType: 'user', active: true }) - .query({ access_token: token }) - .end(function (err, res) { - expect(res.statusCode).to.equal(201); - done(); - }); + it('add the mailbox', async function () { + const response = await superagent.post(`${serverUrl}/api/v1/mail/${dashboardDomain}/mailboxes`) + .send({ name: MAILBOX_NAME, ownerId: owner.id, ownerType: 'user', active: true }) + .query({ access_token: owner.token }); + + expect(response.statusCode).to.equal(201); }); - it('set fails if aliases is missing', function (done) { - superagent.put(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain + '/mailboxes/' + MAILBOX_NAME + '/aliases') - .query({ access_token: token }) - .end(function (err, res) { - expect(res.statusCode).to.equal(400); - done(); - }); + it('set fails if aliases is missing', async function () { + const response = await superagent.put(`${serverUrl}/api/v1/mail/${dashboardDomain}/mailboxes/${MAILBOX_NAME}/aliases`) + .query({ access_token: owner.token }) + .ok(() => true); + + expect(response.statusCode).to.equal(400); }); - it('set fails if user does not exist', function (done) { - superagent.put(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain + '/mailboxes/randomuser/aliases') - .send({ aliases: [{ name: 'hello', domain: DOMAIN_0.domain}, {name: 'there', domain: DOMAIN_0.domain}] }) - .query({ access_token: token }) - .end(function (err, res) { - expect(res.statusCode).to.equal(404); - done(); - }); + it('set fails if user does not exist', async function () { + const response = await superagent.put(`${serverUrl}/api/v1/mail/${dashboardDomain}/mailboxes/randomuser/aliases`) + .send({ aliases: [{ name: 'hello', domain: dashboardDomain}, {name: 'there', domain: dashboardDomain}] }) + .query({ access_token: owner.token }) + .ok(() => true); + + expect(response.statusCode).to.equal(404); }); - it('set fails if aliases is the wrong type', function (done) { - superagent.put(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain + '/mailboxes/' + MAILBOX_NAME + '/aliases') + it('set fails if aliases is the wrong type', async function () { + const response = await superagent.put(`${serverUrl}/api/v1/mail/${dashboardDomain}/mailboxes/${MAILBOX_NAME}/aliases`) .send({ aliases: 'hello, there' }) - .query({ access_token: token }) - .end(function (err, res) { - expect(res.statusCode).to.equal(400); - done(); - }); + .query({ access_token: owner.token }) + .ok(() => true); + + expect(response.statusCode).to.equal(400); }); - it('set succeeds', function (done) { - superagent.put(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain + '/mailboxes/' + MAILBOX_NAME + '/aliases') - .send({ aliases: [{ name: 'hello', domain: DOMAIN_0.domain}, {name: 'there', domain: DOMAIN_0.domain}] }) - .query({ access_token: token }) - .end(function (err, res) { - expect(res.statusCode).to.equal(202); - done(); - }); + it('set succeeds', async function () { + const response = await superagent.put(`${serverUrl}/api/v1/mail/${dashboardDomain}/mailboxes/${MAILBOX_NAME}/aliases`) + .send({ aliases: [{ name: 'hello', domain: dashboardDomain}, {name: 'there', domain: dashboardDomain}] }) + .query({ access_token: owner.token }); + + expect(response.statusCode).to.equal(202); }); - it('get succeeds', function (done) { - superagent.get(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain + '/mailboxes/' + MAILBOX_NAME + '/aliases') - .query({ access_token: token }) - .end(function (err, res) { - expect(res.statusCode).to.equal(200); - expect(res.body.aliases).to.eql([{ name: 'hello', domain: DOMAIN_0.domain}, {name: 'there', domain: DOMAIN_0.domain}]); - done(); - }); + it('get succeeds', async function () { + const response = await superagent.get(`${serverUrl}/api/v1/mail/${dashboardDomain}` + '/mailboxes/' + MAILBOX_NAME + '/aliases') + .query({ access_token: owner.token }); + + expect(response.statusCode).to.equal(200); + expect(response.body.aliases).to.eql([{ name: 'hello', domain: dashboardDomain}, {name: 'there', domain: dashboardDomain}]); }); - it('get fails if mailbox does not exist', function (done) { - superagent.get(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain + '/mailboxes/somerandomuser/aliases') - .query({ access_token: token }) - .end(function (err, res) { - expect(res.statusCode).to.equal(404); - done(); - }); + it('get fails if mailbox does not exist', async function () { + const response = await superagent.get(`${serverUrl}/api/v1/mail/${dashboardDomain}` + '/mailboxes/somerandomuser/aliases') + .query({ access_token: owner.token }) + .ok(() => true); + + expect(response.statusCode).to.equal(404); }); }); describe('mailinglists', function () { + const LIST_NAME = 'people'; + after(function (done) { - mail._removeMailboxes(DOMAIN_0.domain, function (error) { + mail._removeMailboxes(dashboardDomain, function (error) { if (error) return done(error); done(); }); }); - it('add fails without groupId', function (done) { - superagent.post(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain + '/lists') - .query({ access_token: token }) - .end(function (err, res) { - expect(res.statusCode).to.equal(400); - done(); - }); + it('add fails without groupId', async function () { + const response = await superagent.post(`${serverUrl}/api/v1/mail/${dashboardDomain}/lists`) + .query({ access_token: owner.token }) + .ok(() => true); + + expect(response.statusCode).to.equal(400); }); - it('add fails with invalid groupId', function (done) { - superagent.post(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain + '/lists') + it('add fails with invalid groupId', async function () { + const response = await superagent.post(`${serverUrl}/api/v1/mail/${dashboardDomain}/lists`) .send({ groupId: {} }) - .query({ access_token: token }) - .end(function (err, res) { - expect(res.statusCode).to.equal(400); - done(); - }); + .query({ access_token: owner.token }) + .ok(() => true); + + expect(response.statusCode).to.equal(400); }); - it('add fails without members array', function (done) { - superagent.post(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain + '/lists') + it('add fails without members array', async function () { + const response = await superagent.post(`${serverUrl}/api/v1/mail/${dashboardDomain}/lists`) .send({ name: LIST_NAME }) - .query({ access_token: token }) - .end(function (err, res) { - expect(res.statusCode).to.equal(400); - done(); - }); + .query({ access_token: owner.token }) + .ok(() => true); + + expect(response.statusCode).to.equal(400); }); - it('add succeeds', function (done) { - superagent.post(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain + '/lists') - .send({ name: LIST_NAME, members: [ `admin2@${DOMAIN_0.domain}`, `${USERNAME}@${DOMAIN_0.domain}`], membersOnly: false, active: true }) - .query({ access_token: token }) - .end(function (err, res) { - expect(res.statusCode).to.equal(201); - done(); - }); + it('add succeeds', async function () { + const response = await superagent.post(`${serverUrl}/api/v1/mail/${dashboardDomain}/lists`) + .send({ name: LIST_NAME, members: [ `admin2@${dashboardDomain}`, `${owner.username}@${dashboardDomain}`], membersOnly: false, active: true }) + .query({ access_token: owner.token }); + + expect(response.statusCode).to.equal(201); }); - it('add twice fails', function (done) { - superagent.post(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain + '/lists') - .send({ name: LIST_NAME, members: [ `admin2@${DOMAIN_0.domain}`, `${USERNAME}@${DOMAIN_0.domain}`], membersOnly: false, active: true }) - .query({ access_token: token }) - .end(function (err, res) { - expect(res.statusCode).to.equal(409); - done(); - }); + it('add twice fails', async function () { + const response = await superagent.post(`${serverUrl}/api/v1/mail/${dashboardDomain}/lists`) + .send({ name: LIST_NAME, members: [ `admin2@${dashboardDomain}`, `${owner.username}@${dashboardDomain}`], membersOnly: false, active: true }) + .query({ access_token: owner.token }) + .ok(() => true); + + expect(response.statusCode).to.equal(409); }); - it('get fails if not exist', function (done) { - superagent.get(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain + '/lists/' + 'doesnotexist') - .query({ access_token: token }) - .end(function (err, res) { - expect(res.statusCode).to.equal(404); - done(); - }); + it('get fails if not exist', async function (){ + const response = await superagent.get(`${serverUrl}/api/v1/mail/${dashboardDomain}/lists/doesnotexist`) + .query({ access_token: owner.token }) + .ok(() => true); + + expect(response.statusCode).to.equal(404); }); - it('get succeeds', function (done) { - superagent.get(SERVER_URL + `/api/v1/mail/${DOMAIN_0.domain}/lists/${LIST_NAME}`) - .query({ access_token: token }) - .end(function (err, res) { - expect(res.statusCode).to.equal(200); - expect(res.body.list).to.be.an('object'); - expect(res.body.list.name).to.equal(LIST_NAME); - expect(res.body.list.ownerId).to.equal('admin'); - expect(res.body.list.aliasName).to.equal(null); - expect(res.body.list.domain).to.equal(DOMAIN_0.domain); - expect(res.body.list.members).to.eql([ `admin2@${DOMAIN_0.domain}`, `superadmin@${DOMAIN_0.domain}` ]); - expect(res.body.list.membersOnly).to.be(false); - done(); - }); + it('get succeeds', async function () { + const response = await superagent.get(`${serverUrl}/api/v1/mail/${dashboardDomain}/lists/${LIST_NAME}`) + .query({ access_token: owner.token }); + + expect(response.statusCode).to.equal(200); + expect(response.body.list).to.be.an('object'); + expect(response.body.list.name).to.equal(LIST_NAME); + expect(response.body.list.ownerId).to.equal('admin'); + expect(response.body.list.aliasName).to.equal(null); + expect(response.body.list.domain).to.equal(dashboardDomain); + expect(response.body.list.members).to.eql([ `admin2@${dashboardDomain}`, `superadmin@${dashboardDomain}` ]); + expect(response.body.list.membersOnly).to.be(false); }); - it('get all succeeds', function (done) { - superagent.get(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain + '/lists') - .query({ access_token: token }) - .end(function (err, res) { - expect(res.statusCode).to.equal(200); - expect(res.body.lists).to.be.an(Array); - expect(res.body.lists.length).to.equal(1); - expect(res.body.lists[0].name).to.equal(LIST_NAME); - expect(res.body.lists[0].ownerId).to.equal('admin'); - expect(res.body.lists[0].aliasName).to.equal(null); - expect(res.body.lists[0].domain).to.equal(DOMAIN_0.domain); - expect(res.body.lists[0].members).to.eql([ `admin2@${DOMAIN_0.domain}`, `superadmin@${DOMAIN_0.domain}` ]); - expect(res.body.lists[0].membersOnly).to.be(false); - done(); - }); + it('get all succeeds', async function () { + const response = await superagent.get(`${serverUrl}/api/v1/mail/${dashboardDomain}/lists`) + .query({ access_token: owner.token }); + + expect(response.statusCode).to.equal(200); + expect(response.body.lists).to.be.an(Array); + expect(response.body.lists.length).to.equal(1); + expect(response.body.lists[0].name).to.equal(LIST_NAME); + expect(response.body.lists[0].ownerId).to.equal('admin'); + expect(response.body.lists[0].aliasName).to.equal(null); + expect(response.body.lists[0].domain).to.equal(dashboardDomain); + expect(response.body.lists[0].members).to.eql([ `admin2@${dashboardDomain}`, `superadmin@${dashboardDomain}` ]); + expect(response.body.lists[0].membersOnly).to.be(false); }); - it('del fails if list does not exist', function (done) { - superagent.del(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain + '/lists/' + 'doesnotexist') - .query({ access_token: token }) - .end(function (err, res) { - expect(res.statusCode).to.equal(404); - done(); - }); + it('del fails if list does not exist', async function () { + const response = await superagent.del(`${serverUrl}/api/v1/mail/${dashboardDomain}` + '/lists/' + 'doesnotexist') + .query({ access_token: owner.token }) + .ok(() => true); + + expect(response.statusCode).to.equal(404); }); - it('del succeeds', function (done) { - superagent.del(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain + '/lists/' + LIST_NAME) - .query({ access_token: token }) - .end(function (err, res) { - expect(res.statusCode).to.equal(204); + it('del succeeds', async function () { + const response = await superagent.del(`${serverUrl}/api/v1/mail/${dashboardDomain}/lists/${LIST_NAME}`) + .query({ access_token: owner.token }); - superagent.get(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain + '/lists/' + LIST_NAME) - .query({ access_token: token }) - .end(function (err, res) { - expect(res.statusCode).to.equal(404); - done(); - }); - }); + expect(response.statusCode).to.equal(204); + + const response2 = await superagent.get(`${serverUrl}/api/v1/mail/${dashboardDomain}/lists/${LIST_NAME}`) + .query({ access_token: owner.token }) + .ok(() => true); + + expect(response2.statusCode).to.equal(404); }); }); }); diff --git a/src/services.js b/src/services.js index b7a6da06e..f882c506d 100644 --- a/src/services.js +++ b/src/services.js @@ -995,7 +995,9 @@ function setupEmail(app, options, callback) { assert.strictEqual(typeof options, 'object'); assert.strictEqual(typeof callback, 'function'); - mail.getDomains(function (error, mailDomains) { + const listDomainsFunc = util.callbackify(mail.listDomains); + + listDomainsFunc(function (error, mailDomains) { if (error) return callback(error); const mailInDomains = mailDomains.filter(function (d) { return d.enabled; }).map(function (d) { return d.domain; }).join(','); diff --git a/src/test/database-test.js b/src/test/database-test.js index cfbcd5b21..9b4c28734 100644 --- a/src/test/database-test.js +++ b/src/test/database-test.js @@ -16,7 +16,6 @@ const appdb = require('../appdb.js'), expect = require('expect.js'), hat = require('../hat.js'), mailboxdb = require('../mailboxdb.js'), - maildb = require('../maildb.js'), reverseProxy = require('../reverseproxy.js'), settingsdb = require('../settingsdb.js'), taskdb = require('../taskdb.js'), @@ -1287,44 +1286,4 @@ describe('database', function () { }); }); - describe('mail', function () { - const MAIL_DOMAIN_0 = { - domain: DOMAIN_0.domain, - enabled: false, - relay: { provider: 'cloudron-smtp' }, - catchAll: [ ], - mailFromValidation: true, - dkimSelector: 'cloudron', - banner: { html: null, text: null } - }; - - before(function (done) { - domaindb.add(DOMAIN_0.domain, DOMAIN_0, done); - }); - - after(function (done) { - database._clear(done); - }); - - it('can get all domains', function (done) { - maildb.list(function (error, result) { - expect(error).to.equal(null); - expect(result).to.be.an(Array); - expect(result[0]).to.be.an('object'); - expect(result[0].domain).to.eql(MAIL_DOMAIN_0.domain); - - done(); - }); - }); - - it('can get domain', function (done) { - maildb.get(MAIL_DOMAIN_0.domain, function (error, result) { - expect(error).to.equal(null); - expect(result).to.be.an('object'); - expect(result).to.eql(MAIL_DOMAIN_0); - - done(); - }); - }); - }); }); diff --git a/src/test/mail-test.js b/src/test/mail-test.js index 9d405847b..72c23426f 100644 --- a/src/test/mail-test.js +++ b/src/test/mail-test.js @@ -2,116 +2,72 @@ /* global describe:false */ /* global before:false */ /* global after:false */ -/* global beforeEach:false */ 'use strict'; -var async = require('async'), - database = require('../database.js'), - domains = require('../domains.js'), +const common = require('./common.js'), expect = require('expect.js'), - mail = require('../mail.js'), - maildb = require('../maildb.js'), - nock = require('nock'), - settings = require('../settings.js'); - -const DOMAIN_0 = { - domain: 'example.com', - zoneName: 'example.com', - provider: 'manual', - config: {}, - fallbackCertificate: null, - tlsConfig: { provider: 'fallback' }, - wellKnown: null -}; - -const AUDIT_SOURCE = { - ip: '1.2.3.4' -}; - -function setup(done) { - async.series([ - database.initialize, - database._clear, - domains.add.bind(null, DOMAIN_0.domain, DOMAIN_0, AUDIT_SOURCE), - settings.setDashboardLocation.bind(null, DOMAIN_0.domain, 'my.' + DOMAIN_0.domain), - ], done); -} - -function cleanup(done) { - async.series([ - database._clear, - database.uninitialize - ], done); -} + mail = require('../mail.js'); describe('Mail', function () { + const { setup, cleanup, DOMAIN, AUDIT_SOURCE } = common; + before(setup); after(cleanup); - beforeEach(nock.cleanAll); - describe('values', function () { - it('can get default', function (done) { - mail.getDomain(DOMAIN_0.domain, function (error, mailConfig) { - expect(error).to.be(null); - expect(mailConfig.enabled).to.be(false); - expect(mailConfig.mailFromValidation).to.be(true); - expect(mailConfig.catchAll).to.eql([]); - expect(mailConfig.relay).to.eql({ provider: 'cloudron-smtp' }); - done(); - }); + it('can get default', async function () { + const mailConfig = await mail.getDomain(DOMAIN.domain); + expect(mailConfig.enabled).to.be(false); + expect(mailConfig.mailFromValidation).to.be(true); + expect(mailConfig.catchAll).to.eql([]); + expect(mailConfig.relay).to.eql({ provider: 'cloudron-smtp' }); }); - it('can set mail from validation', function (done) { - mail.setMailFromValidation(DOMAIN_0.domain, false, function (error) { - expect(error).to.be(null); - - mail.getDomain(DOMAIN_0.domain, function (error, mailConfig) { - expect(error).to.be(null); - expect(mailConfig.mailFromValidation).to.be(false); - - done(); - }); - }); + it('can get all domains', async function () { + const result = await mail.listDomains(); + expect(result).to.be.an(Array); + expect(result[0]).to.be.an('object'); + expect(result[0].domain).to.eql(DOMAIN.domain); }); - it('can set catch all address', function (done) { - mail.setCatchAllAddress(DOMAIN_0.domain, [ 'user1', 'user2' ], function (error) { - expect(error).to.be(null); + it('can set mail from validation', async function () { + await mail.setMailFromValidation(DOMAIN.domain, false); - mail.getDomain(DOMAIN_0.domain, function (error, mailConfig) { - expect(error).to.be(null); - expect(mailConfig.catchAll).to.eql([ 'user1', 'user2' ]); - done(); - }); - }); + const mailConfig = await mail.getDomain(DOMAIN.domain); + expect(mailConfig.mailFromValidation).to.be(false); }); - it('can set mail relay', function (done) { - var relay = { provider: 'external-smtp', host: 'mx.foo.com', port: 25 }; + it('can set catch all address', async function () { + await mail.setCatchAllAddress(DOMAIN.domain, [ 'user1', 'user2' ]); - maildb.update(DOMAIN_0.domain, { relay: relay }, function (error) { // skip the mail server verify() - expect(error).to.be(null); - - mail.getDomain(DOMAIN_0.domain, function (error, mailConfig) { - expect(error).to.be(null); - expect(mailConfig.relay).to.eql(relay); - done(); - }); - }); + const mailConfig = await mail.getDomain(DOMAIN.domain); + expect(mailConfig.catchAll).to.eql([ 'user1', 'user2' ]); }); - it('can enable mail', function (done) { - mail.setMailEnabled(DOMAIN_0.domain, true, AUDIT_SOURCE, function (error) { - expect(error).to.be(null); + it('can set mail relay', async function () { + const relay = { provider: 'external-smtp', host: 'mx.foo.com', port: 25 }; - mail.getDomain(DOMAIN_0.domain, function (error, mailConfig) { - expect(error).to.be(null); - expect(mailConfig.enabled).to.be(true); - done(); - }); - }); + await mail.setMailRelay(DOMAIN.domain, relay, { skipVerify: true }); + + const mailConfig = await mail.getDomain(DOMAIN.domain); + expect(mailConfig.relay).to.eql(relay); + }); + + it('can set banner', async function () { + const banner = { text: 'text', html: 'html' }; + + await mail.setBanner(DOMAIN.domain, banner); + + const mailConfig = await mail.getDomain(DOMAIN.domain); + expect(mailConfig.banner).to.eql(banner); + }); + + it('can enable mail', async function () { + await mail.setMailEnabled(DOMAIN.domain, true, AUDIT_SOURCE); + + const mailConfig = await mail.getDomain(DOMAIN.domain); + expect(mailConfig.enabled).to.be(true); }); }); }); diff --git a/src/test/users-test.js b/src/test/users-test.js index 33bb4dbef..dad9e8e29 100644 --- a/src/test/users-test.js +++ b/src/test/users-test.js @@ -11,7 +11,6 @@ const async = require('async'), expect = require('expect.js'), fs = require('fs'), mailboxdb = require('../mailboxdb.js'), - maildb = require('../maildb.js'), mailer = require('../mailer.js'), paths = require('../paths.js'), provision = require('../provision.js'), @@ -555,25 +554,6 @@ describe('User', function () { done(); }); }); - - it('succeeds with email enabled', function (done) { - // use maildb to not trigger further events - maildb.update(DOMAIN_0.domain, { enabled: true }, function (error) { - expect(error).not.to.be.ok(); - - users.get(userObject.id, function (error, result) { - expect(error).to.not.be.ok(); - expect(result).to.be.ok(); - expect(result.id).to.equal(userObject.id); - expect(result.email).to.equal(EMAIL.toLowerCase()); - expect(result.fallbackEmail).to.equal(EMAIL.toLowerCase()); - expect(result.username).to.equal(USERNAME.toLowerCase()); - expect(result.displayName).to.equal(DISPLAY_NAME); - - maildb.update(DOMAIN_0.domain, { enabled: false }, done); - }); - }); - }); }); describe('update', function () { diff --git a/src/wellknown.js b/src/wellknown.js index 8c9345efa..8cb913246 100644 --- a/src/wellknown.js +++ b/src/wellknown.js @@ -10,7 +10,8 @@ const assert = require('assert'), ejs = require('ejs'), fs = require('fs'), mail = require('./mail.js'), - settings = require('./settings.js'); + settings = require('./settings.js'), + util = require('util'); const MAIL_AUTOCONFIG_EJS = fs.readFileSync(__dirname + '/autoconfig.xml.ejs', { encoding: 'utf8' }); @@ -20,7 +21,9 @@ function get(domain, location, callback) { assert.strictEqual(typeof callback, 'function'); if (location === 'autoconfig/mail/config-v1.1.xml') { // this also gets a ?emailaddress - mail.getDomain(domain, function (error, mailDomain) { + const getDomainFunc = util.callbackify(mail.getDomain); + + getDomainFunc(domain, function (error, mailDomain) { if (error) return callback(new BoxError(BoxError.NOT_FOUND, error.message)); if (!mailDomain.enabled) return callback(new BoxError(BoxError.NOT_FOUND, 'Email not enabled'));