diff --git a/CHANGES b/CHANGES index 8f4309278..167398410 100644 --- a/CHANGES +++ b/CHANGES @@ -2179,4 +2179,5 @@ * proxyAuth: add path exclusion * turn: fix for CVE-2020-26262 * app password: fix regression where apps are not listed anymore in the UI +* Support for multiDomain apps (domain aliases) diff --git a/package-lock.json b/package-lock.json index 185f23b0b..afe990ce3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -738,9 +738,9 @@ } }, "cloudron-manifestformat": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/cloudron-manifestformat/-/cloudron-manifestformat-5.9.0.tgz", - "integrity": "sha512-bgHadG6s4PRCCPbWGeZ3lC1TTt9rIb/F1eXTQDym6AboXfBMDUO3fZeADISBNCP305pwyUgVqDFR5yfjm/wOKA==", + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/cloudron-manifestformat/-/cloudron-manifestformat-5.10.0.tgz", + "integrity": "sha512-yRPa8ouY5NWtQP6iBQMDNJWX2814gmgL+EazBI7eePVlSckb363CFkXYmXdO447pWg/K3tQVyGA444xa8zP4vA==", "requires": { "cron": "^1.8.2", "java-packagename-regex": "^1.0.0", @@ -750,20 +750,36 @@ "validator": "^12.2.0" }, "dependencies": { + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + }, "safetydance": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/safetydance/-/safetydance-1.0.0.tgz", "integrity": "sha512-ji9T/p5poiGgx4N3OFORGChAS9L8YL8S0/RpYvEPmQucCPrzmQMfdzbjFG/4+0BhJUvNA19KZUVj3YE8gkLpjA==" }, "semver": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", - "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==" + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "requires": { + "lru-cache": "^6.0.0" + } }, "validator": { "version": "12.2.0", "resolved": "https://registry.npmjs.org/validator/-/validator-12.2.0.tgz", "integrity": "sha512-jJfE/DW6tIK1Ek8nCfNFqt8Wb3nzMoAbocBF6/Icgg1ZFSBpObdnwVY2jQj6qUqzhx5jc71fpvBWyLGO7Xl+nQ==" + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" } } }, diff --git a/package.json b/package.json index 75f6dc4bd..463072058 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "aws-sdk": "^2.828.0", "basic-auth": "^2.0.1", "body-parser": "^1.19.0", - "cloudron-manifestformat": "^5.9.0", + "cloudron-manifestformat": "^5.10.0", "connect": "^3.7.0", "connect-lastmile": "^2.0.0", "connect-timeout": "^1.9.0", diff --git a/src/appdb.js b/src/appdb.js index 0a3f27072..78671f856 100644 --- a/src/appdb.js +++ b/src/appdb.js @@ -26,6 +26,7 @@ exports = module.exports = { // subdomain table types SUBDOMAIN_TYPE_PRIMARY: 'primary', SUBDOMAIN_TYPE_REDIRECT: 'redirect', + SUBDOMAIN_TYPE_ALIAS: 'alias', _clear: clear }; @@ -101,12 +102,15 @@ function postProcess(result) { delete result.subdomainTypes; result.alternateDomains = []; + result.aliasDomains = []; for (let i = 0; i < subdomainTypes.length; i++) { if (subdomainTypes[i] === exports.SUBDOMAIN_TYPE_PRIMARY) { result.location = subdomains[i]; result.domain = domains[i]; - } else { + } else if (subdomainTypes[i] === exports.SUBDOMAIN_TYPE_REDIRECT) { result.alternateDomains.push({ domain: domains[i], subdomain: subdomains[i] }); + } else if (subdomainTypes[i] === exports.SUBDOMAIN_TYPE_ALIAS) { + result.aliasDomains.push({ domain: domains[i], subdomain: subdomains[i] }); } } @@ -250,6 +254,15 @@ function add(id, appStoreId, manifest, location, domain, portBindings, data, cal }); } + if (data.aliasDomains) { + data.aliasDomains.forEach(function (d) { + queries.push({ + query: 'INSERT INTO subdomains (appId, domain, subdomain, type) VALUES (?, ?, ?, ?)', + args: [ id, d.domain, d.subdomain, exports.SUBDOMAIN_TYPE_ALIAS ] + }); + }); + } + database.transaction(queries, function (error) { if (error && error.code === 'ER_DUP_ENTRY') return callback(new BoxError(BoxError.ALREADY_EXISTS, error.message)); if (error && error.code === 'ER_NO_REFERENCED_ROW_2') return callback(new BoxError(BoxError.NOT_FOUND, 'no such domain')); @@ -347,6 +360,7 @@ function updateWithConstraints(id, app, constraints, callback) { assert(!('portBindings' in app) || typeof app.portBindings === 'object'); assert(!('accessRestriction' in app) || typeof app.accessRestriction === 'object' || app.accessRestriction === ''); assert(!('alternateDomains' in app) || Array.isArray(app.alternateDomains)); + assert(!('aliasDomains' in app) || Array.isArray(app.aliasDomains)); assert(!('tags' in app) || Array.isArray(app.tags)); assert(!('env' in app) || typeof app.env === 'object'); @@ -382,6 +396,12 @@ function updateWithConstraints(id, app, constraints, callback) { queries.push({ query: 'INSERT INTO subdomains (appId, domain, subdomain, type) VALUES (?, ?, ?, ?)', args: [ id, d.domain, d.subdomain, exports.SUBDOMAIN_TYPE_REDIRECT ]}); }); } + + if ('aliasDomains' in app) { + app.aliasDomains.forEach(function (d) { + queries.push({ query: 'INSERT INTO subdomains (appId, domain, subdomain, type) VALUES (?, ?, ?, ?)', args: [ id, d.domain, d.subdomain, exports.SUBDOMAIN_TYPE_ALIAS ]}); + }); + } } if ('mounts' in app) { @@ -396,7 +416,7 @@ function updateWithConstraints(id, app, constraints, callback) { if (p === 'manifest' || p === 'tags' || p === 'accessRestriction' || p === 'debugMode' || p === 'error' || p === 'reverseProxyConfig' || p === 'servicesConfig') { fields.push(`${p}Json = ?`); values.push(JSON.stringify(app[p])); - } else if (p !== 'portBindings' && p !== 'location' && p !== 'domain' && p !== 'alternateDomains' && p !== 'env' && p !== 'mounts') { + } else if (p !== 'portBindings' && p !== 'location' && p !== 'domain' && p !== 'alternateDomains' && p !== 'aliasDomains' && p !== 'env' && p !== 'mounts') { fields.push(p + ' = ?'); values.push(app[p]); } diff --git a/src/apps.js b/src/apps.js index 019bf8f52..4a2eac215 100644 --- a/src/apps.js +++ b/src/apps.js @@ -405,13 +405,13 @@ function removeInternalFields(app) { 'location', 'domain', 'fqdn', 'mailboxName', 'mailboxDomain', 'accessRestriction', 'manifest', 'portBindings', 'iconUrl', 'memoryLimit', 'cpuShares', 'sso', 'debugMode', 'reverseProxyConfig', 'enableBackup', 'creationTime', 'updateTime', 'ts', 'tags', - 'label', 'alternateDomains', 'env', 'enableAutomaticUpdate', 'dataDir', 'mounts'); + 'label', 'alternateDomains', 'aliasDomains', 'env', 'enableAutomaticUpdate', 'dataDir', 'mounts'); } // non-admins can only see these function removeRestrictedFields(app) { return _.pick(app, - 'id', 'appStoreId', 'installationState', 'error', 'runState', 'health', 'taskId', 'alternateDomains', 'sso', + 'id', 'appStoreId', 'installationState', 'error', 'runState', 'health', 'taskId', 'alternateDomains', 'aliasDomains', 'sso', 'location', 'domain', 'fqdn', 'manifest', 'portBindings', 'iconUrl', 'creationTime', 'ts', 'tags', 'label', 'enableBackup'); } @@ -455,6 +455,7 @@ function postProcess(app, domainObjectMap) { app.iconUrl = getIconUrlSync(app); app.fqdn = domains.fqdn(app.location, domainObjectMap[app.domain]); app.alternateDomains.forEach(function (ad) { ad.fqdn = domains.fqdn(ad.subdomain, domainObjectMap[ad.domain]); }); + app.aliasDomains.forEach(function (ad) { ad.fqdn = domains.fqdn(ad.subdomain, domainObjectMap[ad.domain]); }); } function hasAccessTo(app, user, callback) { @@ -722,6 +723,7 @@ function install(data, auditSource, callback) { enableBackup = 'enableBackup' in data ? data.enableBackup : true, enableAutomaticUpdate = 'enableAutomaticUpdate' in data ? data.enableAutomaticUpdate : true, alternateDomains = data.alternateDomains || [], + aliasDomains = data.aliasDomains || [], env = data.env || {}, label = data.label || null, tags = data.tags || [], @@ -774,7 +776,7 @@ function install(data, auditSource, callback) { } } - const locations = [{subdomain: location, domain}].concat(alternateDomains); + const locations = [{subdomain: location, domain}].concat(alternateDomains).concat(aliasDomains); validateLocations(locations, function (error, domainObjectMap) { if (error) return callback(error); @@ -786,18 +788,19 @@ function install(data, auditSource, callback) { debug('Will install app with id : ' + appId); var data = { - accessRestriction: accessRestriction, - memoryLimit: memoryLimit, - sso: sso, - debugMode: debugMode, - mailboxName: mailboxName, - mailboxDomain: mailboxDomain, - enableBackup: enableBackup, - enableAutomaticUpdate: enableAutomaticUpdate, - alternateDomains: alternateDomains, - env: env, - label: label, - tags: tags, + accessRestriction, + memoryLimit, + sso, + debugMode, + mailboxName, + mailboxDomain, + enableBackup, + enableAutomaticUpdate, + alternateDomains, + aliasDomains, + env, + label, + tags, runState: exports.RSTATE_RUNNING, installationState: exports.ISTATE_PENDING_INSTALL }; @@ -827,6 +830,7 @@ function install(data, auditSource, callback) { const newApp = _.extend({}, data, { appStoreId, manifest, location, domain, portBindings }); newApp.fqdn = domains.fqdn(newApp.location, domainObjectMap[newApp.domain]); newApp.alternateDomains.forEach(function (ad) { ad.fqdn = domains.fqdn(ad.subdomain, domainObjectMap[ad.domain]); }); + newApp.aliasDomains.forEach(function (ad) { ad.fqdn = domains.fqdn(ad.subdomain, domainObjectMap[ad.domain]); }); eventlog.add(eventlog.ACTION_APP_INSTALL, auditSource, { appId, app: newApp, taskId: result.taskId }); @@ -1182,7 +1186,8 @@ function setLocation(app, data, auditSource, callback) { domain: data.domain.toLowerCase(), // these are intentionally reset, if not set portBindings: null, - alternateDomains: [] + alternateDomains: [], + aliasDomains: [] }; if ('portBindings' in data) { @@ -1202,14 +1207,18 @@ function setLocation(app, data, auditSource, callback) { values.alternateDomains = data.alternateDomains; } - const locations = [{subdomain: values.location, domain: values.domain}].concat(values.alternateDomains); + if ('aliasDomains' in data) { + values.aliasDomains = data.aliasDomains; + } + + const locations = [{subdomain: values.location, domain: values.domain}].concat(values.alternateDomains).concat(values.aliasDomains); validateLocations(locations, function (error, domainObjectMap) { if (error) return callback(error); const task = { args: { - oldConfig: _.pick(app, 'location', 'domain', 'fqdn', 'alternateDomains', 'portBindings'), + oldConfig: _.pick(app, 'location', 'domain', 'fqdn', 'alternateDomains', 'aliasDomains', 'portBindings'), overwriteDns: !!data.overwriteDns }, values @@ -1220,6 +1229,7 @@ function setLocation(app, data, auditSource, callback) { values.fqdn = domains.fqdn(values.location, domainObjectMap[values.domain]); values.alternateDomains.forEach(function (ad) { ad.fqdn = domains.fqdn(ad.subdomain, domainObjectMap[ad.domain]); }); + values.aliasDomains.forEach(function (ad) { ad.fqdn = domains.fqdn(ad.subdomain, domainObjectMap[ad.domain]); }); eventlog.add(eventlog.ACTION_APP_CONFIGURE, auditSource, _.extend({ appId, app, taskId: result.taskId }, values)); @@ -1647,7 +1657,8 @@ function clone(app, data, user, auditSource, callback) { enableBackup: app.enableBackup, reverseProxyConfig: app.reverseProxyConfig, env: app.env, - alternateDomains: [] + alternateDomains: [], + aliasDomains: [] }; appdb.add(newAppId, appStoreId, manifest, location, domain, translatePortBindings(portBindings, manifest), data, function (error) { @@ -1669,6 +1680,8 @@ function clone(app, data, user, auditSource, callback) { const newApp = _.extend({}, data, { appStoreId, manifest, location, domain, portBindings }); newApp.fqdn = domains.fqdn(newApp.location, domainObjectMap[newApp.domain]); newApp.alternateDomains.forEach(function (ad) { ad.fqdn = domains.fqdn(ad.subdomain, domainObjectMap[ad.domain]); }); + newApp.aliasDomains.forEach(function (ad) { ad.fqdn = domains.fqdn(ad.subdomain, domainObjectMap[ad.domain]); }); + eventlog.add(eventlog.ACTION_APP_CLONE, auditSource, { appId: newAppId, oldAppId: appId, backupId: backupId, oldApp: app, newApp: newApp, taskId: result.taskId }); callback(null, { id: newAppId, taskId: result.taskId }); diff --git a/src/apptask.js b/src/apptask.js index 062be5467..fa3099ecb 100644 --- a/src/apptask.js +++ b/src/apptask.js @@ -341,7 +341,7 @@ function registerSubdomains(app, overwrite, callback) { sysinfo.getServerIp(function (error, ip) { if (error) return callback(error); - const allDomains = [ { subdomain: app.location, domain: app.domain }].concat(app.alternateDomains); + const allDomains = [ { subdomain: app.location, domain: app.domain }].concat(app.alternateDomains).concat(app.aliasDomains); debugApp(app, `registerSubdomain: Will register ${JSON.stringify(allDomains)}`); @@ -424,8 +424,8 @@ function waitForDnsPropagation(app, callback) { domains.waitForDnsRecord(app.location, app.domain, 'A', ip, { times: 240 }, function (error) { if (error) return callback(new BoxError(BoxError.DNS_ERROR, `DNS Record is not synced yet: ${error.message}`, { ip: ip, subdomain: app.location, domain: app.domain })); - // now wait for alternateDomains, if any - async.eachSeries(app.alternateDomains, function (domain, iteratorCallback) { + // now wait for alternateDomains and aliasDomains, if any + async.eachSeries(app.alternateDomains.concat(app.aliasDomains), function (domain, iteratorCallback) { domains.waitForDnsRecord(domain.subdomain, domain.domain, 'A', ip, { times: 240 }, function (error) { if (error) return callback(new BoxError(BoxError.DNS_ERROR, `DNS Record is not synced yet: ${error.message}`, { ip: ip, subdomain: domain.subdomain, domain: domain.domain })); @@ -663,6 +663,12 @@ function changeLocation(app, args, progressCallback, callback) { return !app.alternateDomains.some(function (n) { return n.subdomain === o.subdomain && n.domain === o.domain; }); }); + if (oldConfig.aliasDomains) { + obsoleteDomains = obsoleteDomains.concat(oldConfig.aliasDomains.filter(function (o) { + return !app.aliasDomains.some(function (n) { return n.subdomain === o.subdomain && n.domain === o.domain; }); + })); + } + if (locationChanged) obsoleteDomains.push({ subdomain: oldConfig.location, domain: oldConfig.domain }); if (obsoleteDomains.length === 0) return next(); @@ -996,7 +1002,7 @@ function uninstall(app, args, progressCallback, callback) { docker.deleteImage.bind(null, app.manifest), progressCallback.bind(null, { percent: 70, message: 'Unregistering domains' }), - unregisterSubdomains.bind(null, app, [ { subdomain: app.location, domain: app.domain } ].concat(app.alternateDomains)), + unregisterSubdomains.bind(null, app, [ { subdomain: app.location, domain: app.domain } ].concat(app.alternateDomains).concat(app.aliasDomains)), progressCallback.bind(null, { percent: 80, message: 'Cleanup icon' }), removeIcon.bind(null, app), diff --git a/src/reverseproxy.js b/src/reverseproxy.js index 6685a7e92..8235d8d6f 100644 --- a/src/reverseproxy.js +++ b/src/reverseproxy.js @@ -438,8 +438,9 @@ function writeDashboardConfig(domain, callback) { }); } -function writeAppNginxConfig(app, bundle, callback) { +function writeAppNginxConfig(app, fqdn, bundle, callback) { assert.strictEqual(typeof app, 'object'); + assert.strictEqual(typeof fqdn, 'string'); assert.strictEqual(typeof bundle, 'object'); assert.strictEqual(typeof callback, 'function'); @@ -458,7 +459,7 @@ function writeAppNginxConfig(app, bundle, callback) { var data = { sourceDir: sourceDir, adminOrigin: settings.adminOrigin(), - vhost: app.fqdn, + vhost: fqdn, hasIPv6: sysinfo.hasIPv6(), ip: app.containerIp, port: app.manifest.httpPort, @@ -477,8 +478,9 @@ function writeAppNginxConfig(app, bundle, callback) { }; var nginxConf = ejs.render(NGINX_APPCONFIG_EJS, data); - var nginxConfigFilename = path.join(paths.NGINX_APPCONFIG_DIR, app.id + '.conf'); - debug('writeAppNginxConfig: writing config for "%s" to %s with options %j', app.fqdn, nginxConfigFilename, data); + const aliasSuffix = app.fqdn === fqdn ? '' : `-alias-${fqdn}`; + var nginxConfigFilename = path.join(paths.NGINX_APPCONFIG_DIR, `${app.id}${aliasSuffix}.conf`); + debug('writeAppNginxConfig: writing config for "%s" to %s with options %j', fqdn, nginxConfigFilename, data); if (!safe.fs.writeFileSync(nginxConfigFilename, nginxConf)) { debug('Error creating nginx config for "%s" : %s', app.fqdn, safe.error.message); @@ -525,21 +527,30 @@ function writeAppConfig(app, callback) { assert.strictEqual(typeof app, 'object'); assert.strictEqual(typeof callback, 'function'); - getCertificate(app.fqdn, app.domain, function (error, bundle) { - if (error) return callback(error); + let appDomains = []; + appDomains.push({ domain: app.domain, fqdn: app.fqdn, type: 'main' }); - writeAppNginxConfig(app, bundle, function (error) { - if (error) return callback(error); - - async.eachSeries(app.alternateDomains, function (alternateDomain, iteratorDone) { - getCertificate(alternateDomain.fqdn, alternateDomain.domain, function (error, bundle) { - if (error) return iteratorDone(error); - - writeAppRedirectNginxConfig(app, alternateDomain.fqdn, bundle, iteratorDone); - }); - }, callback); - }); + app.alternateDomains.forEach(function (alternateDomain) { + appDomains.push({ domain: alternateDomain.domain, fqdn: alternateDomain.fqdn, type: 'alternate' }); }); + + app.aliasDomains.forEach(function (aliasDomain) { + appDomains.push({ domain: aliasDomain.domain, fqdn: aliasDomain.fqdn, type: 'alias' }); + }); + + async.eachSeries(appDomains, function (appDomain, iteratorDone) { + getCertificate(appDomain.fqdn, appDomain.domain, function (error, bundle) { + if (error) return iteratorDone(error); + + if (appDomain.type === 'main') { + writeAppNginxConfig(app, appDomain.fqdn, bundle, iteratorDone); + } else if (appDomain.type === 'alternate') { + writeAppRedirectNginxConfig(app, appDomain.fqdn, bundle, iteratorDone); + } else if (appDomain.type === 'alias') { + writeAppNginxConfig(app, appDomain.fqdn, bundle, iteratorDone); + } + }); + }, callback); } function configureApp(app, auditSource, callback) { @@ -547,21 +558,30 @@ function configureApp(app, auditSource, callback) { assert.strictEqual(typeof auditSource, 'object'); assert.strictEqual(typeof callback, 'function'); - ensureCertificate(app.fqdn, app.domain, auditSource, function (error, bundle) { - if (error) return callback(error); + let appDomains = []; + appDomains.push({ domain: app.domain, fqdn: app.fqdn, type: 'main' }); - writeAppNginxConfig(app, bundle, function (error) { - if (error) return callback(error); - - async.eachSeries(app.alternateDomains, function (alternateDomain, iteratorDone) { - ensureCertificate(alternateDomain.fqdn, alternateDomain.domain, auditSource, function (error, bundle) { - if (error) return iteratorDone(error); - - writeAppRedirectNginxConfig(app, alternateDomain.fqdn, bundle, iteratorDone); - }); - }, callback); - }); + app.alternateDomains.forEach(function (alternateDomain) { + appDomains.push({ domain: alternateDomain.domain, fqdn: alternateDomain.fqdn, type: 'alternate' }); }); + + app.aliasDomains.forEach(function (aliasDomain) { + appDomains.push({ domain: aliasDomain.domain, fqdn: aliasDomain.fqdn, type: 'alias' }); + }); + + async.eachSeries(appDomains, function (appDomain, iteratorDone) { + ensureCertificate(appDomain.fqdn, appDomain.domain, auditSource, function (error, bundle) { + if (error) return iteratorDone(error); + + if (appDomain.type === 'main') { + writeAppNginxConfig(app, appDomain.fqdn, bundle, iteratorDone); + } else if (appDomain.type === 'alternate') { + writeAppRedirectNginxConfig(app, appDomain.fqdn, bundle, iteratorDone); + } else if (appDomain.type === 'alias') { + writeAppNginxConfig(app, appDomain.fqdn, bundle, iteratorDone); + } + }); + }, callback); } function unconfigureApp(app, callback) { @@ -585,7 +605,7 @@ function renewCerts(options, auditSource, progressCallback, callback) { apps.getAll(function (error, allApps) { if (error) return callback(error); - var appDomains = []; + let appDomains = []; // add webadmin and mail domain if (settings.mailFqdn() === settings.adminFqdn()) { @@ -605,6 +625,11 @@ function renewCerts(options, auditSource, progressCallback, callback) { const nginxConfigFilename = path.join(paths.NGINX_APPCONFIG_DIR, `${app.id}-redirect-${alternateDomain.fqdn}.conf`); appDomains.push({ domain: alternateDomain.domain, fqdn: alternateDomain.fqdn, type: 'alternate', app: app, nginxConfigFilename }); }); + + app.aliasDomains.forEach(function (aliasDomain) { + const nginxConfigFilename = path.join(paths.NGINX_APPCONFIG_DIR, `${app.id}-alias-${aliasDomain.fqdn}.conf`); + appDomains.push({ domain: aliasDomain.domain, fqdn: aliasDomain.fqdn, type: 'alias', app: app, nginxConfigFilename }); + }); }); if (options.domain) appDomains = appDomains.filter(function (appDomain) { return appDomain.domain === options.domain; }); @@ -637,9 +662,11 @@ function renewCerts(options, auditSource, progressCallback, callback) { writeDashboardNginxConfig.bind(null, bundle, `${settings.adminFqdn()}.conf`, settings.adminFqdn()) ], iteratorCallback); } else if (appDomain.type === 'main') { - return writeAppNginxConfig(appDomain.app, bundle, iteratorCallback); + return writeAppNginxConfig(appDomain.app, appDomain.fqdn, bundle, iteratorCallback); } else if (appDomain.type === 'alternate') { return writeAppRedirectNginxConfig(appDomain.app, appDomain.fqdn, bundle, iteratorCallback); + } else if (appDomain.type === 'alias') { + return writeAppNginxConfig(appDomain.app, appDomain.fqdn, bundle, iteratorCallback); } iteratorCallback(new BoxError(BoxError.INTERNAL_ERROR, `Unknown domain type for ${appDomain.fqdn}. This should never happen`)); diff --git a/src/routes/apps.js b/src/routes/apps.js index a07bd318d..03452b8f9 100644 --- a/src/routes/apps.js +++ b/src/routes/apps.js @@ -140,6 +140,11 @@ function install(req, res, next) { if (data.alternateDomains.some(function (d) { return (typeof d.domain !== 'string' || typeof d.subdomain !== 'string'); })) return next(new HttpError(400, 'alternateDomains array must contain objects with domain and subdomain strings')); } + if ('aliasDomains' in data) { + if (!Array.isArray(data.aliasDomains)) return next(new HttpError(400, 'aliasDomains must be an array')); + if (data.aliasDomains.some(function (d) { return (typeof d.domain !== 'string' || typeof d.subdomain !== 'string'); })) return next(new HttpError(400, 'aliasDomains array must contain objects with domain and subdomain strings')); + } + if ('env' in data) { if (!data.env || typeof data.env !== 'object') return next(new HttpError(400, 'env must be an object')); if (Object.keys(data.env).some(function (key) { return typeof data.env[key] !== 'string'; })) return next(new HttpError(400, 'env must contain values as strings')); @@ -354,6 +359,11 @@ function setLocation(req, res, next) { if (req.body.alternateDomains.some(function (d) { return (typeof d.domain !== 'string' || typeof d.subdomain !== 'string'); })) return next(new HttpError(400, 'alternateDomains array must contain objects with domain and subdomain strings')); } + if ('aliasDomains' in req.body) { + if (!Array.isArray(req.body.aliasDomains)) return next(new HttpError(400, 'aliasDomains must be an array')); + if (req.body.aliasDomains.some(function (d) { return (typeof d.domain !== 'string' || typeof d.subdomain !== 'string'); })) return next(new HttpError(400, 'aliasDomains array must contain objects with domain and subdomain strings')); + } + if ('overwriteDns' in req.body && typeof req.body.overwriteDns !== 'boolean') return next(new HttpError(400, 'overwriteDns must be boolean')); apps.setLocation(req.resource, req.body, auditSource.fromRequest(req), function (error, result) {