diff --git a/src/domains.js b/src/domains.js index c66f530d5..6f7f7d98f 100644 --- a/src/domains.js +++ b/src/domains.js @@ -208,16 +208,17 @@ function add(domain, zoneName, provider, dnsConfig, fallbackCertificate, tlsConf zoneName = tld.getDomain(domain) || domain; } + if (dnsConfig.hyphenatedSubdomains && !config.allowHyphenatedSubdomains()) return callback(new DomainsError(DomainsError.BAD_FIELD, 'Not allowed in this edition')); + if (fallbackCertificate) { - let error = reverseProxy.validateCertificate(`test.${domain}`, fallbackCertificate.cert, fallbackCertificate.key); + let subdomain = dnsConfig.hyphenatedSubdomains ? `test-${domain}` : `test.${domain}`; + let error = reverseProxy.validateCertificate(subdomain, fallbackCertificate.cert, fallbackCertificate.key); if (error) return callback(new DomainsError(DomainsError.BAD_FIELD, error.message)); } let error = validateTlsConfig(tlsConfig, provider); if (error) return callback(error); - if (dnsConfig.hyphenatedSubdomains && !config.allowHyphenatedSubdomains()) return callback(new DomainsError(DomainsError.BAD_FIELD, 'Not allowed in this edition')); - sysinfo.getPublicIp(function (error, ip) { if (error) return callback(new DomainsError(DomainsError.INTERNAL_ERROR, 'Error getting IP:' + error.message)); @@ -299,16 +300,17 @@ function update(domain, zoneName, provider, dnsConfig, fallbackCertificate, tlsC zoneName = result.zoneName; } + if (dnsConfig.hyphenatedSubdomains && !config.allowHyphenatedSubdomains()) return callback(new DomainsError(DomainsError.BAD_FIELD, 'Not allowed in this edition')); + if (fallbackCertificate) { - let error = reverseProxy.validateCertificate(`test.${domain}`, fallbackCertificate.cert, fallbackCertificate.key); + let subdomain = dnsConfig.hyphenatedSubdomains ? `test-${domain}` : `test.${domain}`; + let error = reverseProxy.validateCertificate(subdomain, fallbackCertificate.cert, fallbackCertificate.key); if (error) return callback(new DomainsError(DomainsError.BAD_FIELD, error.message)); } error = validateTlsConfig(tlsConfig, provider); if (error) return callback(error); - if (dnsConfig.hyphenatedSubdomains && !config.allowHyphenatedSubdomains()) return callback(new DomainsError(DomainsError.BAD_FIELD, 'Not allowed in this edition')); - sysinfo.getPublicIp(function (error, ip) { if (error) return callback(new DomainsError(DomainsError.INTERNAL_ERROR, 'Error getting IP:' + error.message)); diff --git a/src/paths.js b/src/paths.js index b2f1b9071..ca2a8b6be 100644 --- a/src/paths.js +++ b/src/paths.js @@ -33,8 +33,6 @@ exports = module.exports = { CLOUDRON_AVATAR_FILE: path.join(config.baseDir(), 'boxdata/avatar.png'), UPDATE_CHECKER_FILE: path.join(config.baseDir(), 'boxdata/updatechecker.json'), - AUTO_PROVISION_FILE: path.join(config.baseDir(), 'configs/autoprovision.json'), - LOG_DIR: path.join(config.baseDir(), 'platformdata/logs'), // this pattern is for the cloudron logs API route to work BACKUP_LOG_FILE: path.join(config.baseDir(), 'platformdata/logs/backup/app.log'), diff --git a/src/routes/setup.js b/src/routes/setup.js index b9cfa2bad..be395ff4b 100644 --- a/src/routes/setup.js +++ b/src/routes/setup.js @@ -18,7 +18,8 @@ var assert = require('assert'), HttpSuccess = require('connect-lastmile').HttpSuccess, setup = require('../setup.js'), SetupError = require('../setup.js').SetupError, - superagent = require('superagent'); + superagent = require('superagent'), + _ = require('underscore'); function auditSource(req) { var ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress || null; @@ -78,7 +79,10 @@ function provision(req, res, next) { if ('tlsConfig' in dnsConfig && typeof dnsConfig.tlsConfig !== 'object') return next(new HttpError(400, 'tlsConfig must be an object')); if (dnsConfig.tlsConfig && (!dnsConfig.tlsConfig.provider || typeof dnsConfig.tlsConfig.provider !== 'string')) return next(new HttpError(400, 'tlsConfig.provider must be a string')); - setup.provision(dnsConfig, function (error) { + // TODO: validate subfields of these objects + if (req.body.autoconf && typeof req.body.autoconf !== 'object') return next(new HttpError(400, 'autoconf must be an object')); + + setup.provision(dnsConfig, req.body.autoconf || {}, function (error) { if (error && error.reason === SetupError.ALREADY_SETUP) return next(new HttpError(409, error.message)); if (error && error.reason === SetupError.BAD_FIELD) return next(new HttpError(400, error.message)); if (error && error.reason === SetupError.BAD_STATE) return next(new HttpError(409, error.message)); @@ -146,7 +150,10 @@ function restore(req, res, next) { if (typeof req.body.backupId !== 'string') return next(new HttpError(400, 'backupId must be a string or null')); if (typeof req.body.version !== 'string') return next(new HttpError(400, 'version must be a string')); - setup.restore(backupConfig, req.body.backupId, req.body.version, function (error) { + // TODO: validate subfields of these objects + if (req.body.autoconf && typeof req.body.autoconf !== 'object') return next(new HttpError(400, 'autoconf must be an object')); + + setup.restore(backupConfig, req.body.backupId, req.body.version, req.body.autoconf || {}, function (error) { if (error && error.reason === SetupError.ALREADY_SETUP) return next(new HttpError(409, error.message)); if (error && error.reason === SetupError.BAD_FIELD) return next(new HttpError(400, error.message)); if (error && error.reason === SetupError.BAD_STATE) return next(new HttpError(409, error.message)); diff --git a/src/setup.js b/src/setup.js index 9900330b8..73fea9b06 100644 --- a/src/setup.js +++ b/src/setup.js @@ -25,7 +25,6 @@ var assert = require('assert'), eventlog = require('./eventlog.js'), mail = require('./mail.js'), path = require('path'), - paths = require('./paths.js'), reverseProxy = require('./reverseproxy.js'), safe = require('safetydance'), semver = require('semver'), @@ -79,16 +78,11 @@ SetupError.INTERNAL_ERROR = 'Internal Error'; SetupError.EXTERNAL_ERROR = 'External Error'; SetupError.ALREADY_PROVISIONED = 'Already Provisioned'; -function autoprovision(callback) { +function autoprovision(autoconf, callback) { + assert.strictEqual(typeof autoconf, 'object'); assert.strictEqual(typeof callback, 'function'); - const confJson = safe.fs.readFileSync(paths.AUTO_PROVISION_FILE, 'utf8'); - if (!confJson) return callback(); - - const conf = safe.JSON.parse(confJson); - if (!conf) return callback(); - - async.eachSeries(Object.keys(conf), function (key, iteratorDone) { + async.eachSeries(Object.keys(autoconf), function (key, iteratorDone) { var name; switch (key) { case 'appstoreConfig': name = settings.APPSTORE_CONFIG_KEY; break; @@ -100,8 +94,12 @@ function autoprovision(callback) { } debug(`autoprovision: ${name}`); - settingsdb.set(name, JSON.stringify(conf[key]), iteratorDone); - }, callback); + settingsdb.set(name, JSON.stringify(autoconf[key]), iteratorDone); + }, function (error) { + if (error) return callback(new SetupError(SetupError.INTERNAL_ERROR, error)); + + callback(null); + }); } function configureWebadmin(callback) { @@ -148,8 +146,9 @@ function configureWebadmin(callback) { }); } -function provision(dnsConfig, callback) { +function provision(dnsConfig, autoconf, callback) { assert.strictEqual(typeof dnsConfig, 'object'); + assert.strictEqual(typeof autoconf, 'object'); assert.strictEqual(typeof callback, 'function'); if (config.adminDomain()) return callback(new SetupError(SetupError.ALREADY_SETUP)); @@ -163,33 +162,31 @@ function provision(dnsConfig, callback) { debug(`provision: Setting up Cloudron with domain ${domain} and zone ${zoneName} using admin fqdn ${adminFqdn}`); - function done(error) { - if (error && error.reason === DomainsError.BAD_FIELD) return callback(new SetupError(SetupError.BAD_FIELD, error.message)); - if (error && error.reason === DomainsError.ALREADY_EXISTS) return callback(new SetupError(SetupError.BAD_FIELD, error.message)); - if (error) return callback(new SetupError(SetupError.INTERNAL_ERROR, error)); - - config.setAdminDomain(domain); // set fqdn only after dns config is valid, otherwise cannot re-setup if we failed - config.setAdminFqdn(adminFqdn); - config.setAdminLocation('my'); - - autoprovision(function (error) { - if (error) return callback(new SetupError(SetupError.INTERNAL_ERROR, error)); - - clients.addDefaultClients(config.adminOrigin(), callback); - - configureWebadmin(NOOP_CALLBACK); - }); - } - domains.get(domain, function (error, result) { if (error && error.reason !== DomainsError.NOT_FOUND) return callback(new SetupError(SetupError.INTERNAL_ERROR, error)); if (result) return callback(new SetupError(SetupError.BAD_STATE, 'Domain already exists')); async.series([ - domains.add.bind(null, domain, zoneName, dnsConfig.provider, dnsConfig.config, dnsConfig.fallbackCertificate, dnsConfig.tlsConfig || { provider: 'letsencrypt-prod' }), + domains.add.bind(null, domain, zoneName, dnsConfig.provider, dnsConfig.config, dnsConfig.fallbackCertificate || null, dnsConfig.tlsConfig || { provider: 'letsencrypt-prod' }), mail.addDomain.bind(null, domain) - ], done); + ], function (error) { + if (error && error.reason === DomainsError.BAD_FIELD) return callback(new SetupError(SetupError.BAD_FIELD, error.message)); + if (error && error.reason === DomainsError.ALREADY_EXISTS) return callback(new SetupError(SetupError.BAD_FIELD, error.message)); + if (error) return callback(new SetupError(SetupError.INTERNAL_ERROR, error)); + + config.setAdminDomain(domain); // set fqdn only after dns config is valid, otherwise cannot re-setup if we failed + config.setAdminFqdn(adminFqdn); + config.setAdminLocation('my'); + + autoprovision(autoconf, function (error) { + if (error) return callback(error); + + clients.addDefaultClients(config.adminOrigin(), callback); + + configureWebadmin(NOOP_CALLBACK); + }); + }); }); } @@ -252,10 +249,11 @@ function activate(username, password, email, displayName, ip, auditSource, callb }); } -function restore(backupConfig, backupId, version, callback) { +function restore(backupConfig, backupId, version, autoconf, callback) { assert.strictEqual(typeof backupConfig, 'object'); assert.strictEqual(typeof backupId, 'string'); assert.strictEqual(typeof version, 'string'); + assert.strictEqual(typeof autoconf, 'object'); assert.strictEqual(typeof callback, 'function'); if (!semver.valid(version)) return callback(new SetupError(SetupError.BAD_STATE, 'version is not a valid semver')); @@ -281,7 +279,7 @@ function restore(backupConfig, backupId, version, callback) { async.series([ backups.restore.bind(null, backupConfig, backupId), - autoprovision, + autoprovision.bind(null, autoconf), // currently, our suggested restore flow is after a dnsSetup. The dnSetup creates DKIM keys and updates the DNS // for this reason, we have to re-setup DNS after a restore so it has DKIm from the backup // Once we have a 100% IP based restore, we can skip this