make recvmail work

unlike sendmail, recvmail is always optional. this is the case because
the cloudron may not receive emails at all, so app always has to be
prepared for it.

part of #804
This commit is contained in:
Girish Ramakrishnan
2021-10-01 12:09:13 -07:00
parent aed84a6ac9
commit a3fc5f226a
7 changed files with 134 additions and 46 deletions

View File

@@ -40,6 +40,7 @@ exports = module.exports = {
setDebugMode,
setEnvironment,
setMailbox,
setInbox,
setLocation,
setDataDir,
repair,
@@ -180,7 +181,8 @@ const APPS_FIELDS_PREFIXED = [ 'apps.id', 'apps.appStoreId', 'apps.installationS
'apps.health', 'apps.containerId', 'apps.manifestJson', 'apps.accessRestrictionJson', 'apps.memoryLimit', 'apps.cpuShares',
'apps.label', 'apps.tagsJson', 'apps.taskId', 'apps.reverseProxyConfigJson', 'apps.servicesConfigJson', 'apps.operatorsJson',
'apps.sso', 'apps.debugModeJson', 'apps.enableBackup', 'apps.proxyAuth', 'apps.containerIp', 'apps.crontab',
'apps.creationTime', 'apps.updateTime', 'apps.enableMailbox', 'apps.mailboxName', 'apps.mailboxDomain', 'apps.enableAutomaticUpdate',
'apps.creationTime', 'apps.updateTime', 'apps.enableAutomaticUpdate',
'apps.enableMailbox', 'apps.mailboxName', 'apps.mailboxDomain', 'apps.enableInbox', 'apps.inboxName', 'apps.inboxDomain',
'apps.dataDir', 'apps.ts', 'apps.healthTime', '(apps.icon IS NOT NULL) AS hasIcon', '(apps.appStoreIcon IS NOT NULL) AS hasAppStoreIcon' ].join(',');
// const PORT_BINDINGS_FIELDS = [ 'hostPort', 'type', 'environmentVariable', 'appId' ].join(',');
@@ -494,10 +496,11 @@ function getDataDir(app, dataDir) {
function removeInternalFields(app) {
return _.pick(app,
'id', 'appStoreId', 'installationState', 'error', 'runState', 'health', 'taskId',
'location', 'domain', 'fqdn', 'mailboxName', 'mailboxDomain', 'crontab',
'location', 'domain', 'fqdn', 'crontab',
'accessRestriction', 'manifest', 'portBindings', 'iconUrl', 'memoryLimit', 'cpuShares', 'operators',
'sso', 'debugMode', 'reverseProxyConfig', 'enableBackup', 'creationTime', 'updateTime', 'ts', 'tags',
'label', 'alternateDomains', 'aliasDomains', 'env', 'enableAutomaticUpdate', 'dataDir', 'mounts', 'enableMailbox');
'label', 'alternateDomains', 'aliasDomains', 'env', 'enableAutomaticUpdate', 'dataDir', 'mounts',
'enableMailbox', 'mailboxName', 'mailboxDomain', 'enableInbox', 'inboxName', 'inboxDomain');
}
// non-admins can only see these
@@ -586,6 +589,7 @@ function postProcess(result) {
result.enableBackup = !!result.enableBackup;
result.enableAutomaticUpdate = !!result.enableAutomaticUpdate;
result.enableMailbox = !!result.enableMailbox;
result.enableInbox = !!result.enableInbox;
result.proxyAuth = !!result.proxyAuth;
result.hasIcon = !!result.hasIcon;
result.hasAppStoreIcon = !!result.hasAppStoreIcon;
@@ -702,30 +706,31 @@ async function add(id, appStoreId, manifest, location, domain, portBindings, dat
portBindings = portBindings || { };
const manifestJson = JSON.stringify(manifest);
const accessRestriction = data.accessRestriction || null;
const accessRestrictionJson = JSON.stringify(accessRestriction);
const memoryLimit = data.memoryLimit || 0;
const cpuShares = data.cpuShares || 512;
const installationState = data.installationState;
const runState = data.runState;
const sso = 'sso' in data ? data.sso : null;
const debugModeJson = data.debugMode ? JSON.stringify(data.debugMode) : null;
const env = data.env || {};
const label = data.label || null;
const tagsJson = data.tags ? JSON.stringify(data.tags) : null;
const mailboxName = data.mailboxName || null;
const mailboxDomain = data.mailboxDomain || null;
const reverseProxyConfigJson = data.reverseProxyConfig ? JSON.stringify(data.reverseProxyConfig) : null;
const servicesConfigJson = data.servicesConfig ? JSON.stringify(data.servicesConfig) : null;
const enableMailbox = data.enableMailbox || false;
const icon = data.icon || null;
const manifestJson = JSON.stringify(manifest),
accessRestriction = data.accessRestriction || null,
accessRestrictionJson = JSON.stringify(accessRestriction),
memoryLimit = data.memoryLimit || 0,
cpuShares = data.cpuShares || 512,
installationState = data.installationState,
runState = data.runState,
sso = 'sso' in data ? data.sso : null,
debugModeJson = data.debugMode ? JSON.stringify(data.debugMode) : null,
env = data.env || {},
label = data.label || null,
tagsJson = data.tags ? JSON.stringify(data.tags) : null,
mailboxName = data.mailboxName || null,
mailboxDomain = data.mailboxDomain || null,
reverseProxyConfigJson = data.reverseProxyConfig ? JSON.stringify(data.reverseProxyConfig) : null,
servicesConfigJson = data.servicesConfig ? JSON.stringify(data.servicesConfig) : null,
enableMailbox = data.enableMailbox || false,
icon = data.icon || null;
let queries = [];
const queries = [];
queries.push({
query: 'INSERT INTO apps (id, appStoreId, manifestJson, installationState, runState, accessRestrictionJson, memoryLimit, cpuShares, '
+ 'sso, debugModeJson, mailboxName, mailboxDomain, label, tagsJson, reverseProxyConfigJson, servicesConfigJson, icon, enableMailbox) '
+ 'sso, debugModeJson, mailboxName, mailboxDomain, label, tagsJson, reverseProxyConfigJson, servicesConfigJson, icon, '
+ 'enableMailbox) '
+ ' VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
args: [ id, appStoreId, manifestJson, installationState, runState, accessRestrictionJson, memoryLimit, cpuShares,
sso, debugModeJson, mailboxName, mailboxDomain, label, tagsJson, reverseProxyConfigJson, servicesConfigJson, icon, enableMailbox ]
@@ -1178,7 +1183,6 @@ async function install(data, auditSource) {
overwriteDns = 'overwriteDns' in data ? data.overwriteDns : false,
skipDnsSetup = 'skipDnsSetup' in data ? data.skipDnsSetup : false,
appStoreId = data.appStoreId,
enableMailbox = 'enabledMailbox' in data ? data.enableMailbox : true,
manifest = data.manifest;
let error = manifestFormat.parse(manifest);
@@ -1216,6 +1220,7 @@ async function install(data, auditSource) {
if (settings.isDemo() && constants.DEMO_BLACKLISTED_APPS.includes(appStoreId)) throw new BoxError(BoxError.BAD_FIELD, 'This app is blacklisted in the demo');
// sendmail is enabled by default
const enableMailbox = 'enableMailbox' in data ? data.enableMailbox : true;
const mailboxName = manifest.addons?.sendmail ? mailboxNameForLocation(location, manifest) : null;
const mailboxDomain = manifest.addons?.sendmail ? domain : null;
@@ -1477,8 +1482,9 @@ async function setMailbox(app, data, auditSource) {
assert.strictEqual(typeof data, 'object');
assert.strictEqual(typeof auditSource, 'object');
const { enable, mailboxDomain } = data;
assert.strictEqual(typeof enable, 'boolean');
assert.strictEqual(typeof data.enable, 'boolean');
const enableMailbox = data.enable;
const appId = app.id;
let error = checkAppState(app, exports.ISTATE_PENDING_RECREATE_CONTAINER);
@@ -1486,25 +1492,64 @@ async function setMailbox(app, data, auditSource) {
if (!app.manifest.addons?.sendmail) throw new BoxError(BoxError.BAD_FIELD, 'App does not use sendmail');
const optional = 'optional' in app.manifest.addons.sendmail ? app.manifest.addons.sendmail.optional : false;
if (!optional && !enable) throw new BoxError(BoxError.BAD_FIELD, 'App requires sendmail to be enabled');
if (!optional && !enableMailbox) throw new BoxError(BoxError.BAD_FIELD, 'App requires sendmail to be enabled');
await mail.getDomain(mailboxDomain); // check if domain exists
let mailboxName = data.mailboxName || null;
const mailboxDomain = data.mailboxDomain || null;
let mailboxName = data.mailboxName;
if (mailboxName) {
error = mail.validateName(mailboxName);
if (error) throw new BoxError(BoxError.BAD_FIELD, error.message, { field: 'mailboxName' });
} else {
mailboxName = mailboxNameForLocation(app.location, app.domain, app.manifest);
if (enableMailbox) {
await mail.getDomain(mailboxDomain); // check if domain exists
if (mailboxName) {
error = mail.validateName(mailboxName);
if (error) throw new BoxError(BoxError.BAD_FIELD, error.message, { field: 'mailboxName' });
} else {
mailboxName = mailboxNameForLocation(app.location, app.domain, app.manifest);
}
}
const task = {
args: {},
values: { enableMailbox: enable, mailboxName, mailboxDomain }
values: { enableMailbox, mailboxName, mailboxDomain }
};
const taskId = await addTask(appId, exports.ISTATE_PENDING_RECREATE_CONTAINER, task);
await eventlog.add(eventlog.ACTION_APP_CONFIGURE, auditSource, { appId, app, mailboxName, taskId });
await eventlog.add(eventlog.ACTION_APP_CONFIGURE, auditSource, { appId, app, mailboxName, mailboxDomain, taskId });
return { taskId };
}
async function setInbox(app, data, auditSource) {
assert.strictEqual(typeof app, 'object');
assert.strictEqual(typeof data, 'object');
assert.strictEqual(typeof auditSource, 'object');
assert.strictEqual(typeof data.enable, 'boolean');
const enableInbox = data.enable;
const appId = app.id;
let error = checkAppState(app, exports.ISTATE_PENDING_RECREATE_CONTAINER);
if (error) throw error;
if (!app.manifest.addons?.recvmail) throw new BoxError(BoxError.BAD_FIELD, 'App does not use recvmail addon');
const inboxName = data.inboxName || null;
const inboxDomain = data.inboxDomain || null;
if (enableInbox) {
const domain = await mail.getDomain(data.inboxDomain); // check if domain exists
if (!domain.enabled) throw new BoxError(BoxError.BAD_FIELD, 'Domain does not have incoming email enabled');
error = mail.validateName(data.inboxName);
if (error) throw new BoxError(BoxError.BAD_FIELD, error.message, { field: 'inboxName' });
}
const task = {
args: {},
values: { enableInbox, inboxName, inboxDomain }
};
const taskId = await addTask(appId, exports.ISTATE_PENDING_RECREATE_CONTAINER, task);
await eventlog.add(eventlog.ACTION_APP_CONFIGURE, auditSource, { appId, app, enableInbox, inboxName, inboxDomain, taskId });
return { taskId };
}
@@ -1826,7 +1871,7 @@ async function repair(app, data, auditSource) {
error = checkManifestConstraints(data.manifest);
if (error) throw error;
if (!hasMailAddon(data.manifest)) { // clear if repair removed addon
if (!data.manifest.addons?.sendmail) { // clear if repair removed addon
task.values.mailboxName = task.values.mailboxDomain = null;
} else if (!app.mailboxName || app.mailboxName.endsWith('.app')) { // allocate since repair added the addon
task.values.mailboxName = mailboxNameForLocation(app.location, data.manifest);