diff --git a/src/apps.js b/src/apps.js index f53f96850..9c77689d9 100644 --- a/src/apps.js +++ b/src/apps.js @@ -119,10 +119,10 @@ AppsError.BAD_CERTIFICATE = 'Invalid certificate'; // Domain name validation comes from RFC 2181 (Name syntax) // https://en.wikipedia.org/wiki/Hostname#Restrictions_on_valid_host_names // We are validating the validity of the location-fqdn as host name -function validateHostname(location) { - assert.strictEqual(typeof location, 'string'); +function validateHostname(hostname) { + assert.strictEqual(typeof hostname, 'string'); - if (!location) return new AppsError(AppsError.BAD_FIELD, 'location cannot be empty'); + if (!hostname) return new AppsError(AppsError.BAD_FIELD, 'hostname cannot be empty'); const RESERVED_LOCATIONS = [ config.adminFqdn(), @@ -132,13 +132,18 @@ function validateHostname(location) { config.mailFqdn(), config.appFqdn({ location: constants.POSTMAN_LOCATION, domain: config.fqdn() }) ]; - if (RESERVED_LOCATIONS.indexOf(location) !== -1) return new AppsError(AppsError.BAD_FIELD, location + ' is reserved'); + if (RESERVED_LOCATIONS.indexOf(hostname) !== -1) return new AppsError(AppsError.BAD_FIELD, hostname + ' is reserved'); // workaround https://github.com/oncletom/tld.js/issues/73 - var tmp = location.replace('_', '-'); - if (!tld.isValid(tmp)) return new AppsError(AppsError.BAD_FIELD, 'location is not a valid domain name'); + var tmp = hostname.replace('_', '-'); + if (!tld.isValid(tmp)) return new AppsError(AppsError.BAD_FIELD, 'Hostname is not a valid domain name'); - if (location.length > 253) return new AppsError(AppsError.BAD_FIELD, 'FQDN length exceeds 253 characters'); + if (hostname.length > 253) return new AppsError(AppsError.BAD_FIELD, 'Hostname length exceeds 253 characters'); + + if (tld.getSubdomain(tmp) === null) return new AppsError(AppsError.BAD_FIELD, 'Invalid subdomain'); + if (tld.getSubdomain(tmp).match(/^[A-Za-z0-9-]+$/) === null) return new AppsError(AppsError.BAD_FIELD, 'Subdomain can only contain alphanumerics and hyphen'); + if (tld.getSubdomain(tmp).length > 63) return new AppsError(AppsError.BAD_FIELD, 'Subdomain exceeds 63 characters'); + if (tld.getSubdomain(tmp).startsWith('-') || tld.getSubdomain(tmp).endsWith('-')) return new AppsError(AppsError.BAD_FIELD, 'Subdomain cannot start or end with hyphen'); return null; } diff --git a/src/test/apps-test.js b/src/test/apps-test.js index 6ba66d670..16b68f14f 100644 --- a/src/test/apps-test.js +++ b/src/test/apps-test.js @@ -12,6 +12,7 @@ var appdb = require('../appdb.js'), config = require('../config.js'), constants = require('../constants.js'), database = require('../database.js'), + domaindb = require('../domaindb.js'), expect = require('expect.js'), groupdb = require('../groupdb.js'), groups = require('../groups.js'), @@ -66,10 +67,23 @@ describe('Apps', function () { name: 'group1' }; + const DOMAIN_0 = { + domain: 'example.com', + zoneName: 'example.com', + config: { provider: 'manual' } + }; + + const DOMAIN_1 = { + domain: 'example2.com', + zoneName: 'example2.com', + config: { provider: 'manual' } + }; + var APP_0 = { id: 'appid-0', appStoreId: 'appStoreId-0', location: 'some-location-0', + domain: DOMAIN_0.domain, manifest: { version: '0.1', dockerImage: 'docker/app0', healthCheckPath: '/', httpPort: 80, title: 'app0', tcpPorts: { @@ -88,6 +102,7 @@ describe('Apps', function () { id: 'appid-1', appStoreId: 'appStoreId-1', location: 'some-location-1', + domain: DOMAIN_0.domain, manifest: { version: '0.1', dockerImage: 'docker/app1', healthCheckPath: '/', httpPort: 80, title: 'app1', tcpPorts: {} @@ -101,6 +116,7 @@ describe('Apps', function () { id: 'appid-2', appStoreId: 'appStoreId-2', location: 'some-location-2', + domain: DOMAIN_1.domain, manifest: { version: '0.1', dockerImage: 'docker/app2', healthCheckPath: '/', httpPort: 80, title: 'app2', tcpPorts: {} @@ -115,6 +131,8 @@ describe('Apps', function () { async.series([ database.initialize, database._clear, + domaindb.add.bind(null, DOMAIN_0.domain, DOMAIN_0.zoneName, DOMAIN_0.config), + domaindb.add.bind(null, DOMAIN_1.domain, DOMAIN_1.zoneName, DOMAIN_1.config), userdb.add.bind(null, ADMIN_0.id, ADMIN_0), userdb.add.bind(null, USER_0.id, USER_0), userdb.add.bind(null, USER_1.id, USER_1), @@ -122,9 +140,9 @@ describe('Apps', function () { groupdb.add.bind(null, GROUP_1.id, GROUP_1.name), groups.addMember.bind(null, constants.ADMIN_GROUP_ID, ADMIN_0.id), groups.addMember.bind(null, GROUP_0.id, USER_1.id), - appdb.add.bind(null, APP_0.id, APP_0.appStoreId, APP_0.manifest, APP_0.location, APP_0.portBindings, APP_0), - appdb.add.bind(null, APP_1.id, APP_1.appStoreId, APP_1.manifest, APP_1.location, APP_1.portBindings, APP_1), - appdb.add.bind(null, APP_2.id, APP_2.appStoreId, APP_2.manifest, APP_2.location, APP_2.portBindings, APP_2), + appdb.add.bind(null, APP_0.id, APP_0.appStoreId, APP_0.manifest, APP_0.location, APP_0.domain, APP_0.portBindings, APP_0), + appdb.add.bind(null, APP_1.id, APP_1.appStoreId, APP_1.manifest, APP_1.location, APP_1.domain, APP_1.portBindings, APP_1), + appdb.add.bind(null, APP_2.id, APP_2.appStoreId, APP_2.manifest, APP_2.location, APP_2.domain, APP_2.portBindings, APP_2), settingsdb.set.bind(null, settings.BACKUP_CONFIG_KEY, JSON.stringify({ provider: 'caas', token: 'BACKUP_TOKEN', bucket: 'Bucket', prefix: 'Prefix' })) ], done); }); @@ -135,35 +153,35 @@ describe('Apps', function () { describe('validateHostname', function () { it('does not allow admin subdomain', function () { - expect(apps._validateHostname('my', 'cloudron.us')).to.be.an(Error); + expect(apps._validateHostname('my.example.com')).to.be.an(Error); }); it('cannot have >63 length subdomains', function () { var s = ''; for (var i = 0; i < 64; i++) s += 's'; - expect(apps._validateHostname(s, 'cloudron.us')).to.be.an(Error); + expect(apps._validateHostname(s + '.example.com')).to.be.an(Error); }); it('allows only alphanumerics and hypen', function () { - expect(apps._validateHostname('#2r', 'cloudron.us')).to.be.an(Error); - expect(apps._validateHostname('a%b', 'cloudron.us')).to.be.an(Error); - expect(apps._validateHostname('ab_', 'cloudron.us')).to.be.an(Error); - expect(apps._validateHostname('a.b', 'cloudron.us')).to.be.an(Error); - expect(apps._validateHostname('-ab', 'cloudron.us')).to.be.an(Error); - expect(apps._validateHostname('ab-', 'cloudron.us')).to.be.an(Error); + expect(apps._validateHostname('#2r.example.com')).to.be.an(Error); + expect(apps._validateHostname('a%b.example.com')).to.be.an(Error); + expect(apps._validateHostname('ab_.example.com')).to.be.an(Error); + expect(apps._validateHostname('a.b.example.com')).to.be.an(Error); + expect(apps._validateHostname('-ab.example.com')).to.be.an(Error); + expect(apps._validateHostname('ab-.example.com')).to.be.an(Error); }); it('total length cannot exceed 255', function () { var s = ''; - for (var i = 0; i < (255 - 'cloudron.us'.length); i++) s += 's'; + for (var i = 0; i < (255 - 'example.com'.length); i++) s += 's'; - expect(apps._validateHostname(s, 'cloudron.us')).to.be.an(Error); + expect(apps._validateHostname(s + '.example.com')).to.be.an(Error); }); it('allow valid domains', function () { - expect(apps._validateHostname('a', 'cloudron.us')).to.be(null); - expect(apps._validateHostname('a0-x', 'cloudron.us')).to.be(null); - expect(apps._validateHostname('01', 'cloudron.us')).to.be(null); + expect(apps._validateHostname('a.example.com')).to.be(null); + expect(apps._validateHostname('a0-x.example.com')).to.be(null); + expect(apps._validateHostname('01.example.com')).to.be(null); }); });