From 79ef9694b7fbcdce0891610b8c95001b4ca53b33 Mon Sep 17 00:00:00 2001 From: tigerbot Date: Wed, 11 Oct 2017 12:18:01 -0600 Subject: [PATCH] updated API to reflect moved domains --- lib/admin/apis.js | 91 ++++++++++++++------------- lib/admin/config.js | 147 ++++++++++++++++++++++---------------------- 2 files changed, 123 insertions(+), 115 deletions(-) diff --git a/lib/admin/apis.js b/lib/admin/apis.js index 2d817f9..8eb82be 100644 --- a/lib/admin/apis.js +++ b/lib/admin/apis.js @@ -380,21 +380,21 @@ module.exports.create = function (deps, conf) { var config = { restful: {} }; config.restful.readConfig = function (req, res, next) { - var part = conf; + var part = new (require('./config').ConfigChanger)(conf); if (req.params.group) { part = part[req.params.group]; } - if (part && req.params.name) { - part = part[req.params.name]; + if (part && req.params.domId) { + part = part.domains.find(req.params.domId); } - if (part && req.params.id) { - part = part.find(function (mod) { return mod.id === req.params.id; }); + if (part && req.params.mod) { + part = part[req.params.mod]; } - if (part && req.params.name2) { - part = part[req.params.name2]; + if (part && req.params.modGrp) { + part = part[req.params.modGrp]; } - if (part && req.params.id2) { - part = part.find(function (mod) { return mod.id === req.params.id2; }); + if (part && req.params.modId) { + part = part.find(req.params.modId); } if (part) { @@ -439,27 +439,35 @@ module.exports.create = function (deps, conf) { var err; deps.PromiseA.resolve().then(function () { var changer = new (require('./config').ConfigChanger)(conf); - if (!changer[group] || !changer[group].modules) { - err = new Error("'"+group+"' is not a valid settings group or has not modules"); - err.statusCode = 404; - throw err; - } - var modList; - if (req.params.id) { - if (changer[group].domains) { - modList = (changer[group].domains.find(req.params.id) || {}).modules; + if (req.params.domId) { + var dom = changer.domains.find(req.params.domId); + if (!dom) { + err = new Error("no domain with ID '"+req.params.domId+"'"); + } else if (!dom.modules[group]) { + err = new Error("domains don't contain '"+group+"' modules"); + } else { + modList = dom.modules[group]; } } else { - modList = changer[group].modules; + if (!changer[group] || !changer[group].modules) { + err = new Error("'"+group+"' is not a valid settings group or doesn't support modules"); + } else { + modList = changer[group].modules; + } } - if (!modList) { - err = new Error("'"+group+"' has no domains list or '"+req.params.id+"' does not exist"); + + if (err) { err.statusCode = 404; throw err; } - modList.add(req.body); + var update = req.body; + if (!Array.isArray(update)) { + update = [ update ]; + } + update.forEach(modList.add, modList); + var errors = changer.validate(); if (errors.length) { throw Object.assign(new Error(), errors[0], {statusCode: 400}); @@ -467,13 +475,13 @@ module.exports.create = function (deps, conf) { return deps.storage.config.save(changer); }).then(function (config) { - var base; - if (!req.params.id) { - base = config[group]; + var result; + if (!req.params.domId) { + result = config[group].modules; } else { - base = config[group].domains.find(function (dom) { return dom.id === req.params.id; }); + result = config.domains.find(function (dom) { return dom.id === req.params.domId; }).modules[group]; } - res.send(deps.recase.snakeCopy(base.modules)); + res.send(deps.recase.snakeCopy(result)); }, function (err) { res.statusCode = err.statusCode || 500; err.message = err.message || err.toString(); @@ -481,17 +489,15 @@ module.exports.create = function (deps, conf) { }); }; config.restful.createDomain = function (req, res) { - var group = req.params.group; - var err; deps.PromiseA.resolve().then(function () { var changer = new (require('./config').ConfigChanger)(conf); - if (!changer[group] || !changer[group].domains) { - err = new Error("'"+group+"' is not a valid settings group or has no domains list"); - err.statusCode = 404; - throw err; - } - changer[group].domains.add(req.body); + var update = req.body; + if (!Array.isArray(update)) { + update = [ update ]; + } + update.forEach(changer.domains.add, changer.domains); + var errors = changer.validate(); if (errors.length) { throw Object.assign(new Error(), errors[0], {statusCode: 400}); @@ -499,7 +505,7 @@ module.exports.create = function (deps, conf) { return deps.storage.config.save(changer); }).then(function (config) { - res.send(deps.recase.snakeCopy(config[group].domains)); + res.send(deps.recase.snakeCopy(config.domains)); }, function (err) { res.statusCode = err.statusCode || 500; err.message = err.message || err.toString(); @@ -518,14 +524,15 @@ module.exports.create = function (deps, conf) { app.use( '/config', makeCorsHandler()); app.get( '/config', config.restful.readConfig); app.get( '/config/:group', config.restful.readConfig); - app.get( '/config/:group/:name(modules|domains)/:id?', config.restful.readConfig); - app.get( '/config/:group/:name(domains)/:id/:name2(modules)/:id2?', config.restful.readConfig); + app.get( '/config/:group/:mod(modules)/:modId?', config.restful.readConfig); + app.get( '/config/domains/:domId/:mod(modules)?', config.restful.readConfig); + app.get( '/config/domains/:domId/:mod(modules)/:modGrp/:modId?', config.restful.readConfig); app.post( '/config', config.restful.saveBaseConfig); - app.post( '/config/:group', config.restful.saveBaseConfig); - app.post( '/config/:group/modules', config.restful.createModule); - app.post( '/config/:group/domains', config.restful.createDomain); - app.post( '/config/:group/domains/:id/modules', config.restful.createModule); + app.post( '/config/:group(?!domains)', config.restful.saveBaseConfig); + app.post( '/config/:group(?!domains)/modules', config.restful.createModule); + app.post( '/config/domains', config.restful.createDomain); + app.post( '/config/domains/:domId/modules/:group',config.restful.createModule); return app; }; diff --git a/lib/admin/config.js b/lib/admin/config.js index e45a3c2..0cf8646 100644 --- a/lib/admin/config.js +++ b/lib/admin/config.js @@ -63,54 +63,64 @@ Object.keys(moduleSchemas).forEach(function (name) { validator.addSchema(schema, schema.id); }); -function addDomainsSchema(base, modList) { - var modSchemas = modList.map(function (name) { - return { '$ref': '/modules/'+name }; - }); - - base.required = [ 'modules', 'domains' ].concat(base.required || []); - base.properties.modules = { - type: 'array' - , items: { - type: 'object' - , required: [ 'domains' ] - , properties: { - domains: { type: 'array', items: { type: 'string' }, minLength: 1} - } - , oneOf: modSchemas - } - }; - base.properties.domains = { - type: 'array' - , items: { - type: 'object' - , required: [ 'id', 'names', ] - , properties: { - id: { type: 'string' } - , names: { type: 'array', items: { type: 'string' }, minLength: 1} - , modules: { type: 'array', items: { oneOf: modSchemas }} - } - } - }; +function toSchemaRef(name) { + return { '$ref': '/modules/'+name }; } +var moduleRefs = { + http: [ 'proxy', 'static', 'redirect' ].map(toSchemaRef) +, tls: [ 'proxy', 'acme' ].map(toSchemaRef) +, tcp: [ 'forward' ].map(toSchemaRef) +, udp: [ 'proxy' ].map(toSchemaRef) +}; + +function addDomainRequirement(itemSchema) { + itemSchema.required = (itemSchema.required || []).concat('domains'); + itemSchema.properties = itemSchema.properties || {}; + itemSchema.domains = { type: 'array', items: { type: 'string' }, minLength: 1}; + return itemSchema; +} + +var domainSchema = { + type: 'array' +, items: { + type: 'object' + , properties: { + id: { type: 'string' } + , names: { type: 'array', items: { type: 'string' }, minLength: 1} + , modules: { + type: 'object' + , properties: { + tls: { type: 'array', items: { oneOf: moduleRefs.tls }} + , http: { type: 'array', items: { oneOf: moduleRefs.http }} + } + , additionalProperties: false + } + } + } +}; var httpSchema = { type: 'object' , properties: { + modules: { type: 'array', items: addDomainRequirement({ oneOf: moduleRefs.http }) } + // These properties should be snake_case to match the API and config format - primary_domain: { type: 'string' } + , primary_domain: { type: 'string' } , allow_insecure: { type: 'boolean' } , trust_proxy: { type: 'boolean' } - , bind: { not: {} } // this is a forbidden deprecated setting. + // these are forbidden deprecated settings. + , bind: { not: {} } + , domains: { not: {} } } }; -addDomainsSchema(httpSchema, ['proxy', 'static', 'redirect']); var tlsSchema = { type: 'object' , properties: { - acme: { + modules: { type: 'array', items: addDomainRequirement({ oneOf: moduleRefs.tls }) } + + , acme: { type: 'object' // These properties should be snake_case to match the API and config format , required: [ 'email', 'approved_domains' ] @@ -120,19 +130,20 @@ var tlsSchema = { , challenge_type: { type: 'string' } , approved_domains: { type: 'array', items: { type: 'string' }, minLength: 1} - , bind: { not: {} } // this is a forbidden deprecated setting. + // these are forbidden deprecated settings. + , bind: { not: {} } + , domains: { not: {} } } } } }; -addDomainsSchema(tlsSchema, ['proxy', 'acme']); var tcpSchema = { type: 'object' , required: [ 'bind' ] , properties: { bind: { type: 'array', items: portSchema, minLength: 1 } - , modules: { type: 'array', items: { '$ref': '/modules/forward' }} + , modules: { type: 'array', items: { oneOf: moduleRefs.tcp }} } }; @@ -140,7 +151,7 @@ var udpSchema = { type: 'object' , properties: { bind: { type: 'array', items: portSchema } - , modules: { type: 'array', items: { '$ref': '/modules/proxy' }} + , modules: { type: 'array', items: { oneOf: moduleRefs.udp }} } }; @@ -176,9 +187,10 @@ var deviceSchema = { var mainSchema = { type: 'object' -, required: [ 'http', 'tls', 'tcp', 'udp', 'mdns', 'ddns' ] +, required: [ 'domains', 'http', 'tls', 'tcp', 'udp', 'mdns', 'ddns' ] , properties: { - http: httpSchema + domains:domainSchema + , http: httpSchema , tls: tlsSchema , tcp: tcpSchema , udp: udpSchema @@ -228,7 +240,10 @@ class DomainList extends Array { Object.assign(this, JSON.parse(JSON.stringify(rawList))); } this.forEach(function (dom) { - dom.modules = new ModuleList(dom.modules); + dom.modules = { + http: new ModuleList((dom.modules || {}).http), + tls: new ModuleList((dom.modules || {}).tls), + }; }); } @@ -245,15 +260,19 @@ class DomainList extends Array { throw new Error("all domain names must be strings"); } - var modList = new ModuleList(); - if (Array.isArray(dom.modules)) { - dom.modules.forEach(function (mod) { - modList.add(mod); - }); + var modLists = { + http: new ModuleList(), + tls: new ModuleList() + }; + if (dom.modules && Array.isArray(dom.modules.http)) { + dom.modules.http.forEach(modLists.http.add, modLists.http); + } + if (dom.modules && Array.isArray(dom.modules.tls)) { + dom.modules.tls.forEach(modLists.tls.add, modLists.tls); } dom.id = require('crypto').randomBytes(4).toString('hex'); - dom.modules = modList; + dom.modules = modLists; this.push(dom); } } @@ -263,10 +282,9 @@ class ConfigChanger { Object.assign(this, JSON.parse(JSON.stringify(start))); delete this.device; + this.domains = new DomainList(this.domains); this.http.modules = new ModuleList(this.http.modules); - this.http.domains = new DomainList(this.http.domains); this.tls.modules = new ModuleList(this.tls.modules); - this.tls.domains = new DomainList(this.tls.domains); this.tcp.modules = new ModuleList(this.tcp.modules); this.udp.modules = new ModuleList(this.udp.modules); } @@ -274,32 +292,15 @@ class ConfigChanger { update(update) { var self = this; - if (update.http && update.http.modules) { - update.http.modules.forEach(self.http.modules.add.bind(self.http.modules)); - delete update.http.modules; - } - if (update.http && update.http.domains) { - update.http.domains.forEach(self.http.domains.add.bind(self.http.domains)); - delete update.http.domains; - } - - if (update.tls && update.tls.modules) { - update.tls.modules.forEach(self.tls.modules.add.bind(self.tls.modules)); - delete update.tls.modules; - } - if (update.tls && update.tls.domains) { - update.tls.domains.forEach(self.tls.domains.add.bind(self.tls.domains)); - delete update.tls.domains; - } - - if (update.tcp && update.tcp.modules) { - update.tcp.modules.forEach(self.tcp.modules.add.bind(self.tcp.modules)); - delete update.tcp.modules; - } - if (update.udp && update.udp.modules) { - update.udp.modules.forEach(self.udp.modules.add.bind(self.udp.modules)); - delete update.udp.modules; + if (update.domains) { + update.domains.forEach(self.domains.add, self.domains); } + [ 'http', 'tls', 'tcp', 'udp' ].forEach(function (name) { + if (update[name] && update[name].modules) { + update[name].modules.forEach(self[name].modules.add, self[name].modules); + delete update[name].modules; + } + }); function mergeSettings(orig, changes) { Object.keys(changes).forEach(function (key) {