diff --git a/src/domaindb.js b/src/domaindb.js index d9e252e24..de7904a20 100644 --- a/src/domaindb.js +++ b/src/domaindb.js @@ -8,8 +8,7 @@ exports = module.exports = { getAll: getAll, update: update, del: del, - - _clear: clear + clear: clear }; var assert = require('assert'), diff --git a/src/domains.js b/src/domains.js index d98c3186c..1784127c7 100644 --- a/src/domains.js +++ b/src/domains.js @@ -6,6 +6,7 @@ module.exports = exports = { getAll: getAll, update: update, del: del, + clear: clear, isLocked: isLocked, renewCerts: renewCerts, @@ -369,6 +370,16 @@ function del(domain, auditSource, callback) { }); } +function clear(callback) { + assert.strictEqual(typeof callback, 'function'); + + domaindb.clear(function (error) { + if (error) return callback(new DomainsError(DomainsError.INTERNAL_ERROR, error)); + + return callback(null); + }); +} + // returns the 'name' that needs to be inserted into zone function getName(domain, subdomain, type) { // hack for supporting special caas domains. if we want to remove this, we have to fix the appstore domain API first diff --git a/src/mail.js b/src/mail.js index 3797deee6..8d37ea422 100644 --- a/src/mail.js +++ b/src/mail.js @@ -8,6 +8,7 @@ exports = module.exports = { getDomain: getDomain, addDomain: addDomain, removeDomain: removeDomain, + clearDomains: clearDomains, setDnsRecords: setDnsRecords, @@ -807,6 +808,16 @@ function removeDomain(domain, callback) { }); } +function clearDomains(callback) { + assert.strictEqual(typeof callback, 'function'); + + maildb.clear(function (error) { + if (error) return callback(new MailError(MailError.INTERNAL_ERROR, error)); + + callback(); + }); +} + function setMailFromValidation(domain, enabled, callback) { assert.strictEqual(typeof domain, 'string'); assert.strictEqual(typeof enabled, 'boolean'); diff --git a/src/maildb.js b/src/maildb.js index b3c968594..82acd3187 100644 --- a/src/maildb.js +++ b/src/maildb.js @@ -7,7 +7,7 @@ exports = module.exports = { list: list, update: update, - _clear: clear, + clear: clear, TYPE_USER: 'user', TYPE_APP: 'app', @@ -49,7 +49,8 @@ function add(domain, callback) { function clear(callback) { assert.strictEqual(typeof callback, 'function'); - database.query('TRUNCATE TABLE mail', [], function (error) { + // using TRUNCATE makes it fail foreign key check + database.query('DELETE FROM mail', [], function (error) { if (error) return callback(new DatabaseError(DatabaseError.INTERNAL_ERROR, error)); callback(null); }); diff --git a/src/routes/test/server-test.js b/src/routes/test/server-test.js index 75b30ba0b..c53cec95c 100644 --- a/src/routes/test/server-test.js +++ b/src/routes/test/server-test.js @@ -137,12 +137,12 @@ describe('REST API', function () { }); }); - it('dns setup twice fails', function (done) { + it('dns setup twice succeeds', function (done) { superagent.post(SERVER_URL + '/api/v1/cloudron/setup') .send({ dnsConfig: { provider: 'noop', domain: DOMAIN, DOMAIN, config: {} } }) .end(function (error, result) { expect(result).to.be.ok(); - expect(result.statusCode).to.eql(409); + expect(result.statusCode).to.eql(200); done(); }); @@ -247,6 +247,17 @@ describe('REST API', function () { }); }); + it('dns setup after activation fails', function (done) { + superagent.post(SERVER_URL + '/api/v1/cloudron/setup') + .send({ dnsConfig: { provider: 'noop', domain: DOMAIN, DOMAIN, config: {} } }) + .end(function (error, result) { + expect(result).to.be.ok(); + expect(result.statusCode).to.eql(409); + + done(); + }); + }); + it('does not crash with invalid JSON', function (done) { superagent.post(SERVER_URL + '/api/v1/users') .query({ access_token: token }) diff --git a/src/setup.js b/src/setup.js index 88e66a2d4..d7c9f2b92 100644 --- a/src/setup.js +++ b/src/setup.js @@ -95,58 +95,75 @@ function autoprovision(autoconf, callback) { }); } +function unprovision(callback) { + assert.strictEqual(typeof callback, 'function'); + + debug('unprovision'); + + config.setAdminDomain(''); + config.setAdminFqdn(''); + config.setAdminLocation('my'); + + // TODO: also cancel any existing configureWebadmin task + async.series([ + mail.clearDomains, + domains.clear + ], callback); +} + function provision(dnsConfig, autoconf, auditSource, callback) { assert.strictEqual(typeof dnsConfig, 'object'); assert.strictEqual(typeof autoconf, 'object'); assert.strictEqual(typeof auditSource, 'object'); assert.strictEqual(typeof callback, 'function'); - if (config.adminDomain()) return callback(new SetupError(SetupError.ALREADY_SETUP)); + users.isActivated(function (error, activated) { + if (error) return callback(new SetupError(SetupError.INTERNAL_ERROR, error)); + if (activated) return callback(new SetupError(SetupError.ALREADY_SETUP)); - let webadminStatus = cloudron.getWebadminStatus(); - - if (webadminStatus.configuring || webadminStatus.restore.active) return callback(new SetupError(SetupError.BAD_STATE, 'Already restoring or configuring')); - - const domain = dnsConfig.domain.toLowerCase(); - const zoneName = dnsConfig.zoneName ? dnsConfig.zoneName : (tld.getDomain(domain) || domain); - - const adminFqdn = 'my' + (dnsConfig.config.hyphenatedSubdomains ? '-' : '.') + domain; - - debug(`provision: Setting up Cloudron with domain ${domain} and zone ${zoneName} using admin fqdn ${adminFqdn}`); - - 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')); - - let data = { - zoneName: zoneName, - provider: dnsConfig.provider, - config: dnsConfig.config, - fallbackCertificate: dnsConfig.fallbackCertificate || null, - tlsConfig: dnsConfig.tlsConfig || { provider: 'letsencrypt-prod' } - }; - - async.series([ - domains.add.bind(null, domain, data, auditSource), - mail.addDomain.bind(null, domain) - ], 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)); + unprovision(function (error) { 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'); + let webadminStatus = cloudron.getWebadminStatus(); - eventlog.add(eventlog.ACTION_PROVISION, auditSource, { }); + if (webadminStatus.configuring || webadminStatus.restore.active) return callback(new SetupError(SetupError.BAD_STATE, 'Already restoring or configuring')); - clients.addDefaultClients(config.adminOrigin(), callback); + const domain = dnsConfig.domain.toLowerCase(); + const zoneName = dnsConfig.zoneName ? dnsConfig.zoneName : (tld.getDomain(domain) || domain); + + const adminFqdn = 'my' + (dnsConfig.config.hyphenatedSubdomains ? '-' : '.') + domain; + + debug(`provision: Setting up Cloudron with domain ${domain} and zone ${zoneName} using admin fqdn ${adminFqdn}`); + + let data = { + zoneName: zoneName, + provider: dnsConfig.provider, + config: dnsConfig.config, + fallbackCertificate: dnsConfig.fallbackCertificate || null, + tlsConfig: dnsConfig.tlsConfig || { provider: 'letsencrypt-prod' } + }; async.series([ - autoprovision.bind(null, autoconf), - cloudron.configureWebadmin - ], NOOP_CALLBACK); + domains.add.bind(null, domain, data, auditSource), + mail.addDomain.bind(null, domain) + ], 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'); + + eventlog.add(eventlog.ACTION_PROVISION, auditSource, { }); + + clients.addDefaultClients(config.adminOrigin(), callback); + + async.series([ + autoprovision.bind(null, autoconf), + cloudron.configureWebadmin + ], NOOP_CALLBACK); + }); }); }); }