mailboxdb: merge into mail.js
This commit is contained in:
+3
-9
@@ -209,15 +209,9 @@ function runSystemChecks(callback) {
|
||||
], callback);
|
||||
}
|
||||
|
||||
function checkMailStatus(callback) {
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
mail.checkConfiguration(async function (error, message) {
|
||||
if (error) return callback(error);
|
||||
|
||||
await safe(notifications.alert(notifications.ALERT_MAIL_STATUS, 'Email is not configured properly', message));
|
||||
callback();
|
||||
});
|
||||
async function checkMailStatus() {
|
||||
const message = await mail.checkConfiguration();
|
||||
await notifications.alert(notifications.ALERT_MAIL_STATUS, 'Email is not configured properly', message);
|
||||
}
|
||||
|
||||
async function checkRebootRequired() {
|
||||
|
||||
+191
-207
@@ -18,7 +18,6 @@ const assert = require('assert'),
|
||||
groups = require('./groups.js'),
|
||||
ldap = require('ldapjs'),
|
||||
mail = require('./mail.js'),
|
||||
mailboxdb = require('./mailboxdb.js'),
|
||||
safe = require('safetydance'),
|
||||
services = require('./services.js'),
|
||||
users = require('./users.js'),
|
||||
@@ -261,151 +260,33 @@ function groupAdminsCompare(req, res, next) {
|
||||
});
|
||||
}
|
||||
|
||||
function mailboxSearch(req, res, next) {
|
||||
async function mailboxSearch(req, res, next) {
|
||||
debug('mailbox search: dn %s, scope %s, filter %s (from %s)', req.dn.toString(), req.scope, req.filter.toString(), req.connection.ldap.id);
|
||||
|
||||
// if cn is set we only search for one mailbox specifically
|
||||
if (req.dn.rdns[0].attrs.cn) {
|
||||
var email = req.dn.rdns[0].attrs.cn.value.toLowerCase();
|
||||
var parts = email.split('@');
|
||||
const email = req.dn.rdns[0].attrs.cn.value.toLowerCase();
|
||||
const parts = email.split('@');
|
||||
if (parts.length !== 2) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
||||
|
||||
mailboxdb.getMailbox(parts[0], parts[1], function (error, mailbox) {
|
||||
if (error && error.reason === BoxError.NOT_FOUND) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
||||
if (error) return next(new ldap.OperationsError(error.toString()));
|
||||
|
||||
if (!mailbox.active) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
||||
|
||||
let obj = {
|
||||
dn: req.dn.toString(),
|
||||
attributes: {
|
||||
objectclass: ['mailbox'],
|
||||
objectcategory: 'mailbox',
|
||||
cn: `${mailbox.name}@${mailbox.domain}`,
|
||||
uid: `${mailbox.name}@${mailbox.domain}`,
|
||||
mail: `${mailbox.name}@${mailbox.domain}`
|
||||
}
|
||||
};
|
||||
|
||||
// ensure all filter values are also lowercase
|
||||
var lowerCaseFilter = safe(function () { return ldap.parseFilter(req.filter.toString().toLowerCase()); }, null);
|
||||
if (!lowerCaseFilter) return next(new ldap.OperationsError(safe.error.toString()));
|
||||
|
||||
if (lowerCaseFilter.matches(obj.attributes)) {
|
||||
finalSend([ obj ], req, res, next);
|
||||
} else {
|
||||
res.end();
|
||||
}
|
||||
});
|
||||
} else if (req.dn.rdns[0].attrs.domain) { // legacy ldap mailbox search for old sogo
|
||||
var domain = req.dn.rdns[0].attrs.domain.value.toLowerCase();
|
||||
|
||||
mailboxdb.listMailboxes(domain, 1, 1000, function (error, mailboxes) {
|
||||
if (error) return next(new ldap.OperationsError(error.toString()));
|
||||
|
||||
mailboxes = mailboxes.filter(m => m.active);
|
||||
|
||||
let results = [];
|
||||
|
||||
// send mailbox objects
|
||||
mailboxes.forEach(function (mailbox) {
|
||||
var dn = ldap.parseDN(`cn=${mailbox.name}@${domain},domain=${domain},ou=mailboxes,dc=cloudron`);
|
||||
|
||||
var obj = {
|
||||
dn: dn.toString(),
|
||||
attributes: {
|
||||
objectclass: ['mailbox'],
|
||||
objectcategory: 'mailbox',
|
||||
cn: `${mailbox.name}@${domain}`,
|
||||
uid: `${mailbox.name}@${domain}`,
|
||||
mail: `${mailbox.name}@${domain}`
|
||||
}
|
||||
};
|
||||
|
||||
// ensure all filter values are also lowercase
|
||||
var lowerCaseFilter = safe(function () { return ldap.parseFilter(req.filter.toString().toLowerCase()); }, null);
|
||||
if (!lowerCaseFilter) return next(new ldap.OperationsError(safe.error.toString()));
|
||||
if ((req.dn.equals(dn) || req.dn.parentOf(dn)) && lowerCaseFilter.matches(obj.attributes)) {
|
||||
results.push(obj);
|
||||
}
|
||||
});
|
||||
|
||||
finalSend(results, req, res, next);
|
||||
});
|
||||
} else { // new sogo
|
||||
mailboxdb.listAllMailboxes(1, 1000, async function (error, mailboxes) {
|
||||
if (error) return next(new ldap.OperationsError(error.toString()));
|
||||
|
||||
mailboxes = mailboxes.filter(m => m.active);
|
||||
|
||||
let results = [];
|
||||
|
||||
for (const mailbox of mailboxes) {
|
||||
const dn = ldap.parseDN(`cn=${mailbox.name}@${mailbox.domain},ou=mailboxes,dc=cloudron`);
|
||||
|
||||
const [error, ownerObject] = await safe(mailbox.ownerType === mail.OWNERTYPE_USER ? users.get(mailbox.ownerId) : groups.get(mailbox.ownerId));
|
||||
if (error || !ownerObject) continue; // skip mailboxes with unknown user
|
||||
|
||||
const obj = {
|
||||
dn: dn.toString(),
|
||||
attributes: {
|
||||
objectclass: ['mailbox'],
|
||||
objectcategory: 'mailbox',
|
||||
displayname: mailbox.ownerType === mail.OWNERTYPE_USER ? ownerObject.displayName : ownerObject.name,
|
||||
cn: `${mailbox.name}@${mailbox.domain}`,
|
||||
uid: `${mailbox.name}@${mailbox.domain}`,
|
||||
mail: `${mailbox.name}@${mailbox.domain}`
|
||||
}
|
||||
};
|
||||
|
||||
mailbox.aliases.forEach(function (a, idx) {
|
||||
obj.attributes['mail' + idx] = `${a.name}@${a.domain}`;
|
||||
});
|
||||
|
||||
// ensure all filter values are also lowercase
|
||||
const lowerCaseFilter = safe(function () { return ldap.parseFilter(req.filter.toString().toLowerCase()); }, null);
|
||||
if (!lowerCaseFilter) return next(new ldap.OperationsError(safe.error.toString()));
|
||||
|
||||
if ((req.dn.equals(dn) || req.dn.parentOf(dn)) && lowerCaseFilter.matches(obj.attributes)) {
|
||||
results.push(obj);
|
||||
}
|
||||
}
|
||||
|
||||
finalSend(results, req, res, next);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function mailAliasSearch(req, res, next) {
|
||||
debug('mail alias get: dn %s, scope %s, filter %s (from %s)', req.dn.toString(), req.scope, req.filter.toString(), req.connection.ldap.id);
|
||||
|
||||
if (!req.dn.rdns[0].attrs.cn) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
||||
|
||||
var email = req.dn.rdns[0].attrs.cn.value.toLowerCase();
|
||||
var parts = email.split('@');
|
||||
if (parts.length !== 2) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
||||
|
||||
mailboxdb.getAlias(parts[0], parts[1], function (error, alias) {
|
||||
if (error && error.reason === BoxError.NOT_FOUND) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
||||
const [error, mailbox] = await safe(mail.getMailbox(parts[0], parts[1]));
|
||||
if (error) return next(new ldap.OperationsError(error.toString()));
|
||||
if (!mailbox) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
||||
if (!mailbox.active) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
||||
|
||||
if (!alias.active) return next(new ldap.NoSuchObjectError(req.dn.toString())); // there is no way to disable an alias. this is just here for completeness
|
||||
|
||||
// https://wiki.debian.org/LDAP/MigrationTools/Examples
|
||||
// https://docs.oracle.com/cd/E19455-01/806-5580/6jej518pp/index.html
|
||||
// member is fully qualified - https://docs.oracle.com/cd/E19957-01/816-6082-10/chap4.doc.html#43314
|
||||
let obj = {
|
||||
const obj = {
|
||||
dn: req.dn.toString(),
|
||||
attributes: {
|
||||
objectclass: ['nisMailAlias'],
|
||||
objectcategory: 'nisMailAlias',
|
||||
cn: `${alias.name}@${alias.domain}`,
|
||||
rfc822MailMember: `${alias.aliasName}@${alias.aliasDomain}`
|
||||
objectclass: ['mailbox'],
|
||||
objectcategory: 'mailbox',
|
||||
cn: `${mailbox.name}@${mailbox.domain}`,
|
||||
uid: `${mailbox.name}@${mailbox.domain}`,
|
||||
mail: `${mailbox.name}@${mailbox.domain}`
|
||||
}
|
||||
};
|
||||
|
||||
// ensure all filter values are also lowercase
|
||||
var lowerCaseFilter = safe(function () { return ldap.parseFilter(req.filter.toString().toLowerCase()); }, null);
|
||||
const lowerCaseFilter = safe(function () { return ldap.parseFilter(req.filter.toString().toLowerCase()); }, null);
|
||||
if (!lowerCaseFilter) return next(new ldap.OperationsError(safe.error.toString()));
|
||||
|
||||
if (lowerCaseFilter.matches(obj.attributes)) {
|
||||
@@ -413,10 +294,123 @@ function mailAliasSearch(req, res, next) {
|
||||
} else {
|
||||
res.end();
|
||||
}
|
||||
});
|
||||
} else if (req.dn.rdns[0].attrs.domain) { // legacy ldap mailbox search for old sogo
|
||||
const domain = req.dn.rdns[0].attrs.domain.value.toLowerCase();
|
||||
|
||||
let [error, mailboxes] = await safe(mail.listMailboxes(domain, 1, 1000));
|
||||
if (error) return next(new ldap.OperationsError(error.toString()));
|
||||
|
||||
mailboxes = mailboxes.filter(m => m.active);
|
||||
|
||||
let results = [];
|
||||
|
||||
// send mailbox objects
|
||||
mailboxes.forEach(function (mailbox) {
|
||||
var dn = ldap.parseDN(`cn=${mailbox.name}@${domain},domain=${domain},ou=mailboxes,dc=cloudron`);
|
||||
|
||||
var obj = {
|
||||
dn: dn.toString(),
|
||||
attributes: {
|
||||
objectclass: ['mailbox'],
|
||||
objectcategory: 'mailbox',
|
||||
cn: `${mailbox.name}@${domain}`,
|
||||
uid: `${mailbox.name}@${domain}`,
|
||||
mail: `${mailbox.name}@${domain}`
|
||||
}
|
||||
};
|
||||
|
||||
// ensure all filter values are also lowercase
|
||||
var lowerCaseFilter = safe(function () { return ldap.parseFilter(req.filter.toString().toLowerCase()); }, null);
|
||||
if (!lowerCaseFilter) return next(new ldap.OperationsError(safe.error.toString()));
|
||||
if ((req.dn.equals(dn) || req.dn.parentOf(dn)) && lowerCaseFilter.matches(obj.attributes)) {
|
||||
results.push(obj);
|
||||
}
|
||||
});
|
||||
|
||||
finalSend(results, req, res, next);
|
||||
} else { // new sogo
|
||||
let [error, mailboxes] = await safe(mail.listAllMailboxes(1, 1000));
|
||||
if (error) return next(new ldap.OperationsError(error.toString()));
|
||||
|
||||
mailboxes = mailboxes.filter(m => m.active);
|
||||
|
||||
let results = [];
|
||||
|
||||
for (const mailbox of mailboxes) {
|
||||
const dn = ldap.parseDN(`cn=${mailbox.name}@${mailbox.domain},ou=mailboxes,dc=cloudron`);
|
||||
|
||||
const [error, ownerObject] = await safe(mailbox.ownerType === mail.OWNERTYPE_USER ? users.get(mailbox.ownerId) : groups.get(mailbox.ownerId));
|
||||
if (error || !ownerObject) continue; // skip mailboxes with unknown user
|
||||
|
||||
const obj = {
|
||||
dn: dn.toString(),
|
||||
attributes: {
|
||||
objectclass: ['mailbox'],
|
||||
objectcategory: 'mailbox',
|
||||
displayname: mailbox.ownerType === mail.OWNERTYPE_USER ? ownerObject.displayName : ownerObject.name,
|
||||
cn: `${mailbox.name}@${mailbox.domain}`,
|
||||
uid: `${mailbox.name}@${mailbox.domain}`,
|
||||
mail: `${mailbox.name}@${mailbox.domain}`
|
||||
}
|
||||
};
|
||||
|
||||
mailbox.aliases.forEach(function (a, idx) {
|
||||
obj.attributes['mail' + idx] = `${a.name}@${a.domain}`;
|
||||
});
|
||||
|
||||
// ensure all filter values are also lowercase
|
||||
const lowerCaseFilter = safe(function () { return ldap.parseFilter(req.filter.toString().toLowerCase()); }, null);
|
||||
if (!lowerCaseFilter) return next(new ldap.OperationsError(safe.error.toString()));
|
||||
|
||||
if ((req.dn.equals(dn) || req.dn.parentOf(dn)) && lowerCaseFilter.matches(obj.attributes)) {
|
||||
results.push(obj);
|
||||
}
|
||||
}
|
||||
|
||||
finalSend(results, req, res, next);
|
||||
}
|
||||
}
|
||||
|
||||
function mailingListSearch(req, res, next) {
|
||||
async function mailAliasSearch(req, res, next) {
|
||||
debug('mail alias get: dn %s, scope %s, filter %s (from %s)', req.dn.toString(), req.scope, req.filter.toString(), req.connection.ldap.id);
|
||||
|
||||
if (!req.dn.rdns[0].attrs.cn) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
||||
|
||||
const email = req.dn.rdns[0].attrs.cn.value.toLowerCase();
|
||||
const parts = email.split('@');
|
||||
if (parts.length !== 2) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
||||
|
||||
const [error, alias] = await safe(mail.getAlias(parts[0], parts[1]));
|
||||
if (error) return next(new ldap.OperationsError(error.toString()));
|
||||
if (!alias) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
||||
|
||||
if (!alias.active) return next(new ldap.NoSuchObjectError(req.dn.toString())); // there is no way to disable an alias. this is just here for completeness
|
||||
|
||||
// https://wiki.debian.org/LDAP/MigrationTools/Examples
|
||||
// https://docs.oracle.com/cd/E19455-01/806-5580/6jej518pp/index.html
|
||||
// member is fully qualified - https://docs.oracle.com/cd/E19957-01/816-6082-10/chap4.doc.html#43314
|
||||
const obj = {
|
||||
dn: req.dn.toString(),
|
||||
attributes: {
|
||||
objectclass: ['nisMailAlias'],
|
||||
objectcategory: 'nisMailAlias',
|
||||
cn: `${alias.name}@${alias.domain}`,
|
||||
rfc822MailMember: `${alias.aliasName}@${alias.aliasDomain}`
|
||||
}
|
||||
};
|
||||
|
||||
// ensure all filter values are also lowercase
|
||||
const lowerCaseFilter = safe(function () { return ldap.parseFilter(req.filter.toString().toLowerCase()); }, null);
|
||||
if (!lowerCaseFilter) return next(new ldap.OperationsError(safe.error.toString()));
|
||||
|
||||
if (lowerCaseFilter.matches(obj.attributes)) {
|
||||
finalSend([ obj ], req, res, next);
|
||||
} else {
|
||||
res.end();
|
||||
}
|
||||
}
|
||||
|
||||
async function mailingListSearch(req, res, next) {
|
||||
debug('mailing list get: dn %s, scope %s, filter %s (from %s)', req.dn.toString(), req.scope, req.filter.toString(), req.connection.ldap.id);
|
||||
|
||||
if (!req.dn.rdns[0].attrs.cn) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
||||
@@ -426,36 +420,37 @@ function mailingListSearch(req, res, next) {
|
||||
if (parts.length !== 2) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
||||
const name = parts[0], domain = parts[1];
|
||||
|
||||
mail.resolveList(parts[0], parts[1], function (error, resolvedMembers, list) {
|
||||
if (error && error.reason === BoxError.NOT_FOUND) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
||||
if (error) return next(new ldap.OperationsError(error.toString()));
|
||||
const [error, result] = await safe(mail.resolveList(parts[0], parts[1]));
|
||||
if (error && error.reason === BoxError.NOT_FOUND) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
||||
if (error) return next(new ldap.OperationsError(error.toString()));
|
||||
|
||||
if (!list.active) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
||||
const { resolvedMembers, list } = result;
|
||||
|
||||
// http://ldapwiki.willeke.com/wiki/Original%20Mailgroup%20Schema%20From%20Netscape
|
||||
// members are fully qualified (https://docs.oracle.com/cd/E19444-01/816-6018-10/groups.htm#13356)
|
||||
var obj = {
|
||||
dn: req.dn.toString(),
|
||||
attributes: {
|
||||
objectclass: ['mailGroup'],
|
||||
objectcategory: 'mailGroup',
|
||||
cn: `${name}@${domain}`, // fully qualified
|
||||
mail: `${name}@${domain}`,
|
||||
membersOnly: list.membersOnly, // ldapjs only supports strings and string array. so this is not a bool!
|
||||
mgrpRFC822MailMember: resolvedMembers // fully qualified
|
||||
}
|
||||
};
|
||||
if (!list.active) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
||||
|
||||
// ensure all filter values are also lowercase
|
||||
var lowerCaseFilter = safe(function () { return ldap.parseFilter(req.filter.toString().toLowerCase()); }, null);
|
||||
if (!lowerCaseFilter) return next(new ldap.OperationsError(safe.error.toString()));
|
||||
|
||||
if (lowerCaseFilter.matches(obj.attributes)) {
|
||||
finalSend([ obj ], req, res, next);
|
||||
} else {
|
||||
res.end();
|
||||
// http://ldapwiki.willeke.com/wiki/Original%20Mailgroup%20Schema%20From%20Netscape
|
||||
// members are fully qualified (https://docs.oracle.com/cd/E19444-01/816-6018-10/groups.htm#13356)
|
||||
const obj = {
|
||||
dn: req.dn.toString(),
|
||||
attributes: {
|
||||
objectclass: ['mailGroup'],
|
||||
objectcategory: 'mailGroup',
|
||||
cn: `${name}@${domain}`, // fully qualified
|
||||
mail: `${name}@${domain}`,
|
||||
membersOnly: list.membersOnly, // ldapjs only supports strings and string array. so this is not a bool!
|
||||
mgrpRFC822MailMember: resolvedMembers // fully qualified
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// ensure all filter values are also lowercase
|
||||
const lowerCaseFilter = safe(function () { return ldap.parseFilter(req.filter.toString().toLowerCase()); }, null);
|
||||
if (!lowerCaseFilter) return next(new ldap.OperationsError(safe.error.toString()));
|
||||
|
||||
if (lowerCaseFilter.matches(obj.attributes)) {
|
||||
finalSend([ obj ], req, res, next);
|
||||
} else {
|
||||
res.end();
|
||||
}
|
||||
}
|
||||
|
||||
// Will attach req.user if successful
|
||||
@@ -524,39 +519,34 @@ async function verifyMailboxPassword(mailbox, password) {
|
||||
return verifiedUser;
|
||||
}
|
||||
|
||||
function authenticateUserMailbox(req, res, next) {
|
||||
async function authenticateUserMailbox(req, res, next) {
|
||||
debug('user mailbox auth: %s (from %s)', req.dn.toString(), req.connection.ldap.id);
|
||||
|
||||
if (!req.dn.rdns[0].attrs.cn) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
||||
|
||||
var email = req.dn.rdns[0].attrs.cn.value.toLowerCase();
|
||||
var parts = email.split('@');
|
||||
const email = req.dn.rdns[0].attrs.cn.value.toLowerCase();
|
||||
const parts = email.split('@');
|
||||
if (parts.length !== 2) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
||||
|
||||
const getDomainFunc = util.callbackify(mail.getDomain);
|
||||
const [error, domain] = await safe(mail.getDomain(parts[1]));
|
||||
if (error) return next(new ldap.OperationsError(error.message));
|
||||
if (!domain) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
||||
|
||||
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));
|
||||
if (!domain.enabled) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
||||
|
||||
if (!domain.enabled) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
||||
const [getMailboxError, mailbox] = await safe(mail.getMailbox(parts[0], parts[1]));
|
||||
if (getMailboxError) return next(new ldap.OperationsError(getMailboxError.message));
|
||||
if (!mailbox) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
||||
if (!mailbox.active) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
||||
|
||||
mailboxdb.getMailbox(parts[0], parts[1], async function (error, mailbox) {
|
||||
if (error && error.reason === BoxError.NOT_FOUND) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
||||
if (error) return next(new ldap.OperationsError(error.message));
|
||||
const [verifyError, result] = await safe(verifyMailboxPassword(mailbox, req.credentials || ''));
|
||||
if (verifyError && verifyError.reason === BoxError.NOT_FOUND) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
||||
if (verifyError && verifyError.reason === BoxError.INVALID_CREDENTIALS) return next(new ldap.InvalidCredentialsError(req.dn.toString()));
|
||||
if (verifyError) return next(new ldap.OperationsError(verifyError.message));
|
||||
|
||||
if (!mailbox.active) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
||||
eventlog.upsertLoginEvent(eventlog.ACTION_USER_LOGIN, { authType: 'ldap', mailboxId: email }, { userId: result.id, user: users.removePrivateFields(result) });
|
||||
|
||||
const [verifyError, result] = await safe(verifyMailboxPassword(mailbox, req.credentials || ''));
|
||||
if (verifyError && verifyError.reason === BoxError.NOT_FOUND) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
||||
if (verifyError && verifyError.reason === BoxError.INVALID_CREDENTIALS) return next(new ldap.InvalidCredentialsError(req.dn.toString()));
|
||||
if (verifyError) return next(new ldap.OperationsError(verifyError.message));
|
||||
|
||||
eventlog.upsertLoginEvent(eventlog.ACTION_USER_LOGIN, { authType: 'ldap', mailboxId: email }, { userId: result.id, user: users.removePrivateFields(result) });
|
||||
|
||||
res.end();
|
||||
});
|
||||
});
|
||||
res.end();
|
||||
}
|
||||
|
||||
function authenticateSftp(req, res, next) {
|
||||
@@ -658,7 +648,7 @@ function verifyAppMailboxPassword(addonId, username, password, callback) {
|
||||
});
|
||||
}
|
||||
|
||||
function authenticateMailAddon(req, res, next) {
|
||||
async function authenticateMailAddon(req, res, next) {
|
||||
debug('mail addon auth: %s (from %s)', req.dn.toString(), req.connection.ldap.id);
|
||||
|
||||
if (!req.dn.rdns[0].attrs.cn) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
||||
@@ -670,37 +660,31 @@ 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'));
|
||||
|
||||
const getDomainFunc = util.callbackify(mail.getDomain);
|
||||
const [error, domain] = await safe(mail.getDomain(parts[1]));
|
||||
if (error) return next(new ldap.OperationsError(error.message));
|
||||
if (!domain) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
||||
|
||||
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));
|
||||
if (addonId === 'recvmail' && !domain.enabled) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
||||
|
||||
if (addonId === 'recvmail' && !domain.enabled) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
||||
const [appPasswordError] = await safe(util.promisify(verifyAppMailboxPassword)(addonId, email, req.credentials || ''));
|
||||
if (!appPasswordError) return res.end(); // validated as app
|
||||
|
||||
verifyAppMailboxPassword(addonId, email, req.credentials || '', function (error) {
|
||||
if (!error) return res.end(); // validated as app
|
||||
if (appPasswordError && appPasswordError.reason === BoxError.INVALID_CREDENTIALS) return next(new ldap.InvalidCredentialsError(req.dn.toString()));
|
||||
if (appPasswordError && appPasswordError.reason !== BoxError.NOT_FOUND) return next(new ldap.OperationsError(appPasswordError.message));
|
||||
|
||||
if (error && error.reason === BoxError.INVALID_CREDENTIALS) return next(new ldap.InvalidCredentialsError(req.dn.toString()));
|
||||
if (error && error.reason !== BoxError.NOT_FOUND) return next(new ldap.OperationsError(error.message));
|
||||
const [getMailboxError, mailbox] = await safe(mail.getMailbox(parts[0], parts[1]));
|
||||
if (getMailboxError) return next(new ldap.OperationsError(getMailboxError.message));
|
||||
if (!mailbox) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
||||
if (!mailbox.active) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
||||
|
||||
mailboxdb.getMailbox(parts[0], parts[1], async function (error, mailbox) {
|
||||
if (error && error.reason === BoxError.NOT_FOUND) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
||||
if (error) return next(new ldap.OperationsError(error.message));
|
||||
const [verifyError, result] = await safe(verifyMailboxPassword(mailbox, req.credentials || ''));
|
||||
if (verifyError && verifyError.reason === BoxError.NOT_FOUND) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
||||
if (verifyError && verifyError.reason === BoxError.INVALID_CREDENTIALS) return next(new ldap.InvalidCredentialsError(req.dn.toString()));
|
||||
if (verifyError) return next(new ldap.OperationsError(verifyError.message));
|
||||
|
||||
if (!mailbox.active) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
||||
eventlog.upsertLoginEvent(eventlog.ACTION_USER_LOGIN, { authType: 'ldap', mailboxId: email }, { userId: result.id, user: users.removePrivateFields(result) });
|
||||
|
||||
const [verifyError, result] = await safe(verifyMailboxPassword(mailbox, req.credentials || ''));
|
||||
if (verifyError && verifyError.reason === BoxError.NOT_FOUND) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
||||
if (verifyError && verifyError.reason === BoxError.INVALID_CREDENTIALS) return next(new ldap.InvalidCredentialsError(req.dn.toString()));
|
||||
if (verifyError) return next(new ldap.OperationsError(verifyError.message));
|
||||
|
||||
eventlog.upsertLoginEvent(eventlog.ACTION_USER_LOGIN, { authType: 'ldap', mailboxId: email }, { userId: result.id, user: users.removePrivateFields(result) });
|
||||
|
||||
res.end();
|
||||
});
|
||||
});
|
||||
});
|
||||
res.end();
|
||||
}
|
||||
|
||||
function start(callback) {
|
||||
|
||||
+331
-318
@@ -37,11 +37,13 @@ exports = module.exports = {
|
||||
|
||||
getMailboxCount,
|
||||
listMailboxes,
|
||||
listAllMailboxes,
|
||||
getMailbox,
|
||||
addMailbox,
|
||||
updateMailbox,
|
||||
removeMailbox,
|
||||
delMailbox,
|
||||
|
||||
getAlias,
|
||||
getAliases,
|
||||
setAliases,
|
||||
|
||||
@@ -49,7 +51,7 @@ exports = module.exports = {
|
||||
getList,
|
||||
addList,
|
||||
updateList,
|
||||
removeList,
|
||||
delList,
|
||||
resolveList,
|
||||
|
||||
OWNERTYPE_USER: 'user',
|
||||
@@ -57,7 +59,11 @@ exports = module.exports = {
|
||||
|
||||
DEFAULT_MEMORY_LIMIT: 512 * 1024 * 1024,
|
||||
|
||||
_removeMailboxes: removeMailboxes,
|
||||
TYPE_MAILBOX: 'mailbox',
|
||||
TYPE_LIST: 'list',
|
||||
TYPE_ALIAS: 'alias',
|
||||
|
||||
_delByDomain: delByDomain,
|
||||
_readDkimPublicKeySync: readDkimPublicKeySync,
|
||||
_updateDomain: updateDomain
|
||||
};
|
||||
@@ -75,8 +81,8 @@ const assert = require('assert'),
|
||||
eventlog = require('./eventlog.js'),
|
||||
hat = require('./hat.js'),
|
||||
infra = require('./infra_version.js'),
|
||||
mailboxdb = require('./mailboxdb.js'),
|
||||
mailer = require('./mailer.js'),
|
||||
mysql = require('mysql'),
|
||||
net = require('net'),
|
||||
nodemailer = require('nodemailer'),
|
||||
path = require('path'),
|
||||
@@ -97,15 +103,38 @@ const assert = require('assert'),
|
||||
_ = require('underscore');
|
||||
|
||||
const DNS_OPTIONS = { timeout: 5000 };
|
||||
var NOOP_CALLBACK = function (error) { if (error) debug(error); };
|
||||
const REMOVE_MAILBOX = path.join(__dirname, 'scripts/rmmailbox.sh');
|
||||
const NOOP_CALLBACK = function (error) { if (error) debug(error); };
|
||||
const REMOVE_MAILBOX_CMD = path.join(__dirname, 'scripts/rmmailbox.sh');
|
||||
|
||||
const MAILBOX_FIELDS = [ 'name', 'type', 'ownerId', 'ownerType', 'aliasName', 'aliasDomain', 'creationTime', 'membersJson', 'membersOnly', 'domain', 'active' ].join(',');
|
||||
const MAILDB_FIELDS = [ 'domain', 'enabled', 'mailFromValidation', 'catchAllJson', 'relayJson', 'dkimSelector', 'bannerJson' ].join(',');
|
||||
|
||||
const domainsGet = util.callbackify(domains.get),
|
||||
domainsList = util.callbackify(domains.list);
|
||||
|
||||
function postProcess(data) {
|
||||
function postProcessMailbox(data) {
|
||||
data.members = safe.JSON.parse(data.membersJson) || [ ];
|
||||
delete data.membersJson;
|
||||
|
||||
data.membersOnly = !!data.membersOnly;
|
||||
data.active = !!data.active;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
function postProcessAliases(data) {
|
||||
const aliasNames = JSON.parse(data.aliasNames), aliasDomains = JSON.parse(data.aliasDomains);
|
||||
delete data.aliasNames;
|
||||
delete data.aliasDomains;
|
||||
data.aliases = [];
|
||||
for (let i = 0; i < aliasNames.length; i++) { // NOTE: aliasNames is [ null ] when no aliases
|
||||
if (aliasNames[i]) data.aliases[i] = { name: aliasNames[i], domain: aliasDomains[i] };
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
function postProcessDomain(data) {
|
||||
data.enabled = !!data.enabled; // int to boolean
|
||||
data.mailFromValidation = !!data.mailFromValidation; // int to boolean
|
||||
|
||||
@@ -542,121 +571,101 @@ function getStatus(domain, callback) {
|
||||
});
|
||||
}
|
||||
|
||||
function checkConfiguration(callback) {
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
async function checkConfiguration() {
|
||||
let messages = {};
|
||||
|
||||
domainsList(function (error, allDomains) {
|
||||
if (error) return callback(error);
|
||||
const allDomains = await listDomains();
|
||||
|
||||
async.eachSeries(allDomains, function (domainObject, iteratorCallback) {
|
||||
getStatus(domainObject.domain, function (error, result) {
|
||||
if (error) return iteratorCallback(error);
|
||||
for (const domainObject of allDomains) {
|
||||
const result = await util.promisify(getStatus)(domainObject.domain);
|
||||
|
||||
let message = [];
|
||||
let message = [];
|
||||
|
||||
Object.keys(result.dns).forEach((type) => {
|
||||
const record = result.dns[type];
|
||||
if (!record.status) message.push(`${type.toUpperCase()} DNS record (${record.type}) did not match.\n * Hostname: \`${record.name}\`\n * Expected: \`${record.expected}\`\n * Actual: \`${record.value}\``);
|
||||
});
|
||||
if (result.relay && result.relay.status === false) message.push(`Relay error: ${result.relay.value}`);
|
||||
if (result.rbl && result.rbl.status === false) { // rbl field contents is optional
|
||||
const servers = result.rbl.servers.map((bs) => `[${bs.name}](${bs.site})`); // in markdown
|
||||
message.push(`This server's IP \`${result.rbl.ip}\` is blacklisted in the following servers - ${servers.join(', ')}`);
|
||||
}
|
||||
|
||||
if (message.length) messages[domainObject.domain] = message;
|
||||
|
||||
iteratorCallback(null);
|
||||
});
|
||||
}, function (error) {
|
||||
if (error) return callback(error);
|
||||
|
||||
// create bulleted list for each domain
|
||||
let markdownMessage = '';
|
||||
Object.keys(messages).forEach((domain) => {
|
||||
markdownMessage += `**${domain}**\n`;
|
||||
markdownMessage += messages[domain].map((m) => `* ${m}\n`).join('');
|
||||
markdownMessage += '\n\n';
|
||||
});
|
||||
|
||||
if (markdownMessage) markdownMessage += 'Email Status is checked every 30 minutes.\n See the [troubleshooting docs](https://docs.cloudron.io/troubleshooting/#mail-dns) for more information.\n';
|
||||
|
||||
callback(null, markdownMessage); // empty message means all status checks succeeded
|
||||
Object.keys(result.dns).forEach((type) => {
|
||||
const record = result.dns[type];
|
||||
if (!record.status) message.push(`${type.toUpperCase()} DNS record (${record.type}) did not match.\n * Hostname: \`${record.name}\`\n * Expected: \`${record.expected}\`\n * Actual: \`${record.value}\``);
|
||||
});
|
||||
if (result.relay && result.relay.status === false) message.push(`Relay error: ${result.relay.value}`);
|
||||
if (result.rbl && result.rbl.status === false) { // rbl field contents is optional
|
||||
const servers = result.rbl.servers.map((bs) => `[${bs.name}](${bs.site})`); // in markdown
|
||||
message.push(`This server's IP \`${result.rbl.ip}\` is blacklisted in the following servers - ${servers.join(', ')}`);
|
||||
}
|
||||
|
||||
if (message.length) messages[domainObject.domain] = message;
|
||||
}
|
||||
|
||||
// create bulleted list for each domain
|
||||
let markdownMessage = '';
|
||||
Object.keys(messages).forEach((domain) => {
|
||||
markdownMessage += `**${domain}**\n`;
|
||||
markdownMessage += messages[domain].map((m) => `* ${m}\n`).join('');
|
||||
markdownMessage += '\n\n';
|
||||
});
|
||||
|
||||
if (markdownMessage) markdownMessage += 'Email Status is checked every 30 minutes.\n See the [troubleshooting docs](https://docs.cloudron.io/troubleshooting/#mail-dns) for more information.\n';
|
||||
|
||||
return markdownMessage; // empty message means all status checks succeeded
|
||||
}
|
||||
|
||||
function createMailConfig(mailFqdn, mailDomain, callback) {
|
||||
async function createMailConfig(mailFqdn, mailDomain) {
|
||||
assert.strictEqual(typeof mailFqdn, 'string');
|
||||
assert.strictEqual(typeof mailDomain, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
debug('createMailConfig: generating mail config');
|
||||
|
||||
const listDomainsFunc = util.callbackify(listDomains);
|
||||
const mailDomains = await listDomains();
|
||||
|
||||
listDomainsFunc(function (error, mailDomains) {
|
||||
if (error) return callback(error);
|
||||
const mailOutDomains = mailDomains.filter(d => d.relay.provider !== 'noop').map(d => d.domain).join(',');
|
||||
const mailInDomains = mailDomains.filter(function (d) { return d.enabled; }).map(function (d) { return d.domain; }).join(',');
|
||||
|
||||
const mailOutDomains = mailDomains.filter(d => d.relay.provider !== 'noop').map(d => d.domain).join(',');
|
||||
const mailInDomains = mailDomains.filter(function (d) { return d.enabled; }).map(function (d) { return d.domain; }).join(',');
|
||||
// mail_domain is used for SRS
|
||||
if (!safe.fs.writeFileSync(path.join(paths.ADDON_CONFIG_DIR, 'mail/mail.ini'),
|
||||
`mail_in_domains=${mailInDomains}\nmail_out_domains=${mailOutDomains}\nmail_server_name=${mailFqdn}\nmail_domain=${mailDomain}\n\n`, 'utf8')) {
|
||||
throw new BoxError(BoxError.FS_ERROR, `Could not create mail var file: ${safe.error.message}`);
|
||||
}
|
||||
|
||||
// mail_domain is used for SRS
|
||||
if (!safe.fs.writeFileSync(path.join(paths.ADDON_CONFIG_DIR, 'mail/mail.ini'),
|
||||
`mail_in_domains=${mailInDomains}\nmail_out_domains=${mailOutDomains}\nmail_server_name=${mailFqdn}\nmail_domain=${mailDomain}\n\n`, 'utf8')) {
|
||||
return callback(new BoxError(BoxError.FS_ERROR, 'Could not create mail var file:' + safe.error.message));
|
||||
// enable_outbound makes plugin forward email for relayed mail. non-relayed mail always hits LMTP plugin first
|
||||
if (!safe.fs.writeFileSync(path.join(paths.ADDON_CONFIG_DIR, 'mail/smtp_forward.ini'), 'enable_outbound=false\ndomain_selector=mail_from\n', 'utf8')) {
|
||||
throw new BoxError(BoxError.FS_ERROR, `Could not create smtp forward file: ${safe.error.message}`);
|
||||
}
|
||||
|
||||
// create sections for per-domain configuration
|
||||
for (const domain of mailDomains) {
|
||||
const catchAll = domain.catchAll.map(function (c) { return `${c}@${domain.domain}`; }).join(',');
|
||||
const mailFromValidation = domain.mailFromValidation;
|
||||
|
||||
if (!safe.fs.appendFileSync(path.join(paths.ADDON_CONFIG_DIR, 'mail/mail.ini'),
|
||||
`[${domain.domain}]\ncatch_all=${catchAll}\nmail_from_validation=${mailFromValidation}\n\n`, 'utf8')) {
|
||||
throw new BoxError(BoxError.FS_ERROR, `Could not create mail var file: ${safe.error.message}`);
|
||||
}
|
||||
|
||||
// enable_outbound makes plugin forward email for relayed mail. non-relayed mail always hits LMTP plugin first
|
||||
if (!safe.fs.writeFileSync(path.join(paths.ADDON_CONFIG_DIR, 'mail/smtp_forward.ini'), 'enable_outbound=false\ndomain_selector=mail_from\n', 'utf8')) {
|
||||
return callback(new BoxError(BoxError.FS_ERROR, 'Could not create smtp forward file:' + safe.error.message));
|
||||
if (!safe.fs.writeFileSync(`${paths.ADDON_CONFIG_DIR}/mail/banner/${domain.domain}.text`, domain.banner.text || '')) throw new BoxError(BoxError.FS_ERROR, `Could not create text banner file: ${safe.error.message}`);
|
||||
if (!safe.fs.writeFileSync(`${paths.ADDON_CONFIG_DIR}/mail/banner/${domain.domain}.html`, domain.banner.html || '')) throw new BoxError(BoxError.FS_ERROR, `Could not create html banner file: ${safe.error.message}`);
|
||||
|
||||
const relay = domain.relay;
|
||||
|
||||
const enableRelay = relay.provider !== 'cloudron-smtp' && relay.provider !== 'noop',
|
||||
host = relay.host || '',
|
||||
port = relay.port || 25,
|
||||
authType = relay.username ? 'plain' : '',
|
||||
username = relay.username || '',
|
||||
password = relay.password || '';
|
||||
|
||||
if (!enableRelay) continue;
|
||||
|
||||
if (!safe.fs.appendFileSync(paths.ADDON_CONFIG_DIR + '/mail/smtp_forward.ini',
|
||||
`[${domain.domain}]\nenable_outbound=true\nhost=${host}\nport=${port}\nenable_tls=true\nauth_type=${authType}\nauth_user=${username}\nauth_pass=${password}\n\n`, 'utf8')) {
|
||||
throw new BoxError(BoxError.FS_ERROR, `Could not create mail var file: ${safe.error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// create sections for per-domain configuration
|
||||
async.eachSeries(mailDomains, function (domain, iteratorDone) {
|
||||
const catchAll = domain.catchAll.map(function (c) { return `${c}@${domain.domain}`; }).join(',');
|
||||
const mailFromValidation = domain.mailFromValidation;
|
||||
|
||||
if (!safe.fs.appendFileSync(path.join(paths.ADDON_CONFIG_DIR, 'mail/mail.ini'),
|
||||
`[${domain.domain}]\ncatch_all=${catchAll}\nmail_from_validation=${mailFromValidation}\n\n`, 'utf8')) {
|
||||
return iteratorDone(new BoxError(BoxError.FS_ERROR, 'Could not create mail var file:' + safe.error.message));
|
||||
}
|
||||
|
||||
if (!safe.fs.writeFileSync(`${paths.ADDON_CONFIG_DIR}/mail/banner/${domain.domain}.text`, domain.banner.text || '')) return iteratorDone(new BoxError(BoxError.FS_ERROR, 'Could not create text banner file:' + safe.error.message));
|
||||
if (!safe.fs.writeFileSync(`${paths.ADDON_CONFIG_DIR}/mail/banner/${domain.domain}.html`, domain.banner.html || '')) return iteratorDone(new BoxError(BoxError.FS_ERROR, 'Could not create html banner file:' + safe.error.message));
|
||||
|
||||
const relay = domain.relay;
|
||||
|
||||
const enableRelay = relay.provider !== 'cloudron-smtp' && relay.provider !== 'noop',
|
||||
host = relay.host || '',
|
||||
port = relay.port || 25,
|
||||
authType = relay.username ? 'plain' : '',
|
||||
username = relay.username || '',
|
||||
password = relay.password || '';
|
||||
|
||||
if (!enableRelay) return iteratorDone();
|
||||
|
||||
if (!safe.fs.appendFileSync(paths.ADDON_CONFIG_DIR + '/mail/smtp_forward.ini',
|
||||
`[${domain.domain}]\nenable_outbound=true\nhost=${host}\nport=${port}\nenable_tls=true\nauth_type=${authType}\nauth_user=${username}\nauth_pass=${password}\n\n`, 'utf8')) {
|
||||
return iteratorDone(new BoxError(BoxError.FS_ERROR, 'Could not create mail var file:' + safe.error.message));
|
||||
}
|
||||
|
||||
iteratorDone();
|
||||
}, function (error) {
|
||||
if (error) return callback(error);
|
||||
|
||||
callback(null, mailInDomains.length !== 0 /* allowInbound */);
|
||||
});
|
||||
});
|
||||
return mailInDomains.length !== 0 /* allowInbound */;
|
||||
}
|
||||
|
||||
function configureMail(mailFqdn, mailDomain, serviceConfig, callback) {
|
||||
async function configureMail(mailFqdn, mailDomain, serviceConfig) {
|
||||
assert.strictEqual(typeof mailFqdn, 'string');
|
||||
assert.strictEqual(typeof mailDomain, 'string');
|
||||
assert.strictEqual(typeof serviceConfig, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
// mail (note: 2587 is hardcoded in mail container and app use this port)
|
||||
// MAIL_SERVER_NAME is the hostname of the mailserver i.e server uses these certs
|
||||
@@ -668,52 +677,43 @@ function configureMail(mailFqdn, mailDomain, serviceConfig, callback) {
|
||||
const memory = system.getMemoryAllocation(memoryLimit);
|
||||
const cloudronToken = hat(8 * 128), relayToken = hat(8 * 128);
|
||||
|
||||
const getCertificatePath = util.callbackify(reverseProxy.getCertificatePath);
|
||||
getCertificatePath(mailFqdn, mailDomain, function (error, bundle) {
|
||||
if (error) return callback(error);
|
||||
const bundle = await reverseProxy.getCertificatePath(mailFqdn, mailDomain);
|
||||
|
||||
const dhparamsFilePath = path.join(paths.ADDON_CONFIG_DIR, 'mail/dhparams.pem');
|
||||
const mailCertFilePath = path.join(paths.ADDON_CONFIG_DIR, 'mail/tls_cert.pem');
|
||||
const mailKeyFilePath = path.join(paths.ADDON_CONFIG_DIR, 'mail/tls_key.pem');
|
||||
const dhparamsFilePath = path.join(paths.ADDON_CONFIG_DIR, 'mail/dhparams.pem');
|
||||
const mailCertFilePath = path.join(paths.ADDON_CONFIG_DIR, 'mail/tls_cert.pem');
|
||||
const mailKeyFilePath = path.join(paths.ADDON_CONFIG_DIR, 'mail/tls_key.pem');
|
||||
|
||||
if (!safe.child_process.execSync(`cp ${paths.DHPARAMS_FILE} ${dhparamsFilePath}`)) return callback(new BoxError(BoxError.FS_ERROR, 'Could not copy dhparams:' + safe.error.message));
|
||||
if (!safe.child_process.execSync(`cp ${bundle.certFilePath} ${mailCertFilePath}`)) return callback(new BoxError(BoxError.FS_ERROR, 'Could not create cert file:' + safe.error.message));
|
||||
if (!safe.child_process.execSync(`cp ${bundle.keyFilePath} ${mailKeyFilePath}`)) return callback(new BoxError(BoxError.FS_ERROR, 'Could not create key file:' + safe.error.message));
|
||||
if (!safe.child_process.execSync(`cp ${paths.DHPARAMS_FILE} ${dhparamsFilePath}`)) throw new BoxError(BoxError.FS_ERROR, 'Could not copy dhparams:' + safe.error.message);
|
||||
if (!safe.child_process.execSync(`cp ${bundle.certFilePath} ${mailCertFilePath}`)) throw new BoxError(BoxError.FS_ERROR, 'Could not create cert file:' + safe.error.message);
|
||||
if (!safe.child_process.execSync(`cp ${bundle.keyFilePath} ${mailKeyFilePath}`)) throw new BoxError(BoxError.FS_ERROR, 'Could not create key file:' + safe.error.message);
|
||||
|
||||
async.series([
|
||||
shell.exec.bind(null, 'stopMail', 'docker stop mail || true'),
|
||||
shell.exec.bind(null, 'removeMail', 'docker rm -f mail || true'),
|
||||
], function (error) {
|
||||
if (error) return callback(error);
|
||||
await shell.promises.exec('stopMail', 'docker stop mail || true');
|
||||
await shell.promises.exec('removeMail', 'docker rm -f mail || true');
|
||||
|
||||
createMailConfig(mailFqdn, mailDomain, function (error, allowInbound) {
|
||||
if (error) return callback(error);
|
||||
const allowInbound = await createMailConfig(mailFqdn, mailDomain);
|
||||
|
||||
var ports = allowInbound ? '-p 587:2587 -p 993:9993 -p 4190:4190 -p 25:2587' : '';
|
||||
const ports = allowInbound ? '-p 587:2587 -p 993:9993 -p 4190:4190 -p 25:2587' : '';
|
||||
|
||||
const cmd = `docker run --restart=always -d --name="mail" \
|
||||
--net cloudron \
|
||||
--net-alias mail \
|
||||
--log-driver syslog \
|
||||
--log-opt syslog-address=udp://127.0.0.1:2514 \
|
||||
--log-opt syslog-format=rfc5424 \
|
||||
--log-opt tag=mail \
|
||||
-m ${memory} \
|
||||
--memory-swap ${memoryLimit} \
|
||||
--dns 172.18.0.1 \
|
||||
--dns-search=. \
|
||||
-e CLOUDRON_MAIL_TOKEN="${cloudronToken}" \
|
||||
-e CLOUDRON_RELAY_TOKEN="${relayToken}" \
|
||||
-v "${paths.MAIL_DATA_DIR}:/app/data" \
|
||||
-v "${paths.PLATFORM_DATA_DIR}/addons/mail:/etc/mail" \
|
||||
${ports} \
|
||||
--label isCloudronManaged=true \
|
||||
--read-only -v /run -v /tmp ${tag}`;
|
||||
const cmd = `docker run --restart=always -d --name="mail" \
|
||||
--net cloudron \
|
||||
--net-alias mail \
|
||||
--log-driver syslog \
|
||||
--log-opt syslog-address=udp://127.0.0.1:2514 \
|
||||
--log-opt syslog-format=rfc5424 \
|
||||
--log-opt tag=mail \
|
||||
-m ${memory} \
|
||||
--memory-swap ${memoryLimit} \
|
||||
--dns 172.18.0.1 \
|
||||
--dns-search=. \
|
||||
-e CLOUDRON_MAIL_TOKEN="${cloudronToken}" \
|
||||
-e CLOUDRON_RELAY_TOKEN="${relayToken}" \
|
||||
-v "${paths.MAIL_DATA_DIR}:/app/data" \
|
||||
-v "${paths.PLATFORM_DATA_DIR}/addons/mail:/etc/mail" \
|
||||
${ports} \
|
||||
--label isCloudronManaged=true \
|
||||
--read-only -v /run -v /tmp ${tag}`;
|
||||
|
||||
shell.exec('startMail', cmd, callback);
|
||||
});
|
||||
});
|
||||
});
|
||||
await shell.promises.exec('startMail', cmd);
|
||||
}
|
||||
|
||||
function getMailAuth(callback) {
|
||||
@@ -746,11 +746,12 @@ function restartMail(callback) {
|
||||
|
||||
if (process.env.BOX_ENV === 'test' && !process.env.TEST_CREATE_INFRA) return callback();
|
||||
|
||||
services.getServiceConfig('mail', function (error, serviceConfig) {
|
||||
services.getServiceConfig('mail', async function (error, serviceConfig) {
|
||||
if (error) return callback(error);
|
||||
|
||||
debug(`restartMail: restarting mail container with mailFqdn:${settings.mailFqdn()} dashboardDomain:${settings.dashboardDomain()}`);
|
||||
configureMail(settings.mailFqdn(), settings.dashboardDomain(), serviceConfig, callback);
|
||||
[error] = await safe(configureMail(settings.mailFqdn(), settings.dashboardDomain(), serviceConfig));
|
||||
callback(error);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -775,7 +776,7 @@ async function getDomain(domain) {
|
||||
|
||||
const result = await database.query(`SELECT ${MAILDB_FIELDS} FROM mail WHERE domain = ?`, [ domain ]);
|
||||
if (result.length === 0) return null;
|
||||
return postProcess(result[0]);
|
||||
return postProcessDomain(result[0]);
|
||||
}
|
||||
|
||||
async function updateDomain(domain, data) {
|
||||
@@ -804,7 +805,7 @@ async function updateDomain(domain, data) {
|
||||
|
||||
async function listDomains() {
|
||||
const results = await database.query(`SELECT ${MAILDB_FIELDS} FROM mail ORDER BY domain`);
|
||||
results.forEach(function (result) { postProcess(result); });
|
||||
results.forEach(function (result) { postProcessDomain(result); });
|
||||
return results;
|
||||
}
|
||||
|
||||
@@ -1126,79 +1127,99 @@ async function setMailEnabled(domain, enabled, auditSource) {
|
||||
await eventlog.add(enabled ? eventlog.ACTION_MAIL_ENABLED : eventlog.ACTION_MAIL_DISABLED, auditSource, { domain });
|
||||
}
|
||||
|
||||
function sendTestMail(domain, to, callback) {
|
||||
async function sendTestMail(domain, to) {
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert.strictEqual(typeof to, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
const getDomainFunc = util.callbackify(getDomain);
|
||||
const result = await getDomain(domain);
|
||||
if (!result) throw new BoxError(BoxError.NOT_FOUND, 'mail domain not found');
|
||||
|
||||
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);
|
||||
|
||||
callback();
|
||||
});
|
||||
});
|
||||
await util.promisify(mailer.sendTestMail)(result.domain);
|
||||
}
|
||||
|
||||
function listMailboxes(domain, search, page, perPage, callback) {
|
||||
async function listMailboxes(domain, search, page, perPage) {
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert(typeof search === 'string' || search === null);
|
||||
assert.strictEqual(typeof page, 'number');
|
||||
assert.strictEqual(typeof perPage, 'number');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
mailboxdb.listMailboxes(domain, search, page, perPage, function (error, result) {
|
||||
if (error) return callback(error);
|
||||
const escapedSearch = mysql.escape('%' + search + '%'); // this also quotes the string
|
||||
const searchQuery = search ? ` HAVING (name LIKE ${escapedSearch} OR aliasNames LIKE ${escapedSearch} OR aliasDomains LIKE ${escapedSearch})` : ''; // having instead of where because of aggregated columns use
|
||||
|
||||
callback(null, result);
|
||||
});
|
||||
const query = 'SELECT m1.name AS name, m1.domain AS domain, m1.ownerId AS ownerId, m1.ownerType as ownerType, m1.active as active, JSON_ARRAYAGG(m2.name) AS aliasNames, JSON_ARRAYAGG(m2.domain) AS aliasDomains '
|
||||
+ ` FROM (SELECT * FROM mailboxes WHERE type='${exports.TYPE_MAILBOX}') AS m1`
|
||||
+ ` LEFT JOIN (SELECT * FROM mailboxes WHERE type='${exports.TYPE_ALIAS}') AS m2`
|
||||
+ ' ON m1.name=m2.aliasName AND m1.domain=m2.aliasDomain AND m1.ownerId=m2.ownerId'
|
||||
+ ' WHERE m1.domain = ?'
|
||||
+ ' GROUP BY m1.name, m1.domain, m1.ownerId'
|
||||
+ searchQuery
|
||||
+ ' ORDER BY name LIMIT ?,?';
|
||||
|
||||
const results = await database.query(query, [ domain, (page-1)*perPage, perPage ]);
|
||||
|
||||
results.forEach(postProcessMailbox);
|
||||
results.forEach(postProcessAliases);
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
function getMailboxCount(domain, callback) {
|
||||
async function listAllMailboxes(page, perPage) {
|
||||
assert.strictEqual(typeof page, 'number');
|
||||
assert.strictEqual(typeof perPage, 'number');
|
||||
|
||||
const query = 'SELECT m1.name AS name, m1.domain AS domain, m1.ownerId AS ownerId, m1.ownerType as ownerType, m1.active as active, JSON_ARRAYAGG(m2.name) AS aliasNames, JSON_ARRAYAGG(m2.domain) AS aliasDomains '
|
||||
+ ` FROM (SELECT * FROM mailboxes WHERE type='${exports.TYPE_MAILBOX}') AS m1`
|
||||
+ ` LEFT JOIN (SELECT * FROM mailboxes WHERE type='${exports.TYPE_ALIAS}') AS m2`
|
||||
+ ' ON m1.name=m2.aliasName AND m1.domain=m2.aliasDomain AND m1.ownerId=m2.ownerId'
|
||||
+ ' GROUP BY m1.name, m1.domain, m1.ownerId'
|
||||
+ ' ORDER BY name LIMIT ?,?';
|
||||
|
||||
const results = await database.query(query, [ (page-1)*perPage, perPage ]);
|
||||
|
||||
results.forEach(postProcessMailbox);
|
||||
results.forEach(postProcessAliases);
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
async function getMailboxCount(domain) {
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
mailboxdb.getMailboxCount(domain, function (error, result) {
|
||||
if (error) return callback(error);
|
||||
const results = await database.query('SELECT COUNT(*) AS total FROM mailboxes WHERE type = ? AND domain = ?', [ exports.TYPE_MAILBOX, domain ]);
|
||||
|
||||
callback(null, result);
|
||||
});
|
||||
return results[0].total;
|
||||
}
|
||||
|
||||
function removeMailboxes(domain, callback) {
|
||||
async function delByDomain(domain) {
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
mailboxdb.delByDomain(domain, function (error) {
|
||||
if (error) return callback(error);
|
||||
|
||||
callback();
|
||||
});
|
||||
await database.query('DELETE FROM mailboxes WHERE domain = ?', [ domain ]);
|
||||
}
|
||||
|
||||
function getMailbox(name, domain, callback) {
|
||||
async function get(name, domain) {
|
||||
assert.strictEqual(typeof name, 'string');
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
mailboxdb.getMailbox(name, domain, function (error, result) {
|
||||
if (error) return callback(error);
|
||||
const results = await database.query('SELECT ' + MAILBOX_FIELDS + ' FROM mailboxes WHERE name = ? AND domain = ?', [ name, domain ]);
|
||||
if (results.length === 0) return null;
|
||||
|
||||
callback(null, result);
|
||||
});
|
||||
return postProcessMailbox(results[0]);
|
||||
}
|
||||
|
||||
function addMailbox(name, domain, data, auditSource, callback) {
|
||||
async function getMailbox(name, domain) {
|
||||
assert.strictEqual(typeof name, 'string');
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
|
||||
const results = await database.query('SELECT ' + MAILBOX_FIELDS + ' FROM mailboxes WHERE name = ? AND type = ? AND domain = ?', [ name, exports.TYPE_MAILBOX, domain ]);
|
||||
if (results.length === 0) return null;
|
||||
return postProcessMailbox(results[0]);
|
||||
}
|
||||
|
||||
async function addMailbox(name, domain, data, auditSource) {
|
||||
assert.strictEqual(typeof name, 'string');
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert.strictEqual(typeof data, 'object');
|
||||
assert.strictEqual(typeof auditSource, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
const { ownerId, ownerType, active } = data;
|
||||
assert.strictEqual(typeof ownerId, 'string');
|
||||
@@ -1207,26 +1228,23 @@ function addMailbox(name, domain, data, auditSource, callback) {
|
||||
|
||||
name = name.toLowerCase();
|
||||
|
||||
var error = validateName(name);
|
||||
if (error) return callback(error);
|
||||
let error = validateName(name);
|
||||
if (error) throw error;
|
||||
|
||||
if (ownerType !== exports.OWNERTYPE_USER && ownerType !== exports.OWNERTYPE_GROUP) return callback(new BoxError(BoxError.BAD_FIELD, 'bad owner type'));
|
||||
if (ownerType !== exports.OWNERTYPE_USER && ownerType !== exports.OWNERTYPE_GROUP) throw new BoxError(BoxError.BAD_FIELD, 'bad owner type');
|
||||
|
||||
mailboxdb.addMailbox(name, domain, data, function (error) {
|
||||
if (error) return callback(error);
|
||||
[error] = await safe(database.query('INSERT INTO mailboxes (name, type, domain, ownerId, ownerType, active) VALUES (?, ?, ?, ?, ?, ?)', [ name, exports.TYPE_MAILBOX, domain, ownerId, ownerType, active ]));
|
||||
if (error && error.code === 'ER_DUP_ENTRY') throw new BoxError(BoxError.ALREADY_EXISTS, 'mailbox already exists');
|
||||
if (error) throw error;
|
||||
|
||||
eventlog.add(eventlog.ACTION_MAIL_MAILBOX_ADD, auditSource, { name, domain, ownerId, ownerType, active });
|
||||
|
||||
callback(null);
|
||||
});
|
||||
eventlog.add(eventlog.ACTION_MAIL_MAILBOX_ADD, auditSource, { name, domain, ownerId, ownerType, active });
|
||||
}
|
||||
|
||||
function updateMailbox(name, domain, data, auditSource, callback) {
|
||||
async function updateMailbox(name, domain, data, auditSource) {
|
||||
assert.strictEqual(typeof name, 'string');
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert.strictEqual(typeof data, 'object');
|
||||
assert.strictEqual(typeof auditSource, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
const { ownerId, ownerType, active } = data;
|
||||
assert.strictEqual(typeof ownerId, 'string');
|
||||
@@ -1235,19 +1253,15 @@ function updateMailbox(name, domain, data, auditSource, callback) {
|
||||
|
||||
name = name.toLowerCase();
|
||||
|
||||
if (ownerType !== exports.OWNERTYPE_USER && ownerType !== exports.OWNERTYPE_GROUP) return callback(new BoxError(BoxError.BAD_FIELD, 'bad owner type'));
|
||||
if (ownerType !== exports.OWNERTYPE_USER && ownerType !== exports.OWNERTYPE_GROUP) throw new BoxError(BoxError.BAD_FIELD, 'bad owner type');
|
||||
|
||||
getMailbox(name, domain, function (error, result) {
|
||||
if (error) return callback(error);
|
||||
const mailbox = await getMailbox(name, domain);
|
||||
if (!mailbox) throw new BoxError(BoxError.NOT_FOUND, 'No such mailbox');
|
||||
|
||||
mailboxdb.updateMailbox(name, domain, data, function (error) {
|
||||
if (error) return callback(error);
|
||||
const result = await database.query('UPDATE mailboxes SET ownerId = ?, ownerType = ?, active = ? WHERE name = ? AND domain = ?', [ ownerId, ownerType, active, name, domain ]);
|
||||
if (result.affectedRows === 0) throw new BoxError(BoxError.NOT_FOUND, 'Mailbox not found');
|
||||
|
||||
eventlog.add(eventlog.ACTION_MAIL_MAILBOX_UPDATE, auditSource, { name, domain, oldUserId: result.userId, ownerId, ownerType, active });
|
||||
|
||||
callback(null);
|
||||
});
|
||||
});
|
||||
eventlog.add(eventlog.ACTION_MAIL_MAILBOX_UPDATE, auditSource, { name, domain, oldUserId: mailbox.userId, ownerId, ownerType, active });
|
||||
}
|
||||
|
||||
function removeSolrIndex(mailbox, callback) {
|
||||
@@ -1267,101 +1281,117 @@ function removeSolrIndex(mailbox, callback) {
|
||||
});
|
||||
}
|
||||
|
||||
function removeMailbox(name, domain, options, auditSource, callback) {
|
||||
async function delMailbox(name, domain, options, auditSource) {
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert.strictEqual(typeof name, 'string');
|
||||
assert.strictEqual(typeof options, 'object');
|
||||
assert.strictEqual(typeof auditSource, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
const mailbox =`${name}@${domain}`;
|
||||
const deleteMailFunc = options.deleteMails ? shell.sudo.bind(null, 'removeMailbox', [ REMOVE_MAILBOX, mailbox ], {}) : (next) => next();
|
||||
if (options.deleteMails) {
|
||||
const [error] = await safe(shell.promises.sudo('removeMailbox', [ REMOVE_MAILBOX_CMD, mailbox ], {}));
|
||||
if (error) throw new BoxError(BoxError.FS_ERROR, `Error removing mailbox: ${error.message}`);
|
||||
}
|
||||
|
||||
deleteMailFunc(function (error) {
|
||||
if (error) return callback(new BoxError(BoxError.FS_ERROR, `Error removing mailbox: ${error.message}`));
|
||||
// deletes aliases as well
|
||||
const result = await database.query('DELETE FROM mailboxes WHERE ((name=? AND domain=?) OR (aliasName = ? AND aliasDomain=?))', [ name, domain, name, domain ]);
|
||||
if (result.affectedRows === 0) throw new BoxError(BoxError.NOT_FOUND, 'Mailbox not found');
|
||||
|
||||
mailboxdb.del(name, domain, function (error) {
|
||||
if (error) return callback(error);
|
||||
|
||||
removeSolrIndex(mailbox, NOOP_CALLBACK);
|
||||
eventlog.add(eventlog.ACTION_MAIL_MAILBOX_REMOVE, auditSource, { name, domain });
|
||||
|
||||
callback();
|
||||
});
|
||||
});
|
||||
removeSolrIndex(mailbox, NOOP_CALLBACK);
|
||||
eventlog.add(eventlog.ACTION_MAIL_MAILBOX_REMOVE, auditSource, { name, domain });
|
||||
}
|
||||
|
||||
function getAliases(name, domain, callback) {
|
||||
async function getAlias(name, domain) {
|
||||
assert.strictEqual(typeof name, 'string');
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
getMailbox(name, domain, function (error) {
|
||||
if (error) return callback(error);
|
||||
const results = await database.query(`SELECT ${MAILBOX_FIELDS} FROM mailboxes WHERE name = ? AND type = ? AND domain = ?`, [ name, exports.TYPE_ALIAS, domain ]);
|
||||
if (results.length === 0) return null;
|
||||
|
||||
mailboxdb.getAliasesForName(name, domain, function (error, aliases) {
|
||||
if (error) return callback(error);
|
||||
results.forEach(function (result) { postProcessMailbox(result); });
|
||||
|
||||
callback(null, aliases);
|
||||
});
|
||||
});
|
||||
return results[0];
|
||||
}
|
||||
|
||||
function setAliases(name, domain, aliases, callback) {
|
||||
async function getAliases(name, domain) {
|
||||
assert.strictEqual(typeof name, 'string');
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
|
||||
const result = await getMailbox(name, domain); // check if mailbox exists
|
||||
if (result === null) throw new BoxError(BoxError.NOT_FOUND, 'No such mailbox');
|
||||
return await database.query('SELECT name, domain FROM mailboxes WHERE type = ? AND aliasName = ? AND aliasDomain = ? ORDER BY name', [ exports.TYPE_ALIAS, name, domain ]);
|
||||
}
|
||||
|
||||
async function setAliases(name, domain, aliases) {
|
||||
assert.strictEqual(typeof name, 'string');
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert(Array.isArray(aliases));
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
for (var i = 0; i < aliases.length; i++) {
|
||||
for (let i = 0; i < aliases.length; i++) {
|
||||
let name = aliases[i].name.toLowerCase();
|
||||
let domain = aliases[i].domain.toLowerCase();
|
||||
|
||||
let error = validateName(name);
|
||||
if (error) return callback(error);
|
||||
if (error) throw error;
|
||||
|
||||
if (!validator.isEmail(`${name}@${domain}`)) return callback(new BoxError(BoxError.BAD_FIELD, `Invalid email: ${name}@${domain}`));
|
||||
if (!validator.isEmail(`${name}@${domain}`)) throw new BoxError(BoxError.BAD_FIELD, `Invalid email: ${name}@${domain}`);
|
||||
aliases[i] = { name, domain };
|
||||
}
|
||||
mailboxdb.setAliasesForName(name, domain, aliases, function (error) {
|
||||
if (error) return callback(error);
|
||||
|
||||
callback(null);
|
||||
const results = await database.query('SELECT ' + MAILBOX_FIELDS + ' FROM mailboxes WHERE name = ? AND domain = ?', [ name, domain ]);
|
||||
if (results.length === 0) throw new BoxError(BoxError.NOT_FOUND, 'Mailbox not found');
|
||||
|
||||
let queries = [];
|
||||
// clear existing aliases
|
||||
queries.push({ query: 'DELETE FROM mailboxes WHERE aliasName = ? AND aliasDomain = ? AND type = ?', args: [ name, domain, exports.TYPE_ALIAS ] });
|
||||
aliases.forEach(function (alias) {
|
||||
queries.push({ query: 'INSERT INTO mailboxes (name, domain, type, aliasName, aliasDomain, ownerId, ownerType) VALUES (?, ?, ?, ?, ?, ?, ?)',
|
||||
args: [ alias.name, alias.domain, exports.TYPE_ALIAS, name, domain, results[0].ownerId, results[0].ownerType ] });
|
||||
});
|
||||
|
||||
const [error] = await safe(database.transaction(queries));
|
||||
if (error && error.code === 'ER_DUP_ENTRY' && error.message.indexOf('mailboxes_name_domain_unique_index') !== -1) {
|
||||
const aliasMatch = error.message.match(new RegExp(`^ER_DUP_ENTRY: Duplicate entry '(.*)-${domain}' for key 'mailboxes_name_domain_unique_index'$`));
|
||||
if (!aliasMatch) throw new BoxError(BoxError.ALREADY_EXISTS, error.message);
|
||||
|
||||
throw new BoxError(BoxError.ALREADY_EXISTS, `Mailbox, mailinglist or alias for ${aliasMatch[1]} already exists`);
|
||||
}
|
||||
if (error) throw error;
|
||||
}
|
||||
|
||||
function getLists(domain, search, page, perPage, callback) {
|
||||
async function getLists(domain, search, page, perPage) {
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert(typeof search === 'string' || search === null);
|
||||
assert.strictEqual(typeof page, 'number');
|
||||
assert.strictEqual(typeof perPage, 'number');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
mailboxdb.getLists(domain, search, page, perPage, function (error, result) {
|
||||
if (error) return callback(error);
|
||||
let query = `SELECT ${MAILBOX_FIELDS} FROM mailboxes WHERE type = ? AND domain = ?`;
|
||||
if (search) query += ' AND (name LIKE ' + mysql.escape('%' + search + '%') + ' OR membersJson LIKE ' + mysql.escape('%' + search + '%') + ')';
|
||||
|
||||
callback(null, result);
|
||||
});
|
||||
query += 'ORDER BY name LIMIT ?,?';
|
||||
|
||||
const results = await database.query(query, [ exports.TYPE_LIST, domain, (page-1)*perPage, perPage ]);
|
||||
|
||||
results.forEach(function (result) { postProcessMailbox(result); });
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
function getList(name, domain, callback) {
|
||||
async function getList(name, domain) {
|
||||
assert.strictEqual(typeof name, 'string');
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
mailboxdb.getList(name, domain, function (error, result) {
|
||||
if (error) return callback(error);
|
||||
const results = await database.query('SELECT ' + MAILBOX_FIELDS + ' FROM mailboxes WHERE type = ? AND name = ? AND domain = ?', [ exports.TYPE_LIST, name, domain ]);
|
||||
if (results.length === 0) return null;
|
||||
|
||||
callback(null, result);
|
||||
});
|
||||
return postProcessMailbox(results[0]);
|
||||
}
|
||||
|
||||
function addList(name, domain, data, auditSource, callback) {
|
||||
async function addList(name, domain, data, auditSource) {
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert.strictEqual(typeof name, 'string');
|
||||
assert.strictEqual(typeof data, 'object');
|
||||
assert.strictEqual(typeof auditSource, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
const { members, membersOnly, active } = data;
|
||||
assert(Array.isArray(members));
|
||||
@@ -1370,28 +1400,25 @@ function addList(name, domain, data, auditSource, callback) {
|
||||
|
||||
name = name.toLowerCase();
|
||||
|
||||
var error = validateName(name);
|
||||
if (error) return callback(error);
|
||||
let error = validateName(name);
|
||||
if (error) throw error;
|
||||
|
||||
for (var i = 0; i < members.length; i++) {
|
||||
if (!validator.isEmail(members[i])) return callback(new BoxError(BoxError.BAD_FIELD, 'Invalid mail member: ' + members[i]));
|
||||
for (let i = 0; i < members.length; i++) {
|
||||
if (!validator.isEmail(members[i])) throw new BoxError(BoxError.BAD_FIELD, 'Invalid mail member: ' + members[i]);
|
||||
}
|
||||
|
||||
mailboxdb.addList(name, domain, data, function (error) {
|
||||
if (error) return callback(error);
|
||||
[error] = await safe(database.query('INSERT INTO mailboxes (name, type, domain, ownerId, ownerType, membersJson, membersOnly, active) VALUES (?, ?, ?, ?, ?, ?, ?, ?)', [ name, exports.TYPE_LIST, domain, 'admin', 'user', JSON.stringify(members), membersOnly, active ]));
|
||||
if (error && error.code === 'ER_DUP_ENTRY') throw new BoxError(BoxError.ALREADY_EXISTS, 'mailbox already exists');
|
||||
if (error) throw error;
|
||||
|
||||
eventlog.add(eventlog.ACTION_MAIL_LIST_ADD, auditSource, { name, domain, members, membersOnly, active });
|
||||
|
||||
callback();
|
||||
});
|
||||
eventlog.add(eventlog.ACTION_MAIL_LIST_ADD, auditSource, { name, domain, members, membersOnly, active });
|
||||
}
|
||||
|
||||
function updateList(name, domain, data, auditSource, callback) {
|
||||
async function updateList(name, domain, data, auditSource) {
|
||||
assert.strictEqual(typeof name, 'string');
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert.strictEqual(typeof data, 'object');
|
||||
assert.strictEqual(typeof auditSource, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
const { members, membersOnly, active } = data;
|
||||
assert(Array.isArray(members));
|
||||
@@ -1400,90 +1427,76 @@ function updateList(name, domain, data, auditSource, callback) {
|
||||
|
||||
name = name.toLowerCase();
|
||||
|
||||
var error = validateName(name);
|
||||
if (error) return callback(error);
|
||||
let error = validateName(name);
|
||||
if (error) throw error;
|
||||
|
||||
for (var i = 0; i < members.length; i++) {
|
||||
if (!validator.isEmail(members[i])) return callback(new BoxError(BoxError.BAD_FIELD, 'Invalid email: ' + members[i]));
|
||||
for (let i = 0; i < members.length; i++) {
|
||||
if (!validator.isEmail(members[i])) throw new BoxError(BoxError.BAD_FIELD, 'Invalid email: ' + members[i]);
|
||||
}
|
||||
|
||||
getList(name, domain, function (error, result) {
|
||||
if (error) return callback(error);
|
||||
const result = await database.query('UPDATE mailboxes SET membersJson = ?, membersOnly = ?, active = ? WHERE name = ? AND domain = ?',
|
||||
[ JSON.stringify(members), membersOnly, active, name, domain ]);
|
||||
if (result.affectedRows === 0) throw new BoxError(BoxError.NOT_FOUND, 'Mailbox not found');
|
||||
|
||||
mailboxdb.updateList(name, domain, data, function (error) {
|
||||
if (error) return callback(error);
|
||||
|
||||
eventlog.add(eventlog.ACTION_MAIL_MAILBOX_UPDATE, auditSource, { name, domain, oldMembers: result.members, members, membersOnly, active });
|
||||
|
||||
callback(null);
|
||||
});
|
||||
});
|
||||
eventlog.add(eventlog.ACTION_MAIL_MAILBOX_UPDATE, auditSource, { name, domain, oldMembers: result.members, members, membersOnly, active });
|
||||
}
|
||||
|
||||
function removeList(name, domain, auditSource, callback) {
|
||||
async function delList(name, domain, auditSource) {
|
||||
assert.strictEqual(typeof name, 'string');
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert.strictEqual(typeof auditSource, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
mailboxdb.del(name, domain, function (error) {
|
||||
if (error) return callback(error);
|
||||
// deletes aliases as well
|
||||
const result = await database.query('DELETE FROM mailboxes WHERE ((name=? AND domain=?) OR (aliasName = ? AND aliasDomain=?))', [ name, domain, name, domain ]);
|
||||
if (result.affectedRows === 0) throw new BoxError(BoxError.NOT_FOUND, 'Mailbox not found');
|
||||
|
||||
eventlog.add(eventlog.ACTION_MAIL_LIST_REMOVE, auditSource, { name, domain });
|
||||
|
||||
callback();
|
||||
});
|
||||
eventlog.add(eventlog.ACTION_MAIL_LIST_REMOVE, auditSource, { name, domain });
|
||||
}
|
||||
|
||||
// resolves the members of a list. i.e the lists and aliases
|
||||
function resolveList(listName, listDomain, callback) {
|
||||
async function resolveList(listName, listDomain) {
|
||||
assert.strictEqual(typeof listName, 'string');
|
||||
assert.strictEqual(typeof listDomain, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
const listDomainsFunc = util.callbackify(listDomains);
|
||||
const mailDomains = await listDomains();
|
||||
const mailInDomains = mailDomains.filter(function (d) { return d.enabled; }).map(function (d) { return d.domain; }).join(',');
|
||||
|
||||
listDomainsFunc(function (error, mailDomains) {
|
||||
if (error) return callback(error);
|
||||
const list = await getList(listName, listDomain);
|
||||
if (!list) throw new BoxError(BoxError.NOT_FOUND, 'List not found');
|
||||
|
||||
const mailInDomains = mailDomains.filter(function (d) { return d.enabled; }).map(function (d) { return d.domain; }).join(',');
|
||||
let resolvedMembers = [], toResolve = list.members.slice(), visited = []; // slice creates a copy of array
|
||||
|
||||
mailboxdb.getList(listName, listDomain, function (error, list) {
|
||||
if (error) return callback(error);
|
||||
while (toResolve.length != 0) {
|
||||
const toProcess = toResolve.shift();
|
||||
const parts = toProcess.split('@');
|
||||
const memberName = parts[0].split('+')[0], memberDomain = parts[1];
|
||||
|
||||
let result = [], toResolve = list.members.slice(), visited = []; // slice creates a copy of array
|
||||
if (!mailInDomains.includes(memberDomain)) { // external domain
|
||||
resolvedMembers.push(toProcess);
|
||||
continue;
|
||||
}
|
||||
|
||||
async.whilst((testDone) => testDone(null, toResolve.length != 0), function (iteratorCallback) {
|
||||
const toProcess = toResolve.shift();
|
||||
const parts = toProcess.split('@');
|
||||
const memberName = parts[0].split('+')[0], memberDomain = parts[1];
|
||||
const member =`${memberName}@${memberDomain}`; // cleaned up without any '+' subaddress
|
||||
if (visited.includes(member)) {
|
||||
debug(`resolveList: list ${listName}@${listDomain} has a recursion at member ${member}`);
|
||||
continue;
|
||||
}
|
||||
visited.push(member);
|
||||
|
||||
if (!mailInDomains.includes(memberDomain)) { result.push(toProcess); return iteratorCallback(); } // external domain
|
||||
const entry = await get(memberName, memberDomain);
|
||||
if (!entry) { // let it bounce
|
||||
resolvedMembers.push(member);
|
||||
continue;
|
||||
}
|
||||
|
||||
const member =`${memberName}@${memberDomain}`; // cleaned up without any '+' subaddress
|
||||
if (visited.includes(member)) {
|
||||
debug(`resolveList: list ${listName}@${listDomain} has a recursion at member ${member}`);
|
||||
return iteratorCallback();
|
||||
}
|
||||
visited.push(member);
|
||||
if (entry.type === exports.TYPE_MAILBOX) { // concrete mailbox
|
||||
resolvedMembers.push(member);
|
||||
} else if (entry.type === exports.TYPE_ALIAS) { // resolve aliases
|
||||
toResolve = toResolve.concat(`${entry.aliasName}@${entry.aliasDomain}`);
|
||||
} else { // resolve list members
|
||||
toResolve = toResolve.concat(entry.members);
|
||||
}
|
||||
}
|
||||
|
||||
mailboxdb.get(memberName, memberDomain, function (error, entry) {
|
||||
if (error && error.reason == BoxError.NOT_FOUND) { result.push(member); return iteratorCallback(); } // let it bounce
|
||||
if (error) return iteratorCallback(error);
|
||||
|
||||
if (entry.type === mailboxdb.TYPE_MAILBOX) { // concrete mailbox
|
||||
result.push(member);
|
||||
} else if (entry.type === mailboxdb.TYPE_ALIAS) { // resolve aliases
|
||||
toResolve = toResolve.concat(`${entry.aliasName}@${entry.aliasDomain}`);
|
||||
} else { // resolve list members
|
||||
toResolve = toResolve.concat(entry.members);
|
||||
}
|
||||
|
||||
iteratorCallback();
|
||||
});
|
||||
}, function (error) {
|
||||
callback(error, result, list);
|
||||
});
|
||||
});
|
||||
});
|
||||
return { resolvedMembers, list };
|
||||
}
|
||||
|
||||
@@ -1,408 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
exports = module.exports = {
|
||||
addMailbox,
|
||||
addList,
|
||||
|
||||
updateMailbox,
|
||||
updateList,
|
||||
del,
|
||||
|
||||
getMailboxCount,
|
||||
listMailboxes,
|
||||
getLists,
|
||||
|
||||
listAllMailboxes,
|
||||
|
||||
get,
|
||||
getMailbox,
|
||||
getList,
|
||||
getAlias,
|
||||
|
||||
getAliasesForName,
|
||||
setAliasesForName,
|
||||
|
||||
getByOwnerId,
|
||||
delByOwnerId,
|
||||
delByDomain,
|
||||
|
||||
updateName,
|
||||
|
||||
_clear: clear,
|
||||
|
||||
TYPE_MAILBOX: 'mailbox',
|
||||
TYPE_LIST: 'list',
|
||||
TYPE_ALIAS: 'alias'
|
||||
};
|
||||
|
||||
var assert = require('assert'),
|
||||
BoxError = require('./boxerror.js'),
|
||||
database = require('./database.js'),
|
||||
mysql = require('mysql'),
|
||||
safe = require('safetydance'),
|
||||
util = require('util');
|
||||
|
||||
var MAILBOX_FIELDS = [ 'name', 'type', 'ownerId', 'ownerType', 'aliasName', 'aliasDomain', 'creationTime', 'membersJson', 'membersOnly', 'domain', 'active' ].join(',');
|
||||
|
||||
function postProcess(data) {
|
||||
data.members = safe.JSON.parse(data.membersJson) || [ ];
|
||||
delete data.membersJson;
|
||||
|
||||
data.membersOnly = !!data.membersOnly;
|
||||
data.active = !!data.active;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
function postProcessAliases(data) {
|
||||
const aliasNames = JSON.parse(data.aliasNames), aliasDomains = JSON.parse(data.aliasDomains);
|
||||
delete data.aliasNames;
|
||||
delete data.aliasDomains;
|
||||
data.aliases = [];
|
||||
for (let i = 0; i < aliasNames.length; i++) { // NOTE: aliasNames is [ null ] when no aliases
|
||||
if (aliasNames[i]) data.aliases[i] = { name: aliasNames[i], domain: aliasDomains[i] };
|
||||
}
|
||||
}
|
||||
|
||||
function addMailbox(name, domain, data, callback) {
|
||||
assert.strictEqual(typeof name, 'string');
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert.strictEqual(typeof data, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
const { ownerId, ownerType, active } = data;
|
||||
assert.strictEqual(typeof ownerId, 'string');
|
||||
assert.strictEqual(typeof ownerType, 'string');
|
||||
assert.strictEqual(typeof active, 'boolean');
|
||||
|
||||
database.query('INSERT INTO mailboxes (name, type, domain, ownerId, ownerType, active) VALUES (?, ?, ?, ?, ?, ?)', [ name, exports.TYPE_MAILBOX, domain, ownerId, ownerType, active ], function (error) {
|
||||
if (error && error.code === 'ER_DUP_ENTRY') return callback(new BoxError(BoxError.ALREADY_EXISTS, 'mailbox already exists'));
|
||||
if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error));
|
||||
|
||||
callback(null);
|
||||
});
|
||||
}
|
||||
|
||||
function updateMailbox(name, domain, data, callback) {
|
||||
assert.strictEqual(typeof name, 'string');
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert.strictEqual(typeof data, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
const { ownerId, ownerType, active } = data;
|
||||
assert.strictEqual(typeof ownerId, 'string');
|
||||
assert.strictEqual(typeof ownerType, 'string');
|
||||
assert.strictEqual(typeof active, 'boolean');
|
||||
|
||||
database.query('UPDATE mailboxes SET ownerId = ?, ownerType = ?, active = ? WHERE name = ? AND domain = ?', [ ownerId, ownerType, active, name, domain ], function (error, result) {
|
||||
if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error));
|
||||
if (result.affectedRows === 0) return callback(new BoxError(BoxError.NOT_FOUND, 'Mailbox not found'));
|
||||
|
||||
callback(null);
|
||||
});
|
||||
}
|
||||
|
||||
function addList(name, domain, data, callback) {
|
||||
assert.strictEqual(typeof name, 'string');
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert.strictEqual(typeof data, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
const { members, membersOnly, active } = data;
|
||||
assert(Array.isArray(members));
|
||||
assert.strictEqual(typeof membersOnly, 'boolean');
|
||||
assert.strictEqual(typeof active, 'boolean');
|
||||
|
||||
database.query('INSERT INTO mailboxes (name, type, domain, ownerId, ownerType, membersJson, membersOnly, active) VALUES (?, ?, ?, ?, ?, ?, ?, ?)',
|
||||
[ name, exports.TYPE_LIST, domain, 'admin', 'user', JSON.stringify(members), membersOnly, active ], function (error) {
|
||||
if (error && error.code === 'ER_DUP_ENTRY') return callback(new BoxError(BoxError.ALREADY_EXISTS, 'mailbox already exists'));
|
||||
if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error));
|
||||
|
||||
callback(null);
|
||||
});
|
||||
}
|
||||
|
||||
function updateList(name, domain, data, callback) {
|
||||
assert.strictEqual(typeof name, 'string');
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert.strictEqual(typeof data, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
const { members, membersOnly, active } = data;
|
||||
assert(Array.isArray(members));
|
||||
assert.strictEqual(typeof membersOnly, 'boolean');
|
||||
assert.strictEqual(typeof active, 'boolean');
|
||||
|
||||
database.query('UPDATE mailboxes SET membersJson = ?, membersOnly = ?, active = ? WHERE name = ? AND domain = ?',
|
||||
[ JSON.stringify(members), membersOnly, active, name, domain ], function (error, result) {
|
||||
if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error));
|
||||
if (result.affectedRows === 0) return callback(new BoxError(BoxError.NOT_FOUND, 'Mailbox not found'));
|
||||
|
||||
callback(null);
|
||||
});
|
||||
}
|
||||
|
||||
function clear(callback) {
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
database.query('TRUNCATE TABLE mailboxes', [], function (error) {
|
||||
if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error));
|
||||
callback(null);
|
||||
});
|
||||
}
|
||||
|
||||
function del(name, domain, callback) {
|
||||
assert.strictEqual(typeof name, 'string');
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
// deletes aliases as well
|
||||
database.query('DELETE FROM mailboxes WHERE ((name=? AND domain=?) OR (aliasName = ? AND aliasDomain=?))', [ name, domain, name, domain ], function (error, result) {
|
||||
if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error));
|
||||
if (result.affectedRows === 0) return callback(new BoxError(BoxError.NOT_FOUND, 'Mailbox not found'));
|
||||
|
||||
callback(null);
|
||||
});
|
||||
}
|
||||
|
||||
function delByDomain(domain, callback) {
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
database.query('DELETE FROM mailboxes WHERE domain = ?', [ domain ], function (error) {
|
||||
if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error));
|
||||
|
||||
callback(null);
|
||||
});
|
||||
}
|
||||
|
||||
function delByOwnerId(id, callback) {
|
||||
assert.strictEqual(typeof id, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
database.query('DELETE FROM mailboxes WHERE ownerId=?', [ id ], function (error) {
|
||||
if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error));
|
||||
|
||||
callback(null);
|
||||
});
|
||||
}
|
||||
|
||||
function updateName(oldName, oldDomain, newName, newDomain, callback) {
|
||||
assert.strictEqual(typeof oldName, 'string');
|
||||
assert.strictEqual(typeof oldDomain, 'string');
|
||||
assert.strictEqual(typeof newName, 'string');
|
||||
assert.strictEqual(typeof newDomain, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
// skip if no changes
|
||||
if (oldName === newName && oldDomain === newDomain) return callback(null);
|
||||
|
||||
database.query('UPDATE mailboxes SET name=?, domain=? WHERE name=? AND domain = ?', [ newName, newDomain, oldName, oldDomain ], function (error, result) {
|
||||
if (error && error.code === 'ER_DUP_ENTRY') return callback(new BoxError(BoxError.ALREADY_EXISTS, 'mailbox already exists'));
|
||||
if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error));
|
||||
if (result.affectedRows !== 1) return callback(new BoxError(BoxError.NOT_FOUND, 'Mailbox not found'));
|
||||
|
||||
callback(null);
|
||||
});
|
||||
}
|
||||
|
||||
function get(name, domain, callback) {
|
||||
assert.strictEqual(typeof name, 'string');
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
database.query('SELECT ' + MAILBOX_FIELDS + ' FROM mailboxes WHERE name = ? AND domain = ?',
|
||||
[ name, 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, 'Mailbox not found'));
|
||||
|
||||
callback(null, postProcess(results[0]));
|
||||
});
|
||||
}
|
||||
|
||||
function getMailbox(name, domain, callback) {
|
||||
assert.strictEqual(typeof name, 'string');
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
database.query('SELECT ' + MAILBOX_FIELDS + ' FROM mailboxes WHERE name = ? AND type = ? AND domain = ?',
|
||||
[ name, exports.TYPE_MAILBOX, 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, 'Mailbox not found'));
|
||||
|
||||
callback(null, postProcess(results[0]));
|
||||
});
|
||||
}
|
||||
|
||||
function getMailboxCount(domain, callback) {
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
database.query('SELECT COUNT(*) AS total FROM mailboxes WHERE type = ? AND domain = ?', [ exports.TYPE_MAILBOX, domain ], function (error, results) {
|
||||
if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error));
|
||||
|
||||
callback(null, results[0].total);
|
||||
});
|
||||
}
|
||||
|
||||
function listMailboxes(domain, search, page, perPage, callback) {
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert(typeof search === 'string' || search === null);
|
||||
assert.strictEqual(typeof page, 'number');
|
||||
assert.strictEqual(typeof perPage, 'number');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
const escapedSearch = mysql.escape('%' + search + '%'); // this also quotes the string
|
||||
const searchQuery = search ? ` HAVING (name LIKE ${escapedSearch} OR aliasNames LIKE ${escapedSearch} OR aliasDomains LIKE ${escapedSearch})` : ''; // having instead of where because of aggregated columns use
|
||||
|
||||
const query = 'SELECT m1.name AS name, m1.domain AS domain, m1.ownerId AS ownerId, m1.ownerType as ownerType, m1.active as active, JSON_ARRAYAGG(m2.name) AS aliasNames, JSON_ARRAYAGG(m2.domain) AS aliasDomains '
|
||||
+ ` FROM (SELECT * FROM mailboxes WHERE type='${exports.TYPE_MAILBOX}') AS m1`
|
||||
+ ` LEFT JOIN (SELECT * FROM mailboxes WHERE type='${exports.TYPE_ALIAS}') AS m2`
|
||||
+ ' ON m1.name=m2.aliasName AND m1.domain=m2.aliasDomain AND m1.ownerId=m2.ownerId'
|
||||
+ ' WHERE m1.domain = ?'
|
||||
+ ' GROUP BY m1.name, m1.domain, m1.ownerId'
|
||||
+ searchQuery
|
||||
+ ' ORDER BY name LIMIT ?,?';
|
||||
|
||||
database.query(query, [ domain, (page-1)*perPage, perPage ], function (error, results) {
|
||||
if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error));
|
||||
|
||||
results.forEach(postProcess);
|
||||
results.forEach(postProcessAliases);
|
||||
|
||||
callback(null, results);
|
||||
});
|
||||
}
|
||||
|
||||
function listAllMailboxes(page, perPage, callback) {
|
||||
assert.strictEqual(typeof page, 'number');
|
||||
assert.strictEqual(typeof perPage, 'number');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
const query = 'SELECT m1.name AS name, m1.domain AS domain, m1.ownerId AS ownerId, m1.ownerType as ownerType, m1.active as active, JSON_ARRAYAGG(m2.name) AS aliasNames, JSON_ARRAYAGG(m2.domain) AS aliasDomains '
|
||||
+ ` FROM (SELECT * FROM mailboxes WHERE type='${exports.TYPE_MAILBOX}') AS m1`
|
||||
+ ` LEFT JOIN (SELECT * FROM mailboxes WHERE type='${exports.TYPE_ALIAS}') AS m2`
|
||||
+ ' ON m1.name=m2.aliasName AND m1.domain=m2.aliasDomain AND m1.ownerId=m2.ownerId'
|
||||
+ ' GROUP BY m1.name, m1.domain, m1.ownerId'
|
||||
+ ' ORDER BY name LIMIT ?,?';
|
||||
|
||||
database.query(query, [ (page-1)*perPage, perPage ], function (error, results) {
|
||||
if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error));
|
||||
|
||||
results.forEach(postProcess);
|
||||
results.forEach(postProcessAliases);
|
||||
|
||||
callback(null, results);
|
||||
});
|
||||
}
|
||||
|
||||
function getLists(domain, search, page, perPage, callback) {
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert(typeof search === 'string' || search === null);
|
||||
assert.strictEqual(typeof page, 'number');
|
||||
assert.strictEqual(typeof perPage, 'number');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
let query = `SELECT ${MAILBOX_FIELDS} FROM mailboxes WHERE type = ? AND domain = ?`;
|
||||
if (search) query += ' AND (name LIKE ' + mysql.escape('%' + search + '%') + ' OR membersJson LIKE ' + mysql.escape('%' + search + '%') + ')';
|
||||
|
||||
query += 'ORDER BY name LIMIT ?,?';
|
||||
|
||||
database.query(query, [ exports.TYPE_LIST, domain, (page-1)*perPage, perPage ], function (error, results) {
|
||||
if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error));
|
||||
|
||||
results.forEach(function (result) { postProcess(result); });
|
||||
|
||||
callback(null, results);
|
||||
});
|
||||
}
|
||||
|
||||
function getList(name, domain, callback) {
|
||||
assert.strictEqual(typeof name, 'string');
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
database.query('SELECT ' + MAILBOX_FIELDS + ' FROM mailboxes WHERE type = ? AND name = ? AND domain = ?',
|
||||
[ exports.TYPE_LIST, name, 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, 'Mailbox not found'));
|
||||
|
||||
callback(null, postProcess(results[0]));
|
||||
});
|
||||
}
|
||||
|
||||
function getByOwnerId(ownerId, callback) {
|
||||
assert.strictEqual(typeof ownerId, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
database.query('SELECT ' + MAILBOX_FIELDS + ' FROM mailboxes WHERE ownerId = ? ORDER BY name', [ ownerId ], function (error, results) {
|
||||
if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error));
|
||||
if (results.length === 0) return callback(new BoxError(BoxError.NOT_FOUND, 'Mailbox not found'));
|
||||
|
||||
results.forEach(function (result) { postProcess(result); });
|
||||
|
||||
callback(null, results);
|
||||
});
|
||||
}
|
||||
|
||||
function setAliasesForName(name, domain, aliases, callback) {
|
||||
assert.strictEqual(typeof name, 'string');
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert(Array.isArray(aliases));
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
database.query('SELECT ' + MAILBOX_FIELDS + ' FROM mailboxes WHERE name = ? AND domain = ?', [ name, 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, 'Mailbox not found'));
|
||||
|
||||
var queries = [];
|
||||
// clear existing aliases
|
||||
queries.push({ query: 'DELETE FROM mailboxes WHERE aliasName = ? AND aliasDomain = ? AND type = ?', args: [ name, domain, exports.TYPE_ALIAS ] });
|
||||
aliases.forEach(function (alias) {
|
||||
queries.push({ query: 'INSERT INTO mailboxes (name, domain, type, aliasName, aliasDomain, ownerId, ownerType) VALUES (?, ?, ?, ?, ?, ?, ?)',
|
||||
args: [ alias.name, alias.domain, exports.TYPE_ALIAS, name, domain, results[0].ownerId, results[0].ownerType ] });
|
||||
});
|
||||
|
||||
database.transaction(queries, function (error) {
|
||||
if (error && error.code === 'ER_DUP_ENTRY' && error.message.indexOf('mailboxes_name_domain_unique_index') !== -1) {
|
||||
var aliasMatch = error.message.match(new RegExp(`^ER_DUP_ENTRY: Duplicate entry '(.*)-${domain}' for key 'mailboxes_name_domain_unique_index'$`));
|
||||
if (!aliasMatch) return callback(new BoxError(BoxError.ALREADY_EXISTS, error));
|
||||
|
||||
return callback(new BoxError(BoxError.ALREADY_EXISTS, `Mailbox, mailinglist or alias for ${aliasMatch[1]} already exists`));
|
||||
}
|
||||
|
||||
if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error));
|
||||
|
||||
callback(null);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function getAliasesForName(name, domain, callback) {
|
||||
assert.strictEqual(typeof name, 'string');
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
database.query('SELECT name, domain FROM mailboxes WHERE type = ? AND aliasName = ? AND aliasDomain = ? ORDER BY name',
|
||||
[ exports.TYPE_ALIAS, name, domain ], function (error, results) {
|
||||
if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error));
|
||||
|
||||
callback(null, results);
|
||||
});
|
||||
}
|
||||
|
||||
function getAlias(name, domain, callback) {
|
||||
assert.strictEqual(typeof name, 'string');
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
database.query('SELECT ' + MAILBOX_FIELDS + ' FROM mailboxes WHERE name = ? AND type = ? AND domain = ?',
|
||||
[ name, exports.TYPE_ALIAS, 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, 'Mailbox not found'));
|
||||
|
||||
results.forEach(function (result) { postProcess(result); });
|
||||
|
||||
callback(null, results[0]);
|
||||
});
|
||||
}
|
||||
+59
-70
@@ -17,7 +17,7 @@ exports = module.exports = {
|
||||
getMailbox,
|
||||
addMailbox,
|
||||
updateMailbox,
|
||||
removeMailbox,
|
||||
delMailbox,
|
||||
|
||||
getAliases,
|
||||
setAliases,
|
||||
@@ -26,7 +26,7 @@ exports = module.exports = {
|
||||
getList,
|
||||
addList,
|
||||
updateList,
|
||||
removeList,
|
||||
delList,
|
||||
|
||||
getMailboxCount
|
||||
};
|
||||
@@ -133,46 +133,44 @@ function sendTestMail(req, res, next) {
|
||||
});
|
||||
}
|
||||
|
||||
function listMailboxes(req, res, next) {
|
||||
async function listMailboxes(req, res, next) {
|
||||
assert.strictEqual(typeof req.params.domain, 'string');
|
||||
|
||||
var page = typeof req.query.page !== 'undefined' ? parseInt(req.query.page) : 1;
|
||||
const page = typeof req.query.page !== 'undefined' ? parseInt(req.query.page) : 1;
|
||||
if (!page || page < 0) return next(new HttpError(400, 'page query param has to be a positive number'));
|
||||
|
||||
var perPage = typeof req.query.per_page !== 'undefined'? parseInt(req.query.per_page) : 25;
|
||||
const perPage = typeof req.query.per_page !== 'undefined'? parseInt(req.query.per_page) : 25;
|
||||
if (!perPage || perPage < 0) return next(new HttpError(400, 'per_page query param has to be a positive number'));
|
||||
|
||||
if (req.query.search && typeof req.query.search !== 'string') return next(new HttpError(400, 'search must be a string'));
|
||||
|
||||
mail.listMailboxes(req.params.domain, req.query.search || null, page, perPage, function (error, result) {
|
||||
if (error) return next(BoxError.toHttpError(error));
|
||||
const [error, mailboxes] = await safe(mail.listMailboxes(req.params.domain, req.query.search || null, page, perPage));
|
||||
if (error) return next(BoxError.toHttpError(error));
|
||||
|
||||
next(new HttpSuccess(200, { mailboxes: result }));
|
||||
});
|
||||
next(new HttpSuccess(200, { mailboxes }));
|
||||
}
|
||||
|
||||
function getMailboxCount(req, res, next) {
|
||||
async function getMailboxCount(req, res, next) {
|
||||
assert.strictEqual(typeof req.params.domain, 'string');
|
||||
|
||||
mail.getMailboxCount(req.params.domain, function (error, count) {
|
||||
if (error) return next(BoxError.toHttpError(error));
|
||||
const [error, count] = await safe(mail.getMailboxCount(req.params.domain));
|
||||
if (error) return next(BoxError.toHttpError(error));
|
||||
|
||||
next(new HttpSuccess(200, { count }));
|
||||
});
|
||||
next(new HttpSuccess(200, { count }));
|
||||
}
|
||||
|
||||
function getMailbox(req, res, next) {
|
||||
async function getMailbox(req, res, next) {
|
||||
assert.strictEqual(typeof req.params.domain, 'string');
|
||||
assert.strictEqual(typeof req.params.name, 'string');
|
||||
|
||||
mail.getMailbox(req.params.name, req.params.domain, function (error, result) {
|
||||
if (error) return next(BoxError.toHttpError(error));
|
||||
const [error, result] = await safe(mail.getMailbox(req.params.name, req.params.domain));
|
||||
if (error) return next(BoxError.toHttpError(error));
|
||||
if (!result) return next(new HttpError(404, 'Mailbox not found'));
|
||||
|
||||
next(new HttpSuccess(200, { mailbox: result }));
|
||||
});
|
||||
next(new HttpSuccess(200, { mailbox: result }));
|
||||
}
|
||||
|
||||
function addMailbox(req, res, next) {
|
||||
async function addMailbox(req, res, next) {
|
||||
assert.strictEqual(typeof req.params.domain, 'string');
|
||||
|
||||
if (typeof req.body.name !== 'string') return next(new HttpError(400, 'name must be a string'));
|
||||
@@ -180,14 +178,13 @@ function addMailbox(req, res, next) {
|
||||
if (typeof req.body.ownerType !== 'string') return next(new HttpError(400, 'ownerType must be a string'));
|
||||
if (typeof req.body.active !== 'boolean') return next(new HttpError(400, 'active must be a boolean'));
|
||||
|
||||
mail.addMailbox(req.body.name, req.params.domain, req.body, auditSource.fromRequest(req), function (error) {
|
||||
if (error) return next(BoxError.toHttpError(error));
|
||||
const [error] = await safe(mail.addMailbox(req.body.name, req.params.domain, req.body, auditSource.fromRequest(req)));
|
||||
if (error) return next(BoxError.toHttpError(error));
|
||||
|
||||
next(new HttpSuccess(201, {}));
|
||||
});
|
||||
next(new HttpSuccess(201, {}));
|
||||
}
|
||||
|
||||
function updateMailbox(req, res, next) {
|
||||
async function updateMailbox(req, res, next) {
|
||||
assert.strictEqual(typeof req.params.domain, 'string');
|
||||
assert.strictEqual(typeof req.params.name, 'string');
|
||||
|
||||
@@ -195,38 +192,35 @@ function updateMailbox(req, res, next) {
|
||||
if (typeof req.body.ownerType !== 'string') return next(new HttpError(400, 'ownerType must be a string'));
|
||||
if (typeof req.body.active !== 'boolean') return next(new HttpError(400, 'active must be a boolean'));
|
||||
|
||||
mail.updateMailbox(req.params.name, req.params.domain, req.body, auditSource.fromRequest(req), function (error) {
|
||||
if (error) return next(BoxError.toHttpError(error));
|
||||
const [error] = await safe(mail.updateMailbox(req.params.name, req.params.domain, req.body, auditSource.fromRequest(req)));
|
||||
if (error) return next(BoxError.toHttpError(error));
|
||||
|
||||
next(new HttpSuccess(204));
|
||||
});
|
||||
next(new HttpSuccess(204));
|
||||
}
|
||||
|
||||
function removeMailbox(req, res, next) {
|
||||
async function delMailbox(req, res, next) {
|
||||
assert.strictEqual(typeof req.params.domain, 'string');
|
||||
assert.strictEqual(typeof req.params.name, 'string');
|
||||
|
||||
if (typeof req.body.deleteMails !== 'boolean') return next(new HttpError(400, 'deleteMails must be a boolean'));
|
||||
|
||||
mail.removeMailbox(req.params.name, req.params.domain, req.body, auditSource.fromRequest(req), function (error) {
|
||||
if (error) return next(BoxError.toHttpError(error));
|
||||
const [error] = await safe(mail.delMailbox(req.params.name, req.params.domain, req.body, auditSource.fromRequest(req)));
|
||||
if (error) return next(BoxError.toHttpError(error));
|
||||
|
||||
next(new HttpSuccess(201, {}));
|
||||
});
|
||||
next(new HttpSuccess(201, {}));
|
||||
}
|
||||
|
||||
function getAliases(req, res, next) {
|
||||
async function getAliases(req, res, next) {
|
||||
assert.strictEqual(typeof req.params.domain, 'string');
|
||||
assert.strictEqual(typeof req.params.name, 'string');
|
||||
|
||||
mail.getAliases(req.params.name, req.params.domain, function (error, result) {
|
||||
if (error) return next(BoxError.toHttpError(error));
|
||||
const [error, aliases] = await safe(mail.getAliases(req.params.name, req.params.domain));
|
||||
if (error) return next(BoxError.toHttpError(error));
|
||||
|
||||
next(new HttpSuccess(200, { aliases: result }));
|
||||
});
|
||||
next(new HttpSuccess(200, { aliases }));
|
||||
}
|
||||
|
||||
function setAliases(req, res, next) {
|
||||
async function setAliases(req, res, next) {
|
||||
assert.strictEqual(typeof req.params.domain, 'string');
|
||||
assert.strictEqual(typeof req.params.name, 'string');
|
||||
assert.strictEqual(typeof req.body, 'object');
|
||||
@@ -239,11 +233,10 @@ function setAliases(req, res, next) {
|
||||
if (typeof alias.domain !== 'string') return next(new HttpError(400, 'domain must be a string'));
|
||||
}
|
||||
|
||||
mail.setAliases(req.params.name, req.params.domain, req.body.aliases, function (error) {
|
||||
if (error) return next(BoxError.toHttpError(error));
|
||||
const [error] = await safe(mail.setAliases(req.params.name, req.params.domain, req.body.aliases));
|
||||
if (error) return next(BoxError.toHttpError(error));
|
||||
|
||||
next(new HttpSuccess(202));
|
||||
});
|
||||
next(new HttpSuccess(202));
|
||||
}
|
||||
|
||||
async function setBanner(req, res, next) {
|
||||
@@ -259,7 +252,7 @@ async function setBanner(req, res, next) {
|
||||
next(new HttpSuccess(202));
|
||||
}
|
||||
|
||||
function getLists(req, res, next) {
|
||||
async function getLists(req, res, next) {
|
||||
assert.strictEqual(typeof req.params.domain, 'string');
|
||||
|
||||
const page = typeof req.query.page !== 'undefined' ? parseInt(req.query.page) : 1;
|
||||
@@ -270,25 +263,24 @@ function getLists(req, res, next) {
|
||||
|
||||
if (req.query.search && typeof req.query.search !== 'string') return next(new HttpError(400, 'search must be a string'));
|
||||
|
||||
mail.getLists(req.params.domain, req.query.search || null, page, perPage, function (error, result) {
|
||||
if (error) return next(BoxError.toHttpError(error));
|
||||
const [error, lists] = await safe(mail.getLists(req.params.domain, req.query.search || null, page, perPage));
|
||||
if (error) return next(BoxError.toHttpError(error));
|
||||
|
||||
next(new HttpSuccess(200, { lists: result }));
|
||||
});
|
||||
next(new HttpSuccess(200, { lists }));
|
||||
}
|
||||
|
||||
function getList(req, res, next) {
|
||||
async function getList(req, res, next) {
|
||||
assert.strictEqual(typeof req.params.domain, 'string');
|
||||
assert.strictEqual(typeof req.params.name, 'string');
|
||||
|
||||
mail.getList(req.params.name, req.params.domain, function (error, result) {
|
||||
if (error) return next(BoxError.toHttpError(error));
|
||||
const [error, result] = await safe(mail.getList(req.params.name, req.params.domain));
|
||||
if (error) return next(BoxError.toHttpError(error));
|
||||
if (!result) return next(new HttpError(404, 'List not found'));
|
||||
|
||||
next(new HttpSuccess(200, { list: result }));
|
||||
});
|
||||
next(new HttpSuccess(200, { list: result }));
|
||||
}
|
||||
|
||||
function addList(req, res, next) {
|
||||
async function addList(req, res, next) {
|
||||
assert.strictEqual(typeof req.params.domain, 'string');
|
||||
assert.strictEqual(typeof req.body, 'object');
|
||||
|
||||
@@ -296,20 +288,19 @@ function addList(req, res, next) {
|
||||
if (!Array.isArray(req.body.members)) return next(new HttpError(400, 'members must be a string'));
|
||||
if (req.body.members.length === 0) return next(new HttpError(400, 'list must have atleast one member'));
|
||||
|
||||
for (var i = 0; i < req.body.members.length; i++) {
|
||||
for (let i = 0; i < req.body.members.length; i++) {
|
||||
if (typeof req.body.members[i] !== 'string') return next(new HttpError(400, 'member must be a string'));
|
||||
}
|
||||
if (typeof req.body.membersOnly !== 'boolean') return next(new HttpError(400, 'membersOnly must be a boolean'));
|
||||
if (typeof req.body.active !== 'boolean') return next(new HttpError(400, 'active must be a boolean'));
|
||||
|
||||
mail.addList(req.body.name, req.params.domain, req.body, auditSource.fromRequest(req), function (error) {
|
||||
if (error) return next(BoxError.toHttpError(error));
|
||||
const [error] = await safe(mail.addList(req.body.name, req.params.domain, req.body, auditSource.fromRequest(req)));
|
||||
if (error) return next(BoxError.toHttpError(error));
|
||||
|
||||
next(new HttpSuccess(201, {}));
|
||||
});
|
||||
next(new HttpSuccess(201, {}));
|
||||
}
|
||||
|
||||
function updateList(req, res, next) {
|
||||
async function updateList(req, res, next) {
|
||||
assert.strictEqual(typeof req.params.domain, 'string');
|
||||
assert.strictEqual(typeof req.params.name, 'string');
|
||||
|
||||
@@ -322,20 +313,18 @@ function updateList(req, res, next) {
|
||||
if (typeof req.body.membersOnly !== 'boolean') return next(new HttpError(400, 'membersOnly must be a boolean'));
|
||||
if (typeof req.body.active !== 'boolean') return next(new HttpError(400, 'active must be a boolean'));
|
||||
|
||||
mail.updateList(req.params.name, req.params.domain, req.body, auditSource.fromRequest(req), function (error) {
|
||||
if (error) return next(BoxError.toHttpError(error));
|
||||
const [error] = await safe(mail.updateList(req.params.name, req.params.domain, req.body, auditSource.fromRequest(req)));
|
||||
if (error) return next(BoxError.toHttpError(error));
|
||||
|
||||
next(new HttpSuccess(204));
|
||||
});
|
||||
next(new HttpSuccess(204));
|
||||
}
|
||||
|
||||
function removeList(req, res, next) {
|
||||
async function delList(req, res, next) {
|
||||
assert.strictEqual(typeof req.params.domain, 'string');
|
||||
assert.strictEqual(typeof req.params.name, 'string');
|
||||
|
||||
mail.removeList(req.params.name, req.params.domain, auditSource.fromRequest(req), function (error) {
|
||||
if (error) return next(BoxError.toHttpError(error));
|
||||
const [error] = await safe(mail.delList(req.params.name, req.params.domain, auditSource.fromRequest(req)));
|
||||
if (error) return next(BoxError.toHttpError(error));
|
||||
|
||||
next(new HttpSuccess(204));
|
||||
});
|
||||
next(new HttpSuccess(204));
|
||||
}
|
||||
|
||||
+11
-12
@@ -4,8 +4,8 @@ exports = module.exports = {
|
||||
proxy,
|
||||
restart,
|
||||
|
||||
getLocation,
|
||||
setLocation
|
||||
setLocation,
|
||||
getLocation
|
||||
};
|
||||
|
||||
const assert = require('assert'),
|
||||
@@ -16,6 +16,7 @@ const assert = require('assert'),
|
||||
HttpSuccess = require('connect-lastmile').HttpSuccess,
|
||||
mail = require('../mail.js'),
|
||||
middleware = require('../middleware/index.js'),
|
||||
safe = require('safetydance'),
|
||||
services = require('../services.js'),
|
||||
url = require('url');
|
||||
|
||||
@@ -55,23 +56,21 @@ function proxy(req, res, next) {
|
||||
});
|
||||
}
|
||||
|
||||
function getLocation(req, res, next) {
|
||||
mail.getLocation(function (error, result) {
|
||||
if (error) return next(BoxError.toHttpError(error));
|
||||
async function getLocation(req, res, next) {
|
||||
const [error, result] = await safe(mail.getLocation());
|
||||
if (error) return next(BoxError.toHttpError(error));
|
||||
|
||||
next(new HttpSuccess(200, { domain: result.domain, subdomain: result.subdomain }));
|
||||
});
|
||||
next(new HttpSuccess(200, { domain: result.domain, subdomain: result.subdomain }));
|
||||
}
|
||||
|
||||
function setLocation(req, res, next) {
|
||||
async function setLocation(req, res, next) {
|
||||
assert.strictEqual(typeof req.body, 'object');
|
||||
|
||||
if (typeof req.body.domain !== 'string') return next(new HttpError(400, 'domain must be a string'));
|
||||
if (typeof req.body.subdomain !== 'string') return next(new HttpError(400, 'subdomain must be a string'));
|
||||
|
||||
mail.setLocation(req.body.subdomain, req.body.domain, auditSource.fromRequest(req), function (error, taskId) {
|
||||
if (error) return next(BoxError.toHttpError(error));
|
||||
const [error, taskId] = await safe(mail.setLocation(req.body.subdomain, req.body.domain, auditSource.fromRequest(req)));
|
||||
if (error) return next(BoxError.toHttpError(error));
|
||||
|
||||
next(new HttpSuccess(202, { taskId }));
|
||||
});
|
||||
next(new HttpSuccess(202, { taskId }));
|
||||
}
|
||||
@@ -496,11 +496,8 @@ describe('Mail API', function () {
|
||||
describe('aliases', function () {
|
||||
const MAILBOX_NAME = 'support';
|
||||
|
||||
after(function (done) {
|
||||
mail._removeMailboxes(dashboardDomain, function (error) {
|
||||
if (error) return done(error);
|
||||
done();
|
||||
});
|
||||
after(async function () {
|
||||
await mail._delByDomain(dashboardDomain);
|
||||
});
|
||||
|
||||
it('add the mailbox', async function () {
|
||||
@@ -565,12 +562,8 @@ describe('Mail API', function () {
|
||||
describe('mailinglists', function () {
|
||||
const LIST_NAME = 'people';
|
||||
|
||||
after(function (done) {
|
||||
mail._removeMailboxes(dashboardDomain, function (error) {
|
||||
if (error) return done(error);
|
||||
|
||||
done();
|
||||
});
|
||||
after(async function () {
|
||||
await mail._delByDomain(dashboardDomain);
|
||||
});
|
||||
|
||||
it('add fails without groupId', async function () {
|
||||
|
||||
+2
-2
@@ -281,14 +281,14 @@ function initializeExpressSync() {
|
||||
router.get ('/api/v1/mail/:domain/mailboxes/:name', token, authorizeAdmin, routes.mail.getMailbox);
|
||||
router.post('/api/v1/mail/:domain/mailboxes', json, token, authorizeAdmin, routes.mail.addMailbox);
|
||||
router.post('/api/v1/mail/:domain/mailboxes/:name', json, token, authorizeAdmin, routes.mail.updateMailbox);
|
||||
router.del ('/api/v1/mail/:domain/mailboxes/:name', json, token, authorizeAdmin, routes.mail.removeMailbox);
|
||||
router.del ('/api/v1/mail/:domain/mailboxes/:name', json, token, authorizeAdmin, routes.mail.delMailbox);
|
||||
router.get ('/api/v1/mail/:domain/mailboxes/:name/aliases', token, authorizeAdmin, routes.mail.getAliases);
|
||||
router.put ('/api/v1/mail/:domain/mailboxes/:name/aliases', json, token, authorizeAdmin, routes.mail.setAliases);
|
||||
router.get ('/api/v1/mail/:domain/lists', token, authorizeAdmin, routes.mail.getLists);
|
||||
router.post('/api/v1/mail/:domain/lists', json, token, authorizeAdmin, routes.mail.addList);
|
||||
router.get ('/api/v1/mail/:domain/lists/:name', token, authorizeAdmin, routes.mail.getList);
|
||||
router.post('/api/v1/mail/:domain/lists/:name', json, token, authorizeAdmin, routes.mail.updateList);
|
||||
router.del ('/api/v1/mail/:domain/lists/:name', token, authorizeAdmin, routes.mail.removeList);
|
||||
router.del ('/api/v1/mail/:domain/lists/:name', token, authorizeAdmin, routes.mail.delList);
|
||||
|
||||
// support routes
|
||||
router.post('/api/v1/support/ticket', json, token, authorizeAdmin, routes.support.canCreateTicket, routes.support.createTicket);
|
||||
|
||||
@@ -10,8 +10,6 @@ const appdb = require('../appdb.js'),
|
||||
domains = require('../domains.js'),
|
||||
expect = require('expect.js'),
|
||||
fs = require('fs'),
|
||||
mail = require('../mail.js'),
|
||||
mailboxdb = require('../mailboxdb.js'),
|
||||
mailer = require('../mailer.js'),
|
||||
nock = require('nock'),
|
||||
path = require('path'),
|
||||
@@ -133,11 +131,6 @@ exports = module.exports = {
|
||||
manifest,
|
||||
user,
|
||||
appstoreToken: 'atoken',
|
||||
|
||||
mailboxName: 'support',
|
||||
mailbox: `support@${domain.domain}`,
|
||||
mailAliasName: 'alsosupport',
|
||||
mailAlias: `alsosupport@${domain.domain}`
|
||||
};
|
||||
|
||||
function createTree(root, obj) {
|
||||
@@ -197,8 +190,6 @@ function setup(done) {
|
||||
const result = await users.add(user.email, user, auditSource);
|
||||
user.id = result;
|
||||
},
|
||||
(done) => mailboxdb.addMailbox(exports.mailboxName, domain.domain, { ownerId: user.id, ownerType: mail.OWNERTYPE_USER, active: true }, done),
|
||||
(done) => mailboxdb.setAliasesForName(exports.mailboxName, domain.domain, [ { name: exports.mailAliasName, domain: domain.domain} ], done),
|
||||
|
||||
tasks.stopAllTasks,
|
||||
], done);
|
||||
|
||||
@@ -12,7 +12,6 @@ const appdb = require('../appdb.js'),
|
||||
database = require('../database'),
|
||||
domains = require('../domains.js'),
|
||||
expect = require('expect.js'),
|
||||
mailboxdb = require('../mailboxdb.js'),
|
||||
reverseProxy = require('../reverseproxy.js'),
|
||||
settingsdb = require('../settingsdb.js'),
|
||||
_ = require('underscore');
|
||||
@@ -472,169 +471,4 @@ describe('database', function () {
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('mailboxes', function () {
|
||||
before(function (done) {
|
||||
async.series([
|
||||
domains.add.bind(null, DOMAIN_0.domain, DOMAIN_0),
|
||||
], done);
|
||||
});
|
||||
|
||||
after(function (done) {
|
||||
database._clear(done);
|
||||
});
|
||||
|
||||
it('add user mailbox succeeds', function (done) {
|
||||
mailboxdb.addMailbox('girish', DOMAIN_0.domain, { ownerId: 'uid-0', ownerType: 'user', active: true }, function (error) {
|
||||
expect(error).to.be(null);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('cannot add dup entry', function (done) {
|
||||
mailboxdb.addMailbox('girish', DOMAIN_0.domain, { ownerId: 'uid-1', ownerType: 'group', active: true }, function (error) {
|
||||
expect(error.reason).to.be(BoxError.ALREADY_EXISTS);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('add app mailbox succeeds', function (done) {
|
||||
mailboxdb.addMailbox('support', DOMAIN_0.domain, { ownerId: 'osticket', ownerType: 'user', active: true}, function (error) {
|
||||
expect(error).to.be(null);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('get succeeds', function (done) {
|
||||
mailboxdb.getMailbox('support', DOMAIN_0.domain, function (error, mailbox) {
|
||||
expect(error).to.be(null);
|
||||
expect(mailbox.name).to.equal('support');
|
||||
expect(mailbox.ownerId).to.equal('osticket');
|
||||
expect(mailbox.domain).to.equal(DOMAIN_0.domain);
|
||||
expect(mailbox.creationTime).to.be.a(Date);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('list mailboxes succeeds', function (done) {
|
||||
mailboxdb.listMailboxes(DOMAIN_0.domain, null /* search */, 1, 10, function (error, mailboxes) {
|
||||
expect(error).to.be(null);
|
||||
expect(mailboxes.length).to.be(2);
|
||||
expect(mailboxes[0].name).to.be('girish');
|
||||
expect(mailboxes[1].name).to.be('support');
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('can set alias', function (done) {
|
||||
mailboxdb.setAliasesForName('support', DOMAIN_0.domain, [ { name: 'support2', domain: DOMAIN_0.domain }, { name: 'help', domain: DOMAIN_0.domain } ], function (error) {
|
||||
expect(error).to.be(null);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('list all mailboxes succeeds', function (done) {
|
||||
mailboxdb.listAllMailboxes(1, 10, function (error, mailboxes) {
|
||||
expect(error).to.be(null);
|
||||
expect(mailboxes.length).to.be(2);
|
||||
expect(mailboxes[0].name).to.be('girish');
|
||||
expect(mailboxes[1].name).to.be('support');
|
||||
expect(mailboxes[1].domain).to.be(DOMAIN_0.domain);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('can get aliases of name', function (done) {
|
||||
mailboxdb.getAliasesForName('support', DOMAIN_0.domain, function (error, results) {
|
||||
expect(error).to.be(null);
|
||||
expect(results.length).to.be(2);
|
||||
expect(results[0].name).to.be('help');
|
||||
expect(results[0].domain).to.be(DOMAIN_0.domain);
|
||||
expect(results[1].name).to.be('support2');
|
||||
expect(results[1].domain).to.be(DOMAIN_0.domain);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('can get alias', function (done) {
|
||||
mailboxdb.getAlias('support2', DOMAIN_0.domain, function (error, result) {
|
||||
expect(error).to.be(null);
|
||||
expect(result.name).to.be('support2');
|
||||
expect(result.aliasName).to.be('support');
|
||||
expect(result.aliasDomain).to.be(DOMAIN_0.domain);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('can get by owner id', function (done) {
|
||||
mailboxdb.getByOwnerId('osticket', function (error, results) {
|
||||
expect(error).to.be(null);
|
||||
expect(results.length).to.be(3);
|
||||
expect(results[0].name).to.be('help');
|
||||
expect(results[1].name).to.be('support');
|
||||
expect(results[2].name).to.be('support2');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('cannot get non-existing group', function (done) {
|
||||
mailboxdb.getList('random', DOMAIN_0.domain, function (error) {
|
||||
expect(error.reason).to.be(BoxError.NOT_FOUND);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('can change name', function (done) {
|
||||
mailboxdb.updateName('support', DOMAIN_0.domain, 'support3', DOMAIN_0.domain, function (error) {
|
||||
expect(error).to.be(null);
|
||||
|
||||
mailboxdb.updateName('support3', DOMAIN_0.domain, 'support', DOMAIN_0.domain, done);
|
||||
});
|
||||
});
|
||||
|
||||
it('cannot change name to existing one', function (done) {
|
||||
mailboxdb.updateName('support', DOMAIN_0.domain, 'support2', DOMAIN_0.domain, function (error) {
|
||||
expect(error).to.be.ok();
|
||||
expect(error.reason).to.eql(BoxError.ALREADY_EXISTS);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('unset aliases', function (done) {
|
||||
mailboxdb.setAliasesForName('support', DOMAIN_0.domain, [], function (error) {
|
||||
expect(error).to.be(null);
|
||||
|
||||
mailboxdb.getAliasesForName('support', DOMAIN_0.domain, function (error, results) {
|
||||
expect(error).to.be(null);
|
||||
expect(results.length).to.be(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('del succeeds', function (done) {
|
||||
mailboxdb.del('girish', DOMAIN_0.domain, function (error) {
|
||||
expect(error).to.be(null);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('del by ownerId succeeds', function (done) {
|
||||
mailboxdb.delByOwnerId('osticket', function (error) {
|
||||
expect(error).to.be(null);
|
||||
|
||||
mailboxdb.getByOwnerId('osticket', function (error) {
|
||||
expect(error).to.be.ok();
|
||||
expect(error.reason).to.be(BoxError.NOT_FOUND);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
+17
-19
@@ -15,7 +15,6 @@ const appdb = require('../appdb.js'),
|
||||
ldap = require('ldapjs'),
|
||||
ldapServer = require('../ldap.js'),
|
||||
mail = require('../mail.js'),
|
||||
mailboxdb = require('../mailboxdb.js'),
|
||||
safe = require('safetydance'),
|
||||
util = require('util');
|
||||
|
||||
@@ -61,12 +60,19 @@ async function ldapSearch(dn, opts) {
|
||||
}
|
||||
|
||||
describe('Ldap', function () {
|
||||
const { setup, cleanup, admin, user, app, domain, mailbox, mailAlias, mailboxName } = common;
|
||||
const { setup, cleanup, admin, user, app, domain, auditSource } = common;
|
||||
let group;
|
||||
|
||||
const mailboxName = 'support';
|
||||
const mailbox = `support@${domain.domain}`;
|
||||
const mailAliasName = 'alsosupport';
|
||||
const mailAlias = `alsosupport@${domain.domain}`;
|
||||
|
||||
before(function (done) {
|
||||
async.series([
|
||||
setup,
|
||||
async () => await mail.addMailbox(mailboxName, domain.domain, { ownerId: user.id, ownerType: mail.OWNERTYPE_USER, active: true }, auditSource),
|
||||
async () => await mail.setAliases(mailboxName, domain.domain, [ { name: mailAliasName, domain: domain.domain} ], auditSource),
|
||||
ldapServer.start.bind(null),
|
||||
async () => {
|
||||
group = await groups.add({ name: 'ldap-test' });
|
||||
@@ -285,13 +291,11 @@ describe('Ldap', function () {
|
||||
});
|
||||
|
||||
it('cannot get inactive mailbox', async function () {
|
||||
const updateMailbox = util.promisify(mailboxdb.updateMailbox);
|
||||
|
||||
await updateMailbox(mailboxName, domain.domain, { ownerId: user.id, ownerType: mail.OWNERTYPE_USER, active: false });
|
||||
await mail.updateMailbox(mailboxName, domain.domain, { ownerId: user.id, ownerType: mail.OWNERTYPE_USER, active: false }, auditSource);
|
||||
const [error] = await safe(ldapSearch(`cn=${mailbox},ou=mailboxes,dc=cloudron`, 'objectclass=mailbox'));
|
||||
expect(error).to.be.a(ldap.NoSuchObjectError);
|
||||
|
||||
await updateMailbox(mailboxName, domain.domain, { ownerId: user.id, ownerType: mail.OWNERTYPE_USER, active: true });
|
||||
await mail.updateMailbox(mailboxName, domain.domain, { ownerId: user.id, ownerType: mail.OWNERTYPE_USER, active: true }, auditSource);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -317,8 +321,8 @@ describe('Ldap', function () {
|
||||
describe('search mailing list', function () {
|
||||
const LIST_NAME = 'devs', LIST = `devs@${domain.domain}`;
|
||||
|
||||
before(function (done) {
|
||||
mailboxdb.addList(LIST_NAME, domain.domain, { members: [ mailbox , 'outsider@external.com' ], membersOnly: false, active: true }, done);
|
||||
before(async function () {
|
||||
await mail.addList(LIST_NAME, domain.domain, { members: [ mailbox , 'outsider@external.com' ], membersOnly: false, active: true }, auditSource);
|
||||
});
|
||||
|
||||
it('get specific list', async function () {
|
||||
@@ -334,9 +338,7 @@ describe('Ldap', function () {
|
||||
});
|
||||
|
||||
it('inactive list', async function () {
|
||||
const updateList = util.promisify(mailboxdb.updateList);
|
||||
|
||||
await updateList(LIST_NAME, domain.domain, { members: [ mailbox , 'outsider@external.com' ], membersOnly: false, active: false });
|
||||
await mail.updateList(LIST_NAME, domain.domain, { members: [ mailbox , 'outsider@external.com' ], membersOnly: false, active: false }, auditSource);
|
||||
const [error] = await safe(ldapSearch('cn=devs@example.com,ou=mailinglists,dc=cloudron', 'objectclass=mailGroup'));
|
||||
expect(error).to.be.a(ldap.NoSuchObjectError);
|
||||
});
|
||||
@@ -390,13 +392,11 @@ describe('Ldap', function () {
|
||||
});
|
||||
|
||||
it('does not allow for inactive mailbox', async function () {
|
||||
const updateMailbox = util.promisify(mailboxdb.updateMailbox);
|
||||
|
||||
await mail._updateDomain(domain.domain, { enabled: true });
|
||||
await updateMailbox(mailboxName, domain.domain, { ownerId: user.id, ownerType: mail.OWNERTYPE_USER, active: false });
|
||||
await mail.updateMailbox(mailboxName, domain.domain, { ownerId: user.id, ownerType: mail.OWNERTYPE_USER, active: false }, auditSource);
|
||||
const [error] = await safe(ldapBind(`cn=${mailbox},ou=sendmail,dc=cloudron`, 'badpassword'));
|
||||
expect(error).to.be.a(ldap.NoSuchObjectError);
|
||||
await updateMailbox(mailboxName, domain.domain, { ownerId: user.id, ownerType: mail.OWNERTYPE_USER, active: true });
|
||||
await mail.updateMailbox(mailboxName, domain.domain, { ownerId: user.id, ownerType: mail.OWNERTYPE_USER, active: true }, auditSource);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -444,14 +444,12 @@ describe('Ldap', function () {
|
||||
});
|
||||
|
||||
it('does not allow for inactive mailbox', async function () {
|
||||
const updateMailbox = util.promisify(mailboxdb.updateMailbox);
|
||||
|
||||
await mail._updateDomain(domain.domain, { enabled: true });
|
||||
await updateMailbox(mailboxName, domain.domain, { ownerId: user.id, ownerType: mail.OWNERTYPE_USER, active: false });
|
||||
await mail.updateMailbox(mailboxName, domain.domain, { ownerId: user.id, ownerType: mail.OWNERTYPE_USER, active: false }, auditSource);
|
||||
const [error] = await safe(ldapBind(`cn=${mailbox},ou=recvmail,dc=cloudron`, 'badpassword'));
|
||||
expect(error).to.be.a(ldap.NoSuchObjectError);
|
||||
await mail._updateDomain(domain.domain, { enabled: false });
|
||||
await updateMailbox(mailboxName, domain.domain, { ownerId: user.id, ownerType: mail.OWNERTYPE_USER, active: true });
|
||||
await mail.updateMailbox(mailboxName, domain.domain, { ownerId: user.id, ownerType: mail.OWNERTYPE_USER, active: true }, auditSource);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
+109
-2
@@ -6,8 +6,10 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('./common.js'),
|
||||
BoxError = require('../boxerror.js'),
|
||||
expect = require('expect.js'),
|
||||
mail = require('../mail.js');
|
||||
mail = require('../mail.js'),
|
||||
safe = require('safetydance');
|
||||
|
||||
describe('Mail', function () {
|
||||
const { setup, cleanup, domain, auditSource } = common;
|
||||
@@ -15,7 +17,7 @@ describe('Mail', function () {
|
||||
before(setup);
|
||||
after(cleanup);
|
||||
|
||||
describe('values', function () {
|
||||
describe('settings', function () {
|
||||
it('can get default', async function () {
|
||||
const mailConfig = await mail.getDomain(domain.domain);
|
||||
expect(mailConfig.enabled).to.be(false);
|
||||
@@ -70,4 +72,109 @@ describe('Mail', function () {
|
||||
expect(mailConfig.enabled).to.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('mailboxes', function () {
|
||||
it('add user mailbox succeeds', async function () {
|
||||
await mail.addMailbox('girish', domain.domain, { ownerId: 'uid-0', ownerType: mail.OWNERTYPE_USER, active: true }, auditSource);
|
||||
});
|
||||
|
||||
it('cannot add dup entry', async function () {
|
||||
const [error] = await safe(mail.addMailbox('girish', domain.domain, { ownerId: 'uid-1', ownerType: mail.OWNERTYPE_GROUP, active: true }, auditSource));
|
||||
expect(error.reason).to.be(BoxError.ALREADY_EXISTS);
|
||||
});
|
||||
|
||||
it('add app mailbox succeeds', async function () {
|
||||
await mail.addMailbox('support', domain.domain, { ownerId: 'osticket', ownerType: 'user', active: true}, auditSource);
|
||||
});
|
||||
|
||||
it('get succeeds', async function () {
|
||||
const mailbox = await mail.getMailbox('support', domain.domain);
|
||||
expect(mailbox.name).to.equal('support');
|
||||
expect(mailbox.ownerId).to.equal('osticket');
|
||||
expect(mailbox.domain).to.equal(domain.domain);
|
||||
expect(mailbox.creationTime).to.be.a(Date);
|
||||
});
|
||||
|
||||
it('get non-existent mailbox', async function () {
|
||||
const mailbox = await mail.getMailbox('random', domain.domain);
|
||||
expect(mailbox).to.be(null);
|
||||
});
|
||||
|
||||
it('list mailboxes succeeds', async function () {
|
||||
const mailboxes = await mail.listMailboxes(domain.domain, null /* search */, 1, 10);
|
||||
expect(mailboxes.length).to.be(2);
|
||||
expect(mailboxes[0].name).to.be('girish');
|
||||
expect(mailboxes[1].name).to.be('support');
|
||||
});
|
||||
|
||||
it('list all mailboxes succeeds', async function () {
|
||||
const mailboxes = await mail.listAllMailboxes(1, 10);
|
||||
expect(mailboxes.length).to.be(2);
|
||||
expect(mailboxes[0].name).to.be('girish');
|
||||
expect(mailboxes[0].domain).to.be(domain.domain);
|
||||
expect(mailboxes[1].name).to.be('support');
|
||||
expect(mailboxes[1].domain).to.be(domain.domain);
|
||||
});
|
||||
|
||||
it('mailbox count succeeds', async function () {
|
||||
const count = await mail.getMailboxCount(domain.domain);
|
||||
expect(count).to.be(2);
|
||||
});
|
||||
|
||||
it('can set alias', async function () {
|
||||
await mail.setAliases('support', domain.domain, [ { name: 'support2', domain: domain.domain }, { name: 'help', domain: domain.domain } ]);
|
||||
});
|
||||
|
||||
it('can get aliases of name', async function () {
|
||||
const results = await mail.getAliases('support', domain.domain);
|
||||
expect(results.length).to.be(2);
|
||||
expect(results[0].name).to.be('help');
|
||||
expect(results[0].domain).to.be(domain.domain);
|
||||
expect(results[1].name).to.be('support2');
|
||||
expect(results[1].domain).to.be(domain.domain);
|
||||
});
|
||||
|
||||
it('unset aliases', async function () {
|
||||
await mail.setAliases('support', domain.domain, []);
|
||||
|
||||
const results = await mail.getAliases('support', domain.domain);
|
||||
expect(results.length).to.be(0);
|
||||
});
|
||||
|
||||
it('add list succeeds', async function () {
|
||||
await mail.addList('people', domain.domain, { members: [ 'test@cloudron.io' ], membersOnly: false, active: true }, auditSource);
|
||||
});
|
||||
|
||||
it('cannot add dup list', async function () {
|
||||
const [error] = await safe(mail.addList('people', domain.domain, { members: [ 'admin@cloudron.io' ], membersOnly: false, active: true }, auditSource));
|
||||
expect(error.reason).to.be(BoxError.ALREADY_EXISTS);
|
||||
});
|
||||
|
||||
it('cannot get non-existing list', async function () {
|
||||
const result = await mail.getList('random', domain.domain);
|
||||
expect(result).to.be(null);
|
||||
});
|
||||
|
||||
it('del list succeeds', async function () {
|
||||
await mail.delList('people', domain.domain, auditSource);
|
||||
const result = await mail.getList('people', domain.domain);
|
||||
expect(result).to.be(null);
|
||||
});
|
||||
|
||||
it('del non-existent list fails', async function () {
|
||||
const [error] = await safe(mail.delList('people', domain.domain, auditSource));
|
||||
expect(error.reason).to.be(BoxError.NOT_FOUND);
|
||||
});
|
||||
|
||||
it('del mailbox succeeds', async function () {
|
||||
await mail.delMailbox('girish', domain.domain, {/*options*/}, auditSource);
|
||||
const result = await mail.getMailbox('girish', domain.domain);
|
||||
expect(result).to.be(null);
|
||||
});
|
||||
|
||||
it('del non-existent mailbox fails', async function () {
|
||||
const [error] = await safe(mail.delMailbox('girish', domain.domain, {/*options*/}, auditSource));
|
||||
expect(error.reason).to.be(BoxError.NOT_FOUND);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user