diff --git a/migrations/20220601004757-apps-add-mailboxDisplayName.js b/migrations/20220601004757-apps-add-mailboxDisplayName.js new file mode 100644 index 000000000..f34874650 --- /dev/null +++ b/migrations/20220601004757-apps-add-mailboxDisplayName.js @@ -0,0 +1,12 @@ +'use strict'; + +exports.up = function(db, callback) { + db.runSql('ALTER TABLE apps ADD COLUMN mailboxDisplayName VARCHAR(128) DEFAULT "" NOT NULL', [], callback); +}; + +exports.down = function(db, callback) { + db.runSql('ALTER TABLE apps DROP COLUMN mailboxDisplayName', function (error) { + if (error) console.error(error); + callback(error); + }); +}; diff --git a/src/apps.js b/src/apps.js index 7093129fd..dccf39127 100644 --- a/src/apps.js +++ b/src/apps.js @@ -185,7 +185,7 @@ const APPS_FIELDS_PREFIXED = [ 'apps.id', 'apps.appStoreId', 'apps.installationS '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.enableAutomaticUpdate', - 'apps.enableMailbox', 'apps.mailboxName', 'apps.mailboxDomain', 'apps.enableInbox', 'apps.inboxName', 'apps.inboxDomain', + 'apps.enableMailbox', 'apps.mailboxDisplayName', '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(','); @@ -550,7 +550,7 @@ function removeInternalFields(app) { 'accessRestriction', 'manifest', 'portBindings', 'iconUrl', 'memoryLimit', 'cpuShares', 'operators', 'sso', 'debugMode', 'reverseProxyConfig', 'enableBackup', 'creationTime', 'updateTime', 'ts', 'tags', 'label', 'secondaryDomains', 'redirectDomains', 'aliasDomains', 'env', 'enableAutomaticUpdate', 'dataDir', 'mounts', - 'enableMailbox', 'mailboxName', 'mailboxDomain', 'enableInbox', 'inboxName', 'inboxDomain'); + 'enableMailbox', 'mailboxDisplayName', 'mailboxName', 'mailboxDomain', 'enableInbox', 'inboxName', 'inboxDomain'); } // non-admins can only see these @@ -779,6 +779,7 @@ async function add(id, appStoreId, manifest, subdomain, domain, portBindings, da tagsJson = data.tags ? JSON.stringify(data.tags) : null, mailboxName = data.mailboxName || null, mailboxDomain = data.mailboxDomain || null, + mailboxDisplayName = data.mailboxDisplayName || '', reverseProxyConfigJson = data.reverseProxyConfig ? JSON.stringify(data.reverseProxyConfig) : null, servicesConfigJson = data.servicesConfig ? JSON.stringify(data.servicesConfig) : null, enableMailbox = data.enableMailbox || false, @@ -789,10 +790,11 @@ async function add(id, appStoreId, manifest, subdomain, domain, portBindings, da queries.push({ query: 'INSERT INTO apps (id, appStoreId, manifestJson, installationState, runState, accessRestrictionJson, memoryLimit, cpuShares, ' + 'sso, debugModeJson, mailboxName, mailboxDomain, label, tagsJson, reverseProxyConfigJson, servicesConfigJson, icon, ' - + 'enableMailbox) ' - + ' VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', + + 'enableMailbox, mailboxDisplayName) ' + + ' VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', args: [ 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, mailboxDisplayName ] }); queries.push({ @@ -1593,6 +1595,7 @@ async function setMailbox(app, data, auditSource) { const optional = 'optional' in app.manifest.addons.sendmail ? app.manifest.addons.sendmail.optional : false; if (!optional && !enableMailbox) throw new BoxError(BoxError.BAD_FIELD, 'App requires sendmail to be enabled'); + const mailboxDisplayName = data.mailboxDisplayName || ''; let mailboxName = data.mailboxName || null; const mailboxDomain = data.mailboxDomain || null; @@ -1605,15 +1608,20 @@ async function setMailbox(app, data, auditSource) { } else { mailboxName = mailboxNameForSubdomain(app.subdomain, app.domain, app.manifest); } + + if (mailboxDisplayName) { + error = mail.validateDisplayName(mailboxDisplayName); + if (error) throw new BoxError(BoxError.BAD_FIELD, error.message); + } } const task = { args: {}, - values: { enableMailbox, mailboxName, mailboxDomain } + values: { enableMailbox, mailboxName, mailboxDomain, mailboxDisplayName } }; const taskId = await addTask(appId, exports.ISTATE_PENDING_RECREATE_CONTAINER, task, auditSource); - await eventlog.add(eventlog.ACTION_APP_CONFIGURE, auditSource, { appId, app, mailboxName, mailboxDomain, taskId }); + await eventlog.add(eventlog.ACTION_APP_CONFIGURE, auditSource, { appId, app, mailboxName, mailboxDomain, mailboxDisplayName, taskId }); return { taskId }; } @@ -2235,7 +2243,8 @@ async function clone(app, data, user, auditSource) { tags: app.tags, enableAutomaticUpdate: app.enableAutomaticUpdate, icon: icons.icon, - enableMailbox: app.enableMailbox + enableMailbox: app.enableMailbox, + mailboxDisplayName: app.mailboxDisplayName }; const [addError] = await safe(add(newAppId, appStoreId, manifest, subdomain, domain, translatePortBindings(portBindings, manifest), obj)); diff --git a/src/mail.js b/src/mail.js index e0c25e68c..d3f9ac567 100644 --- a/src/mail.js +++ b/src/mail.js @@ -22,6 +22,7 @@ exports = module.exports = { setDnsRecords, validateName, + validateDisplayName, setMailFromValidation, setCatchAllAddress, @@ -65,7 +66,6 @@ exports = module.exports = { TYPE_LIST: 'list', TYPE_ALIAS: 'alias', - _validateName: validateName, _delByDomain: delByDomain, _updateDomain: updateDomain }; @@ -169,6 +169,15 @@ function validateName(name) { return null; } +function validateDisplayName(name) { + assert.strictEqual(typeof name, 'string'); + + if (name.length < 1) return new BoxError(BoxError.BAD_FIELD, 'mailbox display name must be atleast 1 char'); + if (name.length >= 100) return new BoxError(BoxError.BAD_FIELD, 'mailbox name too long'); + + return null; +} + async function checkOutboundPort25() { const relay = { value: 'OK', diff --git a/src/routes/apps.js b/src/routes/apps.js index 376dd25ca..92d91e228 100644 --- a/src/routes/apps.js +++ b/src/routes/apps.js @@ -373,6 +373,7 @@ async function setMailbox(req, res, next) { if (req.body.enable) { if (req.body.mailboxName !== null && typeof req.body.mailboxName !== 'string') return next(new HttpError(400, 'mailboxName must be a string')); if (typeof req.body.mailboxDomain !== 'string') return next(new HttpError(400, 'mailboxDomain must be a string')); + if (typeof req.body.mailboxDisplayName !== 'string') return next(new HttpError(400, 'mailboxDisplayName must be a string')); } const [error, result] = await safe(apps.setMailbox(req.app, req.body, AuditSource.fromRequest(req))); diff --git a/src/services.js b/src/services.js index b7c293fa4..d831bde3a 100644 --- a/src/services.js +++ b/src/services.js @@ -1045,6 +1045,7 @@ async function setupSendMail(app, options) { { name: 'CLOUDRON_MAIL_STARTTLS_PORT', value: '2587' }, { name: 'CLOUDRON_MAIL_SMTP_USERNAME', value: app.mailboxName + '@' + app.mailboxDomain }, { name: 'CLOUDRON_MAIL_SMTP_PASSWORD', value: password }, + { name: 'CLOUDRON_MAIL_FROM_DISPLAY_NAME', value: app.mailboxDisplayName }, { name: 'CLOUDRON_MAIL_FROM', value: app.mailboxName + '@' + app.mailboxDomain }, { name: 'CLOUDRON_MAIL_DOMAIN', value: app.mailboxDomain } ];