From 927656e6ca94e28ba8534a3547a69dfe4b9e5147 Mon Sep 17 00:00:00 2001 From: mixmix Date: Fri, 8 Dec 2023 13:12:08 +1300 Subject: [PATCH 1/3] tweak api, add linting --- README.md | 2 +- index.js | 72 +++++++++++++++--------------- package.json | 13 +++--- test/core-tests.js | 89 ++++++++++++++++++++++---------------- test/db2.test.js | 8 ++-- test/install-order.test.js | 2 +- 6 files changed, 104 insertions(+), 82 deletions(-) diff --git a/README.md b/README.md index 49182f8..3351766 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ explictly public: ```js const explicitPublicMsg = { content: { type: 'profile' }, - options: { allowPublic: true } + allowPublic: true } server.publish(explicitPublicMsg, (err, msg) => { diff --git a/index.js b/index.js index 8fe546f..cbd4150 100644 --- a/index.js +++ b/index.js @@ -1,5 +1,13 @@ +/* eslint-disable brace-style */ const get = require('lodash.get') + const isString = (t) => (typeof t === 'string') +const NotBothError = () => new Error( + 'recps-guard: should not have recps && allowPublic, check your code' +) +const NotAllowedTypeError = (type) => new Error( + `recps-guard: public messages of type "${type}" not allowed` +) module.exports = { name: 'recpsGuard', @@ -9,61 +17,57 @@ module.exports = { }, init (ssb, config) { const allowedTypes = getAllowedTypes(ssb, config) + const isAllowedType = (type) => allowedTypes.has(type) - const publishHook = (publish, args) => { + function publishHook (publish, args) { const [input, cb] = args - if (get(input, ['options', 'allowPublic']) === true) { - // allowPublic and has recps, disallowed - if (hasRecps(input.content)) { - return cb(new Error('recps-guard: should not have recps && allowPublic, check your code')) - } + const isExplictAllow = ( + input.allowPublic === true || + get(input, ['options', 'allowPublic']) === true // legacy support + ) + + if (isExplictAllow) { + const content = input.content - // allowPublic and no recps, allowed - return publish(input.content, cb) - } else { - // without allowPublic, content isn't nested with db1 publish + if (hasRecps(content)) cb(NotBothError()) + else publish(content, cb) + } + else { const content = input - // no allowPublic and has recps/can publish without recps, allowed - if ( - isString(content) || + const isAllowed = ( + isString(content) || // already encrypted hasRecps(content) || - allowedTypes.has(content.type) - ) return publish(content, cb) + isAllowedType(content.type) + ) - // no allowPublic and no recps, disallowed - return cb(new Error(`recps-guard: public messages of type "${content.type}" not allowed`)) + if (isAllowed) publish(content, cb) + else cb(NotAllowedTypeError(content.type)) } } - const createHook = (create, args) => { + function createHook (create, args) { const [input, cb] = args if (input.allowPublic === true) { - // allowPublic and has recps, disallowed - if (hasRecps(input.content)) { - return cb(new Error('recps-guard: should not have recps && allowPublic, check your code')) - } + if (hasRecps(input.content)) return cb(NotBothError()) - // allowPublic and no recps, allowed return create(input, cb) - } else { - // without allowPublic, content isn't nested with db1 publish + } + else { const content = input.content - // no allowPublic and has recps/can publish without recps, allowed - if ( - input.encryptionFormat || - isString(content) || + const isAllowed = ( + isString(content) || // already encrypted + input.encryptionFormat || // signed up for encryption hasRecps(content) || - allowedTypes.has(content.type) - ) return create(input, cb) + isAllowedType(content.type) + ) - // no allowPublic and no recps, disallowed - return cb(new Error(`recps-guard: public messages of type "${content.type}" not allowed`)) + if (isAllowed) create(input, cb) + else cb(NotAllowedTypeError(content.type)) } - } if (ssb.publish) { diff --git a/package.json b/package.json index 2234e53..4b499df 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,9 @@ "description": "guards against unencrypted messages being accidentally published!", "main": "index.js", "scripts": { - "test": "tape test/**/*.test.js | tap-spec" + "test": "npm run test:js && npm run lint", + "test:js": "tape test/**/*.test.js | tap-arc", + "lint": "standard --fix" }, "repository": { "type": "git", @@ -21,6 +23,9 @@ "url": "https://github.com/ssbc/ssb-recps-guard/issues" }, "homepage": "https://github.com/ssbc/ssb-recps-guard#readme", + "dependencies": { + "lodash.get": "^4.4.2" + }, "devDependencies": { "scuttle-testbot": "^2.2.0", "ssb-box": "^1.0.1", @@ -30,10 +35,8 @@ "ssb-db2": "^8.1.0", "ssb-private1": "^1.0.1", "ssb-tribes": "^4.0.0", - "tap-spec": "^5.0.0", + "standard": "^17.1.0", + "tap-arc": "^1.2.2", "tape": "^5.7.2" - }, - "dependencies": { - "lodash.get": "^4.4.2" } } diff --git a/test/core-tests.js b/test/core-tests.js index 887c9d8..692ab0f 100644 --- a/test/core-tests.js +++ b/test/core-tests.js @@ -1,39 +1,54 @@ -module.exports = (server, t, cb) => { - const msg = { type: 'profile' } - server.publish(msg, (err, data) => { - t.match(err.message, /recps-guard: public messages of type "profile" not allowed/, 'public blocked') - - const msg = { type: 'profile', recps: [server.id] } - server.publish(msg, (err, data) => { - t.error(err, 'msgs with recps allowed') - t.equal(typeof data.value.content, 'string', '(msg content encrypted)') - - const msg = Buffer.from('cats are cool').toString('base64') + '.box7' - server.publish(msg, (err, data) => { - t.error(err, 'pre-encrypted content published fine') - t.equal(typeof data.value.content, 'string', '(msg content encrypted)') - - const content = { type: 'profile', name: 'mix' } - server.publish({ content, options: { allowPublic: true } }, (err, data) => { - if (err) return cb(err) - t.error(err, 'msgs { content, options: { allowPublic: true } allowed') - t.deepEqual(data.value.content, content, '(msg content unencrypted, allowPublic pruned)') - - const weird = { - content: { type: 'profile', recps: [server.id] }, - options: { allowPublic: true } - } - server.publish(weird, (err, data) => { - t.match( - err.message, - /recps-guard: should not have recps && allowPublic, check your code/, - 'disallow recps AND allowPublic' - ) - - cb(null) - }) - }) - }) +const { promisify: p } = require('util') + +module.exports = async (server, t, cb) => { + let description, content, input + + description = 'public blocked' + content = { type: 'profile' } + await p(server.publish)(content) + .then(() => t.fail(description)) + .catch(err => { + t.match(err.message, /recps-guard: public messages of type "profile" not allowed/, description) }) - }) + + description = 'msgs with recps allowed' + content = { type: 'profile', recps: [server.id] } + await p(server.publish)(content) + .then(data => t.equal(typeof data.value.content, 'string', description)) + .catch(err => t.error(err, description)) + + description = 'pre-encrypted content published fine' + content = Buffer.from('cats are cool').toString('base64') + '.box7' + await p(server.publish)(content) + .then(data => t.equal(typeof data.value.content, 'string', description)) + .catch(err => t.fail(err, description)) + + description = 'msgs { content, allowPublic: true } allowed' + content = { type: 'profile', name: 'mix' } + input = { content, allowPublic: true } + await p(server.publish)(input) + .then(data => t.deepEqual(data.value.content, content, description)) + .catch(err => t.fail(err, description)) + + description = 'legacy: msgs { content, options: { allowPublic: true } }' + content = { type: 'profile', name: 'mix' } + input = { content, options: { allowPublic: true } } + await p(server.publish)(input) + .then(data => t.deepEqual(data.value.content, content, description)) + .catch(err => t.fail(err, description)) + + description = 'disallow recps AND allowPublic' + input = { + content: { type: 'profile', recps: [server.id] }, + allowPublic: true + } + await p(server.publish)(input) + .then(() => t.fail(description)) + .catch(err => t.match( + err.message, + /recps-guard: should not have recps && allowPublic, check your code/, + 'disallow recps AND allowPublic' + )) + + cb(null) } diff --git a/test/db2.test.js b/test/db2.test.js index 16fa06a..46486ea 100644 --- a/test/db2.test.js +++ b/test/db2.test.js @@ -3,11 +3,11 @@ const test = require('tape') const Server = require('./test-bot') test('db2', async t => { - const server = Server({db1: false}) + const server = Server({ db1: false }) t.deepEqual(server.recpsGuard.allowedTypes(), [], 'recps.allowedTypes') let content = { type: 'profile' } - await p(server.db.create)({content}) + await p(server.db.create)({ content }) .then(msg => t.error(msg, "shouldn't get msg on err")) .catch((err) => { t.match(err.message, /recps-guard: public messages of type "profile" not allowed/, 'public blocked') @@ -19,7 +19,7 @@ test('db2', async t => { }) content = { type: 'profile', recps: [server.id] } - await p(server.db.create)({content}) + await p(server.db.create)({ content }) .then(data => { t.equal(typeof data.value.content, 'string', '(msg content encrypted)') }) @@ -55,7 +55,7 @@ test('db2', async t => { }) test('can create a group', async t => { - const server = Server({db1: false}) + const server = Server({ db1: false }) const group = await p(server.tribes.create)({}) t.equal(typeof group.groupId, 'string', 'created group with groupId') diff --git a/test/install-order.test.js b/test/install-order.test.js index 566ce3c..bd9ece6 100644 --- a/test/install-order.test.js +++ b/test/install-order.test.js @@ -40,7 +40,7 @@ test('installed in right order', t => { test('installed in wrong order', { skip: true }, t => { t.plan(2) // goodHook + throw - var server + let server t.throws( () => { server = Server // eslint-disable-line From 2731cf14bf825a6ea59e36bc380666aee6874aa6 Mon Sep 17 00:00:00 2001 From: mixmix Date: Mon, 11 Dec 2023 10:49:52 +1300 Subject: [PATCH 2/3] update examples to be db2-first --- README.md | 59 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 33 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 3351766..859eaff 100644 --- a/README.md +++ b/README.md @@ -13,8 +13,7 @@ const caps = require('ssb-caps') const stack = Stack({ caps }) - .use(require('ssb-db')) // << required - .use(require('ssb-profile')) + .use(require('ssb-d2')) .use(require('ssb-recps-guard')) // << must be last const config = { @@ -28,9 +27,11 @@ const sever = stack(config) auto-blocked: ```js -const unallowedMsg = { type: 'profile' } +const unallowedMsg = { + content: { type: 'profile' } +} -server.publish(unallowedMsg, (err, msg) => { +server.db.create(unallowedMsg, (err, msg) => { console.log(err) // => Error: recps-guard - no accidental public messages allowed! }) @@ -38,10 +39,12 @@ server.publish(unallowedMsg, (err, msg) => { config-allowed: ```js -const allowedMsg = { type: 'contact' } +const allowedMsg = { + content: { type: 'contact' } +} // this type was allowed in our config (see above) -server.publish(allowedType, (err, msg) => { +server.db.create(allowedType, (err, msg) => { console.log(msg.value.content) // => { type: 'contact' } }) @@ -54,35 +57,23 @@ const explicitPublicMsg = { allowPublic: true } -server.publish(explicitPublicMsg, (err, msg) => { - console.log(msg.value.content) - // => { type: 'profile' } - - // NOTE: only `content` is published -}) - -// with ssb-db2's ssb.db.create, note different option format! -const explicitPublicMsgDb2 = { - content: { type: 'profile' }, - allowPublic: true -} - -server.db.create(explicitPublicMsgDb2, (err, msg) => { +server.db.create(explicitPublicMsg, (err, msg) => { console.log(msg.value.content) // => { type: 'profile' } - - // NOTE: only `content` is published (as usual) }) ``` + private: ```js const privateMsg = { - type: 'profile' - recps: ['@ye+QM09iPcDJD6YvQYjoQc7sLF/IFhmNbEqgdzQo3lQ=.ed25519'] + content: { + type: 'profile' + recps: ['@ye+QM09iPcDJD6YvQYjoQc7sLF/IFhmNbEqgdzQo3lQ=.ed25519'] + } } -server.publish(privateMsg, (err, msg) => { +server.db.create(privateMsg, (err, msg) => { console.log(msg.value.content) // => VayTFa.....yZ3Wqsg==.box @@ -91,6 +82,21 @@ server.publish(privateMsg, (err, msg) => { }) ``` +NOTE that if you are using _classic_ `ssb-db`, the API behaves the same: + +```js +const explicitPublicMsgDB1 = { + content: { type: 'profile' }, + allowPublic: true +} + +server.db.create(explicitPublicMsgDN!, (err, msg) => { + console.log(msg.value.content) + // => { type: 'profile' } +}) +``` + + ## Installation Because `ssb-recps-guard` hooks the publish method you **must install it as the LAST plugin** @@ -118,7 +124,7 @@ where `allowedTypes` is an Array of message types which are allowed to be publis ## Explicit bypass Messages which would normally be blocked by the guard bypass the guard by changing what's passed to the -publish method to be of form `{ content, options: { allowPublic: true } }` +publish method to be of form `{ content, allowPublic: true }` The `content` is what will be passed to the normal publish function. @@ -126,6 +132,7 @@ Design: this is deliberately verbose to avoid accidental publishing. It also has the benefit that if `ssb-guard-recps` isn't installed this publish will error because publish will expect the `type` to be in a different place. + ## API You can check if `ssb-recps-guard` is installed in your server by looking to From 1d398935dec5091a62eac3c6945541b2c2127eb3 Mon Sep 17 00:00:00 2001 From: mixmix Date: Mon, 11 Dec 2023 10:56:41 +1300 Subject: [PATCH 3/3] console.trace deprecated usage --- index.js | 14 ++++++++++---- test/db2.test.js | 2 +- test/install-order.test.js | 2 +- test/not-installed.test.js | 2 +- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/index.js b/index.js index cbd4150..05fd90b 100644 --- a/index.js +++ b/index.js @@ -9,6 +9,8 @@ const NotAllowedTypeError = (type) => new Error( `recps-guard: public messages of type "${type}" not allowed` ) +let warnings = 0 + module.exports = { name: 'recpsGuard', version: require('./package.json').version, @@ -22,10 +24,14 @@ module.exports = { function publishHook (publish, args) { const [input, cb] = args - const isExplictAllow = ( - input.allowPublic === true || - get(input, ['options', 'allowPublic']) === true // legacy support - ) + const modernAllow = input.allowPublic === true + const legacyAllow = get(input, ['options', 'allowPublic']) === true + if (legacyAllow && warnings < 5) { + console.trace('input.options.allowPublic is deprecated, please use input.allowPublic') + warnings++ + } + + const isExplictAllow = (modernAllow || legacyAllow) if (isExplictAllow) { const content = input.content diff --git a/test/db2.test.js b/test/db2.test.js index 46486ea..b4b7eae 100644 --- a/test/db2.test.js +++ b/test/db2.test.js @@ -33,7 +33,7 @@ test('db2', async t => { t.deepEqual(data.value.content, content, '(msg content unencrypted, allowPublic pruned). db.create') }) .catch(err => { - t.error(err, 'msgs { content, options: { allowPublic: true } allowed. db.create') + t.error(err, 'msgs { content, { allowPublic: true } db.create') }) const weird = { diff --git a/test/install-order.test.js b/test/install-order.test.js index bd9ece6..3ae80a1 100644 --- a/test/install-order.test.js +++ b/test/install-order.test.js @@ -24,7 +24,7 @@ test('installed in right order', t => { const input = { content, - options: { allowPublic: true } + allowPublic: true } server.publish(input, (err, msg) => { diff --git a/test/not-installed.test.js b/test/not-installed.test.js index 718a9df..889e857 100644 --- a/test/not-installed.test.js +++ b/test/not-installed.test.js @@ -10,7 +10,7 @@ test('not installed', t => { const content = { type: 'profile' } - ssb.publish({ content, options: { allowPublic: true } }, (err) => { + ssb.publish({ content, allowPublic: true }, (err) => { t.match(err.message, /type must be a string/) ssb.close(t.end) })