From 3663abd4cf8aba3676e8416e6bb3430fcb0acbd0 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Wed, 28 Feb 2018 14:47:57 +0000 Subject: [PATCH 1/5] adds config validation --- package.json | 5 ++++- src/core/index.js | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 00d430ceb5..373c7c71b2 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,8 @@ "./src/core/runtime/repo-nodejs.js": "./src/core/runtime/repo-browser.js", "./src/core/runtime/dns-nodejs.js": "./src/core/runtime/dns-browser.js", "./test/utils/create-repo-nodejs.js": "./test/utils/create-repo-browser.js", - "stream": "readable-stream" + "stream": "readable-stream", + "joi": "joi-browser" }, "engines": { "node": ">=6.0.0", @@ -119,6 +120,8 @@ "is-ipfs": "^0.3.2", "is-stream": "^1.1.0", "joi": "^13.1.2", + "joi-browser": "^13.0.1", + "joi-ipfs-config": "^1.0.2", "libp2p": "~0.18.0", "libp2p-circuit": "~0.1.4", "libp2p-floodsub": "~0.14.1", diff --git a/src/core/index.js b/src/core/index.js index f9912d25d4..005fe8d787 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -11,6 +11,7 @@ const CID = require('cids') const debug = require('debug') const extend = require('deep-extend') const EventEmitter = require('events') +const Joi = require('joi').extend(require('joi-ipfs-config')) const boot = require('./boot') const components = require('./components') @@ -27,7 +28,7 @@ class IPFS extends EventEmitter { EXPERIMENTAL: {} } - options = options || {} + options = Joi.attempt(options || {}, Joi.ipfsConfig()) this._libp2pModules = options.libp2p && options.libp2p.modules extend(this._options, options) From d077c574ff6998aacbd5ae38866ed32705aed42e Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Thu, 1 Mar 2018 09:12:54 +0000 Subject: [PATCH 2/5] adds schema to js-ipfs --- package.json | 2 +- src/core/config.js | 41 ++++++++ src/core/index.js | 4 +- test/core/config.spec.js | 220 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 264 insertions(+), 3 deletions(-) create mode 100644 src/core/config.js create mode 100644 test/core/config.spec.js diff --git a/package.json b/package.json index 373c7c71b2..04e8fb93fa 100644 --- a/package.json +++ b/package.json @@ -121,7 +121,7 @@ "is-stream": "^1.1.0", "joi": "^13.1.2", "joi-browser": "^13.0.1", - "joi-ipfs-config": "^1.0.2", + "joi-multiaddr": "^1.0.1", "libp2p": "~0.18.0", "libp2p-circuit": "~0.1.4", "libp2p-floodsub": "~0.14.1", diff --git a/src/core/config.js b/src/core/config.js new file mode 100644 index 0000000000..4717c5a58b --- /dev/null +++ b/src/core/config.js @@ -0,0 +1,41 @@ +const Joi = require('joi').extend(require('joi-multiaddr')) + +const schema = Joi.object().keys({ + repo: Joi.alternatives().try( + Joi.object(), // TODO: schema for IPFS repo + Joi.string() + ).allow(null), + init: Joi.alternatives().try( + Joi.boolean(), + Joi.object().keys({ bits: Joi.number().integer() }) + ).allow(null), + start: Joi.boolean(), + pass: Joi.string().allow(''), + EXPERIMENTAL: Joi.object().keys({ + pubsub: Joi.boolean(), + sharding: Joi.boolean(), + dht: Joi.boolean() + }).allow(null), + config: Joi.object().keys({ + Addresses: Joi.object().keys({ + Swarm: Joi.array().items(Joi.multiaddr().options({ convert: false })), + API: Joi.multiaddr().options({ convert: false }), + Gateway: Joi.multiaddr().options({ convert: false }) + }).allow(null), + Discovery: Joi.object().keys({ + MDNS: Joi.object().keys({ + Enabled: Joi.boolean(), + Interval: Joi.number().integer() + }).allow(null), + webRTCStar: Joi.object().keys({ + Enabled: Joi.boolean() + }).allow(null) + }).allow(null), + Bootstrap: Joi.array().items(Joi.multiaddr().IPFS().options({ convert: false })) + }).allow(null), + libp2p: Joi.object().keys({ + modules: Joi.object().allow(null) // TODO: schemas for libp2p modules? + }).allow(null) + }).options({ allowUnknown: true }) + +module.exports.validate = (config) => Joi.attempt(config, schema) diff --git a/src/core/index.js b/src/core/index.js index 005fe8d787..7e9c82a1ac 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -11,8 +11,8 @@ const CID = require('cids') const debug = require('debug') const extend = require('deep-extend') const EventEmitter = require('events') -const Joi = require('joi').extend(require('joi-ipfs-config')) +const Config = require('./config') const boot = require('./boot') const components = require('./components') // replaced by repo-browser when running in the browser @@ -28,7 +28,7 @@ class IPFS extends EventEmitter { EXPERIMENTAL: {} } - options = Joi.attempt(options || {}, Joi.ipfsConfig()) + options = Config.validate(options || {}) this._libp2pModules = options.libp2p && options.libp2p.modules extend(this._options, options) diff --git a/test/core/config.spec.js b/test/core/config.spec.js new file mode 100644 index 0000000000..e3f17d13fa --- /dev/null +++ b/test/core/config.spec.js @@ -0,0 +1,220 @@ +/* eslint-env mocha */ +'use strict' + +const chai = require('chai') +const dirtyChai = require('dirty-chai') +const expect = chai.expect +chai.use(dirtyChai) + +const Config = require('../../src/core/config') + +describe.only('config', () => { + it('should allow empty config', () => { + const config = {} + expect(() => Config.validate(config)).to.not.throw() + }) + + it('should allow undefined config', () => { + const config = undefined + expect(() => Config.validate(config)).to.not.throw() + }) + + it('should allow unknown key at root', () => { + const config = { [`${Date.now()}`]: 'test' } + expect(() => Config.validate(config)).to.not.throw() + }) + + it('should validate valid repo', () => { + const configs = [ + { repo: { unknown: 'value' } }, + { repo: '/path/to-repo' }, + { repo: null }, + { repo: undefined } + ] + + configs.forEach(c => expect(() => Config.validate(c)).to.not.throw()) + }) + + it('should validate invalid repo', () => { + const configs = [ + { repo: 138 } + ] + + configs.forEach(c => expect(() => Config.validate(c)).to.throw()) + }) + + it('should validate valid init', () => { + const configs = [ + { init: { bits: 138 } }, + { init: { bits: 138, unknown: 'value' } }, + { init: true }, + { init: false }, + { init: null }, + { init: undefined } + ] + + configs.forEach(c => expect(() => Config.validate(c)).to.not.throw()) + }) + + it('should validate invalid init', () => { + const configs = [ + { init: 138 }, + { init: { bits: 'not an int' } } + ] + + configs.forEach(c => expect(() => Config.validate(c)).to.throw()) + }) + + it('should validate valid start', () => { + const configs = [ + { start: true }, + { start: false }, + { start: undefined } + ] + + configs.forEach(c => expect(() => Config.validate(c)).to.not.throw()) + }) + + it('should validate invalid start', () => { + const configs = [ + { start: 138 }, + { start: 'make it so number 1' }, + { start: null } + ] + + configs.forEach(c => expect(() => Config.validate(c)).to.throw()) + }) + + it('should validate valid pass', () => { + const configs = [ + { pass: 'correctbatteryhorsestaple' }, + { pass: '' }, + { pass: undefined } + ] + + configs.forEach(c => expect(() => Config.validate(c)).to.not.throw()) + }) + + it('should validate invalid pass', () => { + const configs = [ + { pass: 138 }, + { pass: null } + ] + + configs.forEach(c => expect(() => Config.validate(c)).to.throw()) + }) + + it('should validate valid EXPERIMENTAL', () => { + const configs = [ + { EXPERIMENTAL: { pubsub: true, dht: true, sharding: true } }, + { EXPERIMENTAL: { pubsub: false, dht: false, sharding: false } }, + { EXPERIMENTAL: { unknown: 'value' } }, + { EXPERIMENTAL: null }, + { EXPERIMENTAL: undefined } + ] + + configs.forEach(c => expect(() => Config.validate(c)).to.not.throw()) + }) + + it('should validate invalid EXPERIMENTAL', () => { + const configs = [ + { EXPERIMENTAL: { pubsub: 138 } }, + { EXPERIMENTAL: { dht: 138 } }, + { EXPERIMENTAL: { sharding: 138 } } + ] + + configs.forEach(c => expect(() => Config.validate(c)).to.throw()) + }) + + it('should validate valid config', () => { + const configs = [ + { config: { Addresses: { Swarm: ['/ip4/0.0.0.0/tcp/4002'] } } }, + { config: { Addresses: { Swarm: [] } } }, + { config: { Addresses: { Swarm: undefined } } }, + + { config: { Addresses: { API: '/ip4/127.0.0.1/tcp/5002' } } }, + { config: { Addresses: { API: undefined } } }, + + { config: { Addresses: { Gateway: '/ip4/127.0.0.1/tcp/9090' } } }, + { config: { Addresses: { Gateway: undefined } } }, + + { config: { Addresses: { unknown: 'value' } } }, + { config: { Addresses: null } }, + { config: { Addresses: undefined } }, + + { config: { Discovery: { MDNS: { Enabled: true } } } }, + { config: { Discovery: { MDNS: { Enabled: false } } } }, + { config: { Discovery: { MDNS: { Interval: 138 } } } }, + { config: { Discovery: { MDNS: { unknown: 'value' } } } }, + { config: { Discovery: { MDNS: null } } }, + { config: { Discovery: { MDNS: undefined } } }, + + { config: { Discovery: { webRTCStar: { Enabled: true } } } }, + { config: { Discovery: { webRTCStar: { Enabled: false } } } }, + { config: { Discovery: { webRTCStar: { unknown: 'value' } } } }, + { config: { Discovery: { webRTCStar: null } } }, + { config: { Discovery: { webRTCStar: undefined } } }, + + { config: { Discovery: { unknown: 'value' } } }, + { config: { Discovery: null } }, + { config: { Discovery: undefined } }, + + { config: { Bootstrap: ['/ip4/104.236.176.52/tcp/4001/ipfs/QmSoLnSGccFuZQJzRadHn95W2CrSFmZuTdDWP8HXaHca9z'] } }, + { config: { Bootstrap: [] } }, + + { config: { unknown: 'value' } }, + { config: null }, + { config: undefined } + ] + + configs.forEach(c => expect(() => Config.validate(c)).to.not.throw()) + }) + + it('should validate invalid config', () => { + const configs = [ + { config: { Addresses: { Swarm: 138 } } }, + { config: { Addresses: { Swarm: null } } }, + + { config: { Addresses: { API: 138 } } }, + { config: { Addresses: { API: null } } }, + + { config: { Addresses: { Gateway: 138 } } }, + { config: { Addresses: { Gateway: null } } }, + + { config: { Discovery: { MDNS: { Enabled: 138 } } } }, + { config: { Discovery: { MDNS: { Interval: true } } } }, + + { config: { Discovery: { webRTCStar: { Enabled: 138 } } } }, + + { config: { Bootstrap: ['/ip4/0.0.0.0/tcp/4002'] } }, + { config: { Bootstrap: 138 } }, + + { config: 138 } + ] + + configs.forEach(c => expect(() => Config.validate(c)).to.throw()) + }) + + it('should validate valid libp2p', () => { + const configs = [ + { libp2p: { modules: {} } }, + { libp2p: { modules: { unknown: 'value' } } }, + { libp2p: { modules: null } }, + { libp2p: { modules: undefined } }, + { libp2p: { unknown: 'value' } }, + { libp2p: null }, + { libp2p: undefined } + ] + + configs.forEach(c => expect(() => Config.validate(c)).to.not.throw()) + }) + + it('should validate invalid libp2p', () => { + const configs = [ + { libp2p: { modules: 138 } }, + { libp2p: 138 } + ] + + configs.forEach(c => expect(() => Config.validate(c)).to.throw()) + }) +}) From 500b3bd24e9cb3acc861764fc6776a7a2d841b0d Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Thu, 1 Mar 2018 09:14:50 +0000 Subject: [PATCH 3/5] fixes linter warnings --- src/core/config.js | 68 ++++++++++++++++++++++++---------------------- 1 file changed, 35 insertions(+), 33 deletions(-) diff --git a/src/core/config.js b/src/core/config.js index 4717c5a58b..493a30e5eb 100644 --- a/src/core/config.js +++ b/src/core/config.js @@ -1,41 +1,43 @@ +'use strict' + const Joi = require('joi').extend(require('joi-multiaddr')) const schema = Joi.object().keys({ - repo: Joi.alternatives().try( - Joi.object(), // TODO: schema for IPFS repo - Joi.string() - ).allow(null), - init: Joi.alternatives().try( - Joi.boolean(), - Joi.object().keys({ bits: Joi.number().integer() }) - ).allow(null), - start: Joi.boolean(), - pass: Joi.string().allow(''), - EXPERIMENTAL: Joi.object().keys({ - pubsub: Joi.boolean(), - sharding: Joi.boolean(), - dht: Joi.boolean() + repo: Joi.alternatives().try( + Joi.object(), // TODO: schema for IPFS repo + Joi.string() + ).allow(null), + init: Joi.alternatives().try( + Joi.boolean(), + Joi.object().keys({ bits: Joi.number().integer() }) + ).allow(null), + start: Joi.boolean(), + pass: Joi.string().allow(''), + EXPERIMENTAL: Joi.object().keys({ + pubsub: Joi.boolean(), + sharding: Joi.boolean(), + dht: Joi.boolean() + }).allow(null), + config: Joi.object().keys({ + Addresses: Joi.object().keys({ + Swarm: Joi.array().items(Joi.multiaddr().options({ convert: false })), + API: Joi.multiaddr().options({ convert: false }), + Gateway: Joi.multiaddr().options({ convert: false }) }).allow(null), - config: Joi.object().keys({ - Addresses: Joi.object().keys({ - Swarm: Joi.array().items(Joi.multiaddr().options({ convert: false })), - API: Joi.multiaddr().options({ convert: false }), - Gateway: Joi.multiaddr().options({ convert: false }) - }).allow(null), - Discovery: Joi.object().keys({ - MDNS: Joi.object().keys({ - Enabled: Joi.boolean(), - Interval: Joi.number().integer() - }).allow(null), - webRTCStar: Joi.object().keys({ - Enabled: Joi.boolean() - }).allow(null) + Discovery: Joi.object().keys({ + MDNS: Joi.object().keys({ + Enabled: Joi.boolean(), + Interval: Joi.number().integer() }).allow(null), - Bootstrap: Joi.array().items(Joi.multiaddr().IPFS().options({ convert: false })) + webRTCStar: Joi.object().keys({ + Enabled: Joi.boolean() + }).allow(null) }).allow(null), - libp2p: Joi.object().keys({ - modules: Joi.object().allow(null) // TODO: schemas for libp2p modules? - }).allow(null) - }).options({ allowUnknown: true }) + Bootstrap: Joi.array().items(Joi.multiaddr().IPFS().options({ convert: false })) + }).allow(null), + libp2p: Joi.object().keys({ + modules: Joi.object().allow(null) // TODO: schemas for libp2p modules? + }).allow(null) +}).options({ allowUnknown: true }) module.exports.validate = (config) => Joi.attempt(config, schema) From 9159a31acee344654111b9c2c92f1c9f9b208ba8 Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Thu, 1 Mar 2018 09:16:04 +0000 Subject: [PATCH 4/5] removes .only --- test/core/config.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/core/config.spec.js b/test/core/config.spec.js index e3f17d13fa..315acb0e08 100644 --- a/test/core/config.spec.js +++ b/test/core/config.spec.js @@ -8,7 +8,7 @@ chai.use(dirtyChai) const Config = require('../../src/core/config') -describe.only('config', () => { +describe('config', () => { it('should allow empty config', () => { const config = {} expect(() => Config.validate(config)).to.not.throw() From 02920526b69cee4443a4d6d10ff71828864ae3ca Mon Sep 17 00:00:00 2001 From: Alan Shaw Date: Thu, 1 Mar 2018 13:47:35 +0000 Subject: [PATCH 5/5] renames Config -> config --- src/core/index.js | 4 +-- test/core/config.spec.js | 70 ++++++++++++++++++++-------------------- 2 files changed, 37 insertions(+), 37 deletions(-) diff --git a/src/core/index.js b/src/core/index.js index 7e9c82a1ac..d27d178f85 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -12,7 +12,7 @@ const debug = require('debug') const extend = require('deep-extend') const EventEmitter = require('events') -const Config = require('./config') +const config = require('./config') const boot = require('./boot') const components = require('./components') // replaced by repo-browser when running in the browser @@ -28,7 +28,7 @@ class IPFS extends EventEmitter { EXPERIMENTAL: {} } - options = Config.validate(options || {}) + options = config.validate(options || {}) this._libp2pModules = options.libp2p && options.libp2p.modules extend(this._options, options) diff --git a/test/core/config.spec.js b/test/core/config.spec.js index 315acb0e08..c90e9454c6 100644 --- a/test/core/config.spec.js +++ b/test/core/config.spec.js @@ -6,45 +6,45 @@ const dirtyChai = require('dirty-chai') const expect = chai.expect chai.use(dirtyChai) -const Config = require('../../src/core/config') +const config = require('../../src/core/config') describe('config', () => { it('should allow empty config', () => { - const config = {} - expect(() => Config.validate(config)).to.not.throw() + const cfg = {} + expect(() => config.validate(cfg)).to.not.throw() }) it('should allow undefined config', () => { - const config = undefined - expect(() => Config.validate(config)).to.not.throw() + const cfg = undefined + expect(() => config.validate(cfg)).to.not.throw() }) it('should allow unknown key at root', () => { - const config = { [`${Date.now()}`]: 'test' } - expect(() => Config.validate(config)).to.not.throw() + const cfg = { [`${Date.now()}`]: 'test' } + expect(() => config.validate(cfg)).to.not.throw() }) it('should validate valid repo', () => { - const configs = [ + const cfgs = [ { repo: { unknown: 'value' } }, { repo: '/path/to-repo' }, { repo: null }, { repo: undefined } ] - configs.forEach(c => expect(() => Config.validate(c)).to.not.throw()) + cfgs.forEach(cfg => expect(() => config.validate(cfg)).to.not.throw()) }) it('should validate invalid repo', () => { - const configs = [ + const cfgs = [ { repo: 138 } ] - configs.forEach(c => expect(() => Config.validate(c)).to.throw()) + cfgs.forEach(cfg => expect(() => config.validate(cfg)).to.throw()) }) it('should validate valid init', () => { - const configs = [ + const cfgs = [ { init: { bits: 138 } }, { init: { bits: 138, unknown: 'value' } }, { init: true }, @@ -53,59 +53,59 @@ describe('config', () => { { init: undefined } ] - configs.forEach(c => expect(() => Config.validate(c)).to.not.throw()) + cfgs.forEach(cfg => expect(() => config.validate(cfg)).to.not.throw()) }) it('should validate invalid init', () => { - const configs = [ + const cfgs = [ { init: 138 }, { init: { bits: 'not an int' } } ] - configs.forEach(c => expect(() => Config.validate(c)).to.throw()) + cfgs.forEach(cfg => expect(() => config.validate(cfg)).to.throw()) }) it('should validate valid start', () => { - const configs = [ + const cfgs = [ { start: true }, { start: false }, { start: undefined } ] - configs.forEach(c => expect(() => Config.validate(c)).to.not.throw()) + cfgs.forEach(cfg => expect(() => config.validate(cfg)).to.not.throw()) }) it('should validate invalid start', () => { - const configs = [ + const cfgs = [ { start: 138 }, { start: 'make it so number 1' }, { start: null } ] - configs.forEach(c => expect(() => Config.validate(c)).to.throw()) + cfgs.forEach(cfg => expect(() => config.validate(cfg)).to.throw()) }) it('should validate valid pass', () => { - const configs = [ + const cfgs = [ { pass: 'correctbatteryhorsestaple' }, { pass: '' }, { pass: undefined } ] - configs.forEach(c => expect(() => Config.validate(c)).to.not.throw()) + cfgs.forEach(cfg => expect(() => config.validate(cfg)).to.not.throw()) }) it('should validate invalid pass', () => { - const configs = [ + const cfgs = [ { pass: 138 }, { pass: null } ] - configs.forEach(c => expect(() => Config.validate(c)).to.throw()) + cfgs.forEach(cfg => expect(() => config.validate(cfg)).to.throw()) }) it('should validate valid EXPERIMENTAL', () => { - const configs = [ + const cfgs = [ { EXPERIMENTAL: { pubsub: true, dht: true, sharding: true } }, { EXPERIMENTAL: { pubsub: false, dht: false, sharding: false } }, { EXPERIMENTAL: { unknown: 'value' } }, @@ -113,21 +113,21 @@ describe('config', () => { { EXPERIMENTAL: undefined } ] - configs.forEach(c => expect(() => Config.validate(c)).to.not.throw()) + cfgs.forEach(cfg => expect(() => config.validate(cfg)).to.not.throw()) }) it('should validate invalid EXPERIMENTAL', () => { - const configs = [ + const cfgs = [ { EXPERIMENTAL: { pubsub: 138 } }, { EXPERIMENTAL: { dht: 138 } }, { EXPERIMENTAL: { sharding: 138 } } ] - configs.forEach(c => expect(() => Config.validate(c)).to.throw()) + cfgs.forEach(cfg => expect(() => config.validate(cfg)).to.throw()) }) it('should validate valid config', () => { - const configs = [ + const cfgs = [ { config: { Addresses: { Swarm: ['/ip4/0.0.0.0/tcp/4002'] } } }, { config: { Addresses: { Swarm: [] } } }, { config: { Addresses: { Swarm: undefined } } }, @@ -167,11 +167,11 @@ describe('config', () => { { config: undefined } ] - configs.forEach(c => expect(() => Config.validate(c)).to.not.throw()) + cfgs.forEach(cfg => expect(() => config.validate(cfg)).to.not.throw()) }) it('should validate invalid config', () => { - const configs = [ + const cfgs = [ { config: { Addresses: { Swarm: 138 } } }, { config: { Addresses: { Swarm: null } } }, @@ -192,11 +192,11 @@ describe('config', () => { { config: 138 } ] - configs.forEach(c => expect(() => Config.validate(c)).to.throw()) + cfgs.forEach(cfg => expect(() => config.validate(cfg)).to.throw()) }) it('should validate valid libp2p', () => { - const configs = [ + const cfgs = [ { libp2p: { modules: {} } }, { libp2p: { modules: { unknown: 'value' } } }, { libp2p: { modules: null } }, @@ -206,15 +206,15 @@ describe('config', () => { { libp2p: undefined } ] - configs.forEach(c => expect(() => Config.validate(c)).to.not.throw()) + cfgs.forEach(cfg => expect(() => config.validate(cfg)).to.not.throw()) }) it('should validate invalid libp2p', () => { - const configs = [ + const cfgs = [ { libp2p: { modules: 138 } }, { libp2p: 138 } ] - configs.forEach(c => expect(() => Config.validate(c)).to.throw()) + cfgs.forEach(cfg => expect(() => config.validate(cfg)).to.throw()) }) })