'use strict'; var validator = new (require('jsonschema').Validator)(); var recase = require('recase').create({}); function deepCopy(obj) { if (!obj || typeof obj !== 'object') { return obj; } var result; if (Array.isArray(obj)) { result = []; } else { result = {}; } Object.keys(obj).forEach(function (key) { result[key] = deepCopy(obj[key]); }); return result; } var portSchema = { type: 'number', minimum: 1, maximum: 65535 }; var moduleSchemas = { // the proxy module is common to basically all categories. proxy: { type: 'object' , oneOf: [ { required: [ 'address' ] } , { required: [ 'port' ] } ] , properties: { address: { type: 'string' } , host: { type: 'string' } , port: portSchema } } // redirect and static modules are for HTTP , redirect: { type: 'object' , required: [ 'to', 'from' ] , properties: { to: { type: 'string'} , from: { type: 'string'} , status: { type: 'integer', minimum: 1, maximum: 999 } , } } , static: { type: 'object' , required: [ 'root' ] , properties: { root: { type: 'string' } } } // the acme module is for TLS , acme: { type: 'object' , required: [ 'email' ] , properties: { email: { type: 'string' } , server: { type: 'string' } , challengeType: { type: 'string' } } } }; // forward is basically the name for the TCP proxy moduleSchemas.forward = deepCopy(moduleSchemas.proxy); moduleSchemas.forward.required = [ 'ports' ]; moduleSchemas.forward.properties.ports = { type: 'array', items: portSchema }; Object.keys(moduleSchemas).forEach(function (name) { var schema = moduleSchemas[name]; schema.id = '/modules/'+name; schema.required = ['id', 'type'].concat(schema.required || []); schema.properties.id = { type: 'string' }; schema.properties.type = { type: 'string', const: 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 }} } } }; } var httpSchema = { type: 'object' , properties: { // These properties should be snake_case to match the API and config format primary_domain: { type: 'string' } , allow_insecure: { type: 'boolean' } , trust_proxy: { type: 'boolean' } , } }; addDomainsSchema(httpSchema, ['proxy', 'static', 'redirect']); var tlsSchema = { type: 'object' , properties: { acme: { type: 'object' // These properties should be snake_case to match the API and config format , required: [ 'email', 'approved_domains' ] , properties: { email: { type: 'string' } , server: { type: 'string' } , challenge_type: { type: 'string' } , approved_domains: { type: 'array', items: { type: 'string' }, minLength: 1} } } } }; 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' }} } }; var dnsSchema = { type: 'object' , properties: { bind: { type: 'array', items: portSchema } , modules: { type: 'array', items: { '$ref': '/modules/proxy' }} } }; var mdnsSchema = { type: 'object' , required: [ 'port', 'broadcast', 'ttl' ] , properties: { port: portSchema , broadcast: { type: 'string' } , ttl: { type: 'integer', minimum: 0, maximum: 2147483647 } } }; var ddnsSchema = { type: 'object' , properties: { enabled: { type: 'boolean' } } }; var socks5Schema = { type: 'object' , properties: { enabled: { type: 'boolean' } , port: portSchema } }; var mainSchema = { type: 'object' , required: [ 'http', 'tls', 'tcp', 'dns', 'mdns', 'ddns' ] , properties: { http: httpSchema , tls: tlsSchema , tcp: tcpSchema , dns: dnsSchema , mdns: mdnsSchema , ddns: ddnsSchema , socks5: socks5Schema } , additionalProperties: false }; module.exports.validate = function (config) { return validator.validate(recase.snakeCopy(config), mainSchema).errors; };