Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tweak api, add linting #7

Merged
merged 3 commits into from
Dec 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 33 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -28,20 +27,24 @@ 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!
})
```

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' }
})
Expand All @@ -50,39 +53,27 @@ server.publish(allowedType, (err, msg) => {
explictly public:
```js
const explicitPublicMsg = {
content: { type: 'profile' },
options: { 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

Expand All @@ -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**
Expand Down Expand Up @@ -118,14 +124,15 @@ 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.

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
Expand Down
78 changes: 44 additions & 34 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
/* 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`
)

let warnings = 0

module.exports = {
name: 'recpsGuard',
Expand All @@ -9,61 +19,61 @@ 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 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++
}

// allowPublic and no recps, allowed
return publish(input.content, cb)
} else {
// without allowPublic, content isn't nested with db1 publish
const isExplictAllow = (modernAllow || legacyAllow)

if (isExplictAllow) {
const content = input.content

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) {
Expand Down
13 changes: 8 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand All @@ -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"
}
}
89 changes: 52 additions & 37 deletions test/core-tests.js
Original file line number Diff line number Diff line change
@@ -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))

mixmix marked this conversation as resolved.
Show resolved Hide resolved
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)
}
Loading
Loading