diff --git a/src/routes/setup.js b/src/routes/setup.js index 686a4b74f..b9cfa2bad 100644 --- a/src/routes/setup.js +++ b/src/routes/setup.js @@ -3,7 +3,7 @@ exports = module.exports = { providerTokenAuth: providerTokenAuth, setupTokenAuth: setupTokenAuth, - dnsSetup: dnsSetup, + provision: provision, activate: activate, restore: restore, getStatus: getStatus, @@ -62,20 +62,23 @@ function setupTokenAuth(req, res, next) { }); } -function dnsSetup(req, res, next) { +function provision(req, res, next) { assert.strictEqual(typeof req.body, 'object'); - if (typeof req.body.provider !== 'string' || !req.body.provider) return next(new HttpError(400, 'provider is required')); - if (typeof req.body.domain !== 'string' || !req.body.domain) return next(new HttpError(400, 'domain is required')); - if (typeof req.body.adminFqdn !== 'string' || !req.body.adminFqdn) return next(new HttpError(400, 'adminFqdn is required')); + if (!req.body.dnsConfig || typeof req.body.dnsConfig !== 'object') return next(new HttpError(400, 'dnsConfig is required')); - if ('zoneName' in req.body && typeof req.body.zoneName !== 'string') return next(new HttpError(400, 'zoneName must be a string')); - if (!req.body.config || typeof req.body.config !== 'object') return next(new HttpError(400, 'config must be an object')); + const dnsConfig = req.body.dnsConfig; - if ('tlsConfig' in req.body && typeof req.body.tlsConfig !== 'object') return next(new HttpError(400, 'tlsConfig must be an object')); - if (req.body.tlsConfig && (!req.body.tlsConfig.provider || typeof req.body.tlsConfig.provider !== 'string')) return next(new HttpError(400, 'tlsConfig.provider must be a string')); + if (typeof dnsConfig.provider !== 'string' || !dnsConfig.provider) return next(new HttpError(400, 'provider is required')); + if (typeof dnsConfig.domain !== 'string' || !dnsConfig.domain) return next(new HttpError(400, 'domain is required')); - setup.dnsSetup(req.body.adminFqdn.toLowerCase(), req.body.domain.toLowerCase(), req.body.zoneName || '', req.body.provider, req.body.config, req.body.tlsConfig || { provider: 'letsencrypt-prod' }, function (error) { + if ('zoneName' in dnsConfig && typeof dnsConfig.zoneName !== 'string') return next(new HttpError(400, 'zoneName must be a string')); + if (!dnsConfig.config || typeof dnsConfig.config !== 'object') return next(new HttpError(400, 'config must be an object')); + + 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) { 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/routes/test/mail-test.js b/src/routes/test/mail-test.js index 403aaf176..2ef82f334 100644 --- a/src/routes/test/mail-test.js +++ b/src/routes/test/mail-test.js @@ -10,7 +10,6 @@ var async = require('async'), database = require('../../database.js'), expect = require('expect.js'), mail = require('../../mail.js'), - domains = require('../../domains.js'), maildb = require('../../maildb.js'), server = require('../../server.js'), superagent = require('superagent'), @@ -48,8 +47,8 @@ function setup(done) { database._clear.bind(null), function dnsSetup(callback) { - superagent.post(SERVER_URL + '/api/v1/cloudron/dns_setup') - .send({ provider: ADMIN_DOMAIN.provider, domain: ADMIN_DOMAIN.domain, adminFqdn: 'my.' + ADMIN_DOMAIN.domain, config: ADMIN_DOMAIN.config }) + superagent.post(SERVER_URL + '/api/v1/cloudron/setup') + .send({ dnsConfig: { provider: ADMIN_DOMAIN.provider, domain: ADMIN_DOMAIN.domain, config: ADMIN_DOMAIN.config } }) .end(function (error, result) { expect(result).to.be.ok(); expect(result.statusCode).to.eql(200); diff --git a/src/routes/test/server-test.js b/src/routes/test/server-test.js index bd2f95292..75b30ba0b 100644 --- a/src/routes/test/server-test.js +++ b/src/routes/test/server-test.js @@ -39,234 +39,212 @@ describe('REST API', function () { after(cleanup); it('dns setup fails without provider', function (done) { - superagent.post(SERVER_URL + '/api/v1/cloudron/dns_setup') - .send({ domain: DOMAIN, adminFqdn: 'my.' + DOMAIN, config: {} }) - .end(function (error, result) { - expect(result).to.be.ok(); - expect(result.statusCode).to.eql(400); + superagent.post(SERVER_URL + '/api/v1/cloudron/setup') + .send({ dnsConfig: { domain: DOMAIN, config: {} } }) + .end(function (error, result) { + expect(result).to.be.ok(); + expect(result.statusCode).to.eql(400); - done(); - }); + done(); + }); }); it('dns setup fails with invalid provider', function (done) { - superagent.post(SERVER_URL + '/api/v1/cloudron/dns_setup') - .send({ provider: 'foobar', domain: DOMAIN, adminFqdn: 'my.' + DOMAIN, config: {} }) - .end(function (error, result) { - expect(result).to.be.ok(); - expect(result.statusCode).to.eql(400); + superagent.post(SERVER_URL + '/api/v1/cloudron/setup') + .send({ dnsConfig: { provider: 'foobar', domain: DOMAIN, config: {} } }) + .end(function (error, result) { + expect(result).to.be.ok(); + expect(result.statusCode).to.eql(400); - done(); - }); + done(); + }); }); it('dns setup fails with missing domain', function (done) { - superagent.post(SERVER_URL + '/api/v1/cloudron/dns_setup') - .send({ provider: 'noop', adminFqdn: 'my.' + DOMAIN, config: {} }) - .end(function (error, result) { - expect(result).to.be.ok(); - expect(result.statusCode).to.eql(400); + superagent.post(SERVER_URL + '/api/v1/cloudron/setup') + .send({ dnsConfig: { provider: 'noop', config: {} } }) + .end(function (error, result) { + expect(result).to.be.ok(); + expect(result.statusCode).to.eql(400); - done(); - }); + done(); + }); }); it('dns setup fails with invalid domain', function (done) { - superagent.post(SERVER_URL + '/api/v1/cloudron/dns_setup') - .send({ provider: 'noop', domain: '.foo', adminFqdn: 'my.' + DOMAIN, config: {} }) - .end(function (error, result) { - expect(result).to.be.ok(); - expect(result.statusCode).to.eql(400); + superagent.post(SERVER_URL + '/api/v1/cloudron/setup') + .send({ dnsConfig: { provider: 'noop', domain: '.foo', config: {} } }) + .end(function (error, result) { + expect(result).to.be.ok(); + expect(result.statusCode).to.eql(400); - done(); - }); - }); - - it('dns setup fails with missing adminFqdn', function (done) { - superagent.post(SERVER_URL + '/api/v1/cloudron/dns_setup') - .send({ provider: 'noop', domain: DOMAIN, config: {} }) - .end(function (error, result) { - expect(result).to.be.ok(); - expect(result.statusCode).to.eql(400); - - done(); - }); - }); - - it('dns setup fails with invalid adminFqdn', function (done) { - superagent.post(SERVER_URL + '/api/v1/cloudron/dns_setup') - .send({ provider: 'noop', domain: DOMAIN, adminFqdn: 'my', config: {} }) - .end(function (error, result) { - expect(result).to.be.ok(); - expect(result.statusCode).to.eql(400); - - done(); - }); + done(); + }); }); it('dns setup fails with invalid config', function (done) { - superagent.post(SERVER_URL + '/api/v1/cloudron/dns_setup') - .send({ provider: 'noop', domain: DOMAIN, adminFqdn: 'my' + DOMAIN, config: 'not an object' }) - .end(function (error, result) { - expect(result).to.be.ok(); - expect(result.statusCode).to.eql(400); + superagent.post(SERVER_URL + '/api/v1/cloudron/setup') + .send({ dnsConfig: { provider: 'noop', domain: DOMAIN, config: 'not an object' } }) + .end(function (error, result) { + expect(result).to.be.ok(); + expect(result.statusCode).to.eql(400); - done(); - }); + done(); + }); }); it('dns setup fails with invalid zoneName', function (done) { - superagent.post(SERVER_URL + '/api/v1/cloudron/dns_setup') - .send({ provider: 'noop', domain: DOMAIN, adminFqdn: 'my' + DOMAIN, config: {}, zoneName: 1337 }) - .end(function (error, result) { - expect(result).to.be.ok(); - expect(result.statusCode).to.eql(400); + superagent.post(SERVER_URL + '/api/v1/cloudron/setup') + .send({ dnsConfig: { provider: 'noop', domain: DOMAIN, config: {}, zoneName: 1337 } }) + .end(function (error, result) { + expect(result).to.be.ok(); + expect(result.statusCode).to.eql(400); - done(); - }); + done(); + }); }); it('dns setup fails with invalid tlsConfig', function (done) { - superagent.post(SERVER_URL + '/api/v1/cloudron/dns_setup') - .send({ provider: 'noop', domain: DOMAIN, adminFqdn: 'my' + DOMAIN, config: {}, tlsConfig: 'foobar' }) - .end(function (error, result) { - expect(result).to.be.ok(); - expect(result.statusCode).to.eql(400); + superagent.post(SERVER_URL + '/api/v1/cloudron/setup') + .send({ dnsConfig: { provider: 'noop', domain: DOMAIN, config: {}, tlsConfig: 'foobar' } }) + .end(function (error, result) { + expect(result).to.be.ok(); + expect(result.statusCode).to.eql(400); - done(); - }); + done(); + }); }); it('dns setup fails with invalid tlsConfig provider', function (done) { - superagent.post(SERVER_URL + '/api/v1/cloudron/dns_setup') - .send({ provider: 'noop', domain: DOMAIN, adminFqdn: 'my' + DOMAIN, config: {}, tlsConfig: { provider: 1337 } }) - .end(function (error, result) { - expect(result).to.be.ok(); - expect(result.statusCode).to.eql(400); + superagent.post(SERVER_URL + '/api/v1/cloudron/setup') + .send({ dnsConfig: { provider: 'noop', domain: DOMAIN, config: {}, tlsConfig: { provider: 1337 } } }) + .end(function (error, result) { + expect(result).to.be.ok(); + expect(result.statusCode).to.eql(400); - done(); - }); + done(); + }); }); it('dns setup succeeds', function (done) { - superagent.post(SERVER_URL + '/api/v1/cloudron/dns_setup') - .send({ provider: 'noop', domain: DOMAIN, adminFqdn: 'my.' + DOMAIN, config: {} }) - .end(function (error, result) { - expect(result).to.be.ok(); - expect(result.statusCode).to.eql(200); + superagent.post(SERVER_URL + '/api/v1/cloudron/setup') + .send({ dnsConfig: { provider: 'noop', domain: DOMAIN, adminFqdn: 'my.' + DOMAIN, config: {} } }) + .end(function (error, result) { + expect(result).to.be.ok(); + expect(result.statusCode).to.eql(200); - done(); - }); + done(); + }); }); it('dns setup twice fails', function (done) { - superagent.post(SERVER_URL + '/api/v1/cloudron/dns_setup') - .send({ provider: 'noop', domain: DOMAIN, adminFqdn: 'my.' + DOMAIN, config: {} }) - .end(function (error, result) { - expect(result).to.be.ok(); - expect(result.statusCode).to.eql(409); + 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(); - }); + done(); + }); }); it('activation fails without username', function (done) { superagent.post(SERVER_URL + '/api/v1/cloudron/activate') - .query({ setupToken: 'somesetuptoken' }) - .send({ password: PASSWORD, email: EMAIL }) - .end(function (error, result) { - expect(result).to.be.ok(); - expect(result.statusCode).to.eql(400); + .query({ setupToken: 'somesetuptoken' }) + .send({ password: PASSWORD, email: EMAIL }) + .end(function (error, result) { + expect(result).to.be.ok(); + expect(result.statusCode).to.eql(400); - done(); - }); + done(); + }); }); it('activation fails with invalid username', function (done) { superagent.post(SERVER_URL + '/api/v1/cloudron/activate') - .query({ setupToken: 'somesetuptoken' }) - .send({ username: '?this.is-not!valid', password: PASSWORD, email: EMAIL }) - .end(function (error, result) { - expect(result).to.be.ok(); - expect(result.statusCode).to.eql(400); + .query({ setupToken: 'somesetuptoken' }) + .send({ username: '?this.is-not!valid', password: PASSWORD, email: EMAIL }) + .end(function (error, result) { + expect(result).to.be.ok(); + expect(result.statusCode).to.eql(400); - done(); - }); + done(); + }); }); it('activation fails without email', function (done) { superagent.post(SERVER_URL + '/api/v1/cloudron/activate') - .query({ setupToken: 'somesetuptoken' }) - .send({ username: USERNAME, password: PASSWORD }) - .end(function (error, result) { - expect(result).to.be.ok(); - expect(result.statusCode).to.eql(400); + .query({ setupToken: 'somesetuptoken' }) + .send({ username: USERNAME, password: PASSWORD }) + .end(function (error, result) { + expect(result).to.be.ok(); + expect(result.statusCode).to.eql(400); - done(); - }); + done(); + }); }); it('activation fails with invalid email', function (done) { superagent.post(SERVER_URL + '/api/v1/cloudron/activate') - .query({ setupToken: 'somesetuptoken' }) - .send({ username: USERNAME, password: PASSWORD, email: 'notanemail' }) - .end(function (error, result) { - expect(result).to.be.ok(); - expect(result.statusCode).to.eql(400); + .query({ setupToken: 'somesetuptoken' }) + .send({ username: USERNAME, password: PASSWORD, email: 'notanemail' }) + .end(function (error, result) { + expect(result).to.be.ok(); + expect(result.statusCode).to.eql(400); - done(); - }); + done(); + }); }); it('activation fails without password', function (done) { superagent.post(SERVER_URL + '/api/v1/cloudron/activate') - .query({ setupToken: 'somesetuptoken' }) - .send({ username: USERNAME, email: EMAIL }) - .end(function (error, result) { - expect(result).to.be.ok(); - expect(result.statusCode).to.eql(400); + .query({ setupToken: 'somesetuptoken' }) + .send({ username: USERNAME, email: EMAIL }) + .end(function (error, result) { + expect(result).to.be.ok(); + expect(result.statusCode).to.eql(400); - done(); - }); + done(); + }); }); it('activation fails with invalid password', function (done) { superagent.post(SERVER_URL + '/api/v1/cloudron/activate') - .query({ setupToken: 'somesetuptoken' }) - .send({ username: USERNAME, password: 'short', email: EMAIL }) - .end(function (error, result) { - expect(result).to.be.ok(); - expect(result.statusCode).to.eql(400); + .query({ setupToken: 'somesetuptoken' }) + .send({ username: USERNAME, password: 'short', email: EMAIL }) + .end(function (error, result) { + expect(result).to.be.ok(); + expect(result.statusCode).to.eql(400); - done(); - }); + done(); + }); }); it('activation succeeds', function (done) { superagent.post(SERVER_URL + '/api/v1/cloudron/activate') - .query({ setupToken: 'somesetuptoken' }) - .send({ username: USERNAME, password: PASSWORD, email: EMAIL }) - .end(function (error, result) { - expect(result).to.be.ok(); - expect(result.statusCode).to.eql(201); + .query({ setupToken: 'somesetuptoken' }) + .send({ username: USERNAME, password: PASSWORD, email: EMAIL }) + .end(function (error, result) { + expect(result).to.be.ok(); + expect(result.statusCode).to.eql(201); - // stash token for further use - token = result.body.token; + // stash token for further use + token = result.body.token; - done(); - }); + done(); + }); }); it('activating twice fails', function (done) { superagent.post(SERVER_URL + '/api/v1/cloudron/activate') - .query({ setupToken: 'somesetuptoken' }) - .send({ username: USERNAME, password: PASSWORD, email: EMAIL }) - .end(function (error, result) { - expect(result).to.be.ok(); - expect(result.statusCode).to.eql(409); + .query({ setupToken: 'somesetuptoken' }) + .send({ username: USERNAME, password: PASSWORD, email: EMAIL }) + .end(function (error, result) { + expect(result).to.be.ok(); + expect(result.statusCode).to.eql(409); - done(); - }); + done(); + }); }); it('does not crash with invalid JSON', function (done) { diff --git a/src/server.js b/src/server.js index 238c6e044..ad294bed2 100644 --- a/src/server.js +++ b/src/server.js @@ -109,7 +109,7 @@ function initializeExpressSync() { var csrf = routes.oauth2.csrf(); // public routes - router.post('/api/v1/cloudron/dns_setup', routes.setup.providerTokenAuth, routes.setup.dnsSetup); // only available until no-domain + router.post('/api/v1/cloudron/setup', routes.setup.providerTokenAuth, routes.setup.provision); // only available until no-domain router.post('/api/v1/cloudron/restore', routes.setup.restore); // only available until activated router.post('/api/v1/cloudron/activate', routes.setup.setupTokenAuth, routes.setup.activate); router.get ('/api/v1/cloudron/status', routes.setup.getStatus); diff --git a/src/setup.js b/src/setup.js index 2dd7dd586..fef407a1d 100644 --- a/src/setup.js +++ b/src/setup.js @@ -1,7 +1,7 @@ 'use strict'; exports = module.exports = { - dnsSetup: dnsSetup, + provision: provision, restore: restore, getStatus: getStatus, activate: activate, @@ -155,24 +155,20 @@ function configureWebadmin(callback) { }); } -function dnsSetup(adminFqdn, domain, zoneName, provider, dnsConfig, tlsConfig, callback) { - assert.strictEqual(typeof adminFqdn, 'string'); - assert.strictEqual(typeof domain, 'string'); - assert.strictEqual(typeof zoneName, 'string'); - assert.strictEqual(typeof provider, 'string'); +function provision(dnsConfig, callback) { assert.strictEqual(typeof dnsConfig, 'object'); - assert.strictEqual(typeof tlsConfig, 'object'); assert.strictEqual(typeof callback, 'function'); if (config.adminDomain()) return callback(new SetupError(SetupError.ALREADY_SETUP)); if (gWebadminStatus.configuring || gWebadminStatus.restore.active) return callback(new SetupError(SetupError.BAD_STATE, 'Already restoring or configuring')); - if (!tld.isValid(adminFqdn) || !adminFqdn.endsWith(domain)) return callback(new SetupError(SetupError.BAD_FIELD, 'adminFqdn must be a subdomain of domain')); + const domain = dnsConfig.domain.toLowerCase(); + const zoneName = dnsConfig.zoneName ? dnsConfig.zoneName : (tld.getDomain(domain) || domain); - if (!zoneName) zoneName = tld.getDomain(domain) || domain; + const adminFqdn = 'my' + (dnsConfig.config.hyphenatedSubdomains ? '-' : '.') + domain; - debug(`dnsSetup: Setting up Cloudron with domain ${domain} and zone ${zoneName} using admin fqdn ${adminFqdn}`); + 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)); @@ -198,7 +194,7 @@ function dnsSetup(adminFqdn, domain, zoneName, provider, dnsConfig, tlsConfig, c if (result) return callback(new SetupError(SetupError.BAD_STATE, 'Domain already exists')); async.series([ - domains.add.bind(null, domain, zoneName, provider, dnsConfig, null /* cert */, tlsConfig), + domains.add.bind(null, domain, zoneName, dnsConfig.provider, dnsConfig.config, null /* cert */, dnsConfig.tlsConfig || { provider: 'letsencrypt-prod' }), mail.addDomain.bind(null, domain) ], done); }); diff --git a/src/test/dockerproxy-test.js b/src/test/dockerproxy-test.js index 75e8d2802..8bff0a9be 100644 --- a/src/test/dockerproxy-test.js +++ b/src/test/dockerproxy-test.js @@ -21,7 +21,7 @@ describe('Dockerproxy', function () { dockerProxy.start(function (error) { expect(error).to.not.be.ok(); - exec(`${DOCKER} run -d ubuntu "bin/bash" "-c" "while true; do echo 'perpetual walrus'; sleep 1; done"`, function (error, stdout, stderr) { + exec(`${DOCKER} run -d cloudron/base:1.0.0 "bin/bash" "-c" "while true; do echo 'perpetual walrus'; sleep 1; done"`, function (error, stdout, stderr) { expect(error).to.be(null); expect(stderr).to.be.empty(); @@ -55,7 +55,7 @@ describe('Dockerproxy', function () { }); it('can create container', function (done) { - var cmd = DOCKER + ` run ubuntu "/bin/bash" "-c" "echo 'hello'"`; + var cmd = `${DOCKER} run cloudron/base:1.0.0 "/bin/bash" "-c" "echo 'hello'"`; exec(cmd, function (error, stdout, stderr) { expect(error).to.be(null); expect(stdout).to.contain('hello'); @@ -65,7 +65,7 @@ describe('Dockerproxy', function () { }); it('proxy overwrites the container network option', function (done) { - var cmd = `${DOCKER} run --network ifnotrewritethiswouldfail ubuntu "/bin/bash" "-c" "echo 'hello'"`; + var cmd = `${DOCKER} run --network ifnotrewritethiswouldfail cloudron/base:1.0.0 "/bin/bash" "-c" "echo 'hello'"`; exec(cmd, function (error, stdout, stderr) { expect(error).to.be(null); expect(stdout).to.contain('hello'); @@ -85,7 +85,7 @@ describe('Dockerproxy', function () { }); it('can use PUT to upload archive into a container', function (done) { - exec(`${DOCKER} cp -a ${__dirname}/proxytestarchive.tar ${containerId}:/tmp/`, function (error, stdout, stderr) { + exec(`${DOCKER} cp ${__dirname}/proxytestarchive.tar ${containerId}:/tmp/`, function (error, stdout, stderr) { expect(error).to.be(null); expect(stderr).to.be.empty(); expect(stdout).to.be.empty();