update to ppppp-db in 2024

This commit is contained in:
Andre Staltz 2024-01-01 18:23:31 +02:00
parent 05d0523d1d
commit 85d594591a
No known key found for this signature in database
GPG Key ID: 9EDE23EA7E8A4890
3 changed files with 111 additions and 53 deletions

View File

@ -6,7 +6,14 @@ const bs58 = require('bs58')
const b4a = require('b4a') const b4a = require('b4a')
/** /**
* @typedef {import('ppppp-db/msg-v3').AccountAdd} AccountAdd * @typedef {ReturnType<import('ppppp-db').init>} PPPPPDB
* @typedef {import('ppppp-db/msg-v4').AccountAdd} AccountAdd
* @typedef {Buffer | Uint8Array} B4A
* @typedef {{global: {path: string}}} ExpectedConfig
* @typedef {{global: {path?: string}}} Config
* @typedef {{type: 'follow'}} FollowPromise
* @typedef {{type: 'account-add', account: string}} AccountAddPromise
* @typedef {FollowPromise | AccountAddPromise} PPromise
*/ */
/** /**
@ -15,16 +22,23 @@ const b4a = require('b4a')
*/ */
/** /**
* @typedef {Buffer | Uint8Array} B4A * @param {{ db: PPPPPDB | null }} peer
* @returns {asserts peer is { db: PPPPPDB }}
*/ */
function assertDBPlugin(peer) {
// prettier-ignore
if (!peer.db) throw new Error('promise plugin plugin requires ppppp-db plugin')
}
/** /**
* @typedef {{type: 'follow'}} FollowPromise * @param {Config} config
* @typedef {{type: 'account-add', account: string}} AccountAddPromise * @returns {asserts config is ExpectedConfig}
* @typedef {FollowPromise | AccountAddPromise} PPromise
*/ */
function assertValidConfig(config) {
const FILENAME = 'promises.json' if (typeof config.global?.path !== 'string') {
throw new Error('promise plugin requires config.global.path')
}
}
module.exports = { module.exports = {
name: 'promise', name: 'promise',
@ -43,11 +57,13 @@ module.exports = {
}, },
/** /**
* @param {any} local * @param {{ db: PPPPPDB | null }} peer
* @param {any} config * @param {Config} config
*/ */
init(local, config) { init(peer, config) {
const devicePromisesFile = Path.join(config.path, FILENAME) assertDBPlugin(peer)
assertValidConfig(config)
const devicePromisesFile = Path.join(config.global.path, 'promises.json')
const promises = /** @type {Map<string, PPromise>} */ (new Map()) const promises = /** @type {Map<string, PPromise>} */ (new Map())
let loaded = false let loaded = false
@ -164,15 +180,52 @@ module.exports = {
setTimeout(() => accountAdd(token, addition, cb), 100) setTimeout(() => accountAdd(token, addition, cb), 100)
return return
} }
try {
assertDBPlugin(peer)
} catch (err) {
cb(/**@type {Error}*/ (err))
return
}
if (!addition?.consent) {
// prettier-ignore
cb(new Error('Invalid key to be added, missing "consent": ' + JSON.stringify(addition)))
return
}
if ( if (
!addition?.key?.purpose || !addition?.key?.purpose ||
!addition?.key?.algorithm || !addition?.key?.algorithm ||
!addition?.key?.bytes || !addition?.key?.bytes
!addition?.consent ||
addition?.key?.purpose !== 'sig' ||
addition?.key?.algorithm !== 'ed25519'
) { ) {
cb(new Error('Invalid key to be added: ' + JSON.stringify(addition))) // prettier-ignore
cb(new Error('Invalid key to be added, missing purpose/algorithm/bytes: ' + JSON.stringify(addition)))
return
}
const { algorithm, purpose } = addition.key
switch (purpose) {
case 'sig':
case 'shs-and-sig':
if (algorithm !== 'ed25519') {
// prettier-ignore
cb(new Error(`Invalid key to be added, expected algorithm "ed25519" for "${purpose}": ${JSON.stringify(addition)}`))
return
} else {
break
}
case 'external-encryption':
if (algorithm !== 'x25519-xsalsa20-poly1305') {
// prettier-ignore
cb(new Error(`Invalid key to be added, expected algorithm "x25519-xsalsa20-poly1305" for "${purpose}": ${JSON.stringify(addition)}`))
return
} else {
break
}
default:
// prettier-ignore
cb(new Error(`Invalid key to be added, expected purpose "sig", "shs-and-sig", or "external-encryption": ${JSON.stringify(addition)}`))
return return
} }
@ -180,6 +233,7 @@ module.exports = {
cb(new Error('Invalid token')) cb(new Error('Invalid token'))
return return
} }
const promise = /** @type {AccountAddPromise} */ (promises.get(token)) const promise = /** @type {AccountAddPromise} */ (promises.get(token))
const { type, account } = promise const { type, account } = promise
if (type !== 'account-add') { if (type !== 'account-add') {
@ -187,18 +241,17 @@ module.exports = {
return return
} }
const keypair = { curve: 'ed25519', public: addition.key.bytes } const keypair = {
if (local.db.account.has({ account, keypair })) { curve: /**@type {const}*/ ('ed25519'),
public: addition.key.bytes,
}
if (peer.db.account.has({ account, keypair })) {
cb(null, false) cb(null, false)
return return
} }
local.db.account.add( peer.db.account.add(
{ account, keypair, consent: addition.consent }, { account, keypair, consent: addition.consent },
/**
* @param {Error | null} err
* @param {any} rec
*/
(err, rec) => { (err, rec) => {
if (err) return cb(err) if (err) return cb(err)
promises.delete(token) promises.delete(token)

View File

@ -14,14 +14,14 @@
"files": [ "files": [
"lib/**/*" "lib/**/*"
], ],
"engines": {
"node": ">=16"
},
"exports": { "exports": {
".": { ".": {
"require": "./lib/index.js" "require": "./lib/index.js"
} }
}, },
"engines": {
"node": ">=16"
},
"dependencies": { "dependencies": {
"atomic-file-rw": "~0.3.0", "atomic-file-rw": "~0.3.0",
"b4a": "^1.6.4", "b4a": "^1.6.4",
@ -38,9 +38,9 @@
"prettier": "^2.6.2", "prettier": "^2.6.2",
"pretty-quick": "^3.1.3", "pretty-quick": "^3.1.3",
"rimraf": "^5.0.1", "rimraf": "^5.0.1",
"secret-handshake-ext": "~0.0.8", "secret-handshake-ext": "0.0.11",
"secret-stack": "~7.1.0", "secret-stack": "~8.0.0",
"typescript": "^5.0.2" "typescript": "^5.1.3"
}, },
"scripts": { "scripts": {
"clean-check": "tsc --build --clean", "clean-check": "tsc --build --clean",

View File

@ -8,28 +8,33 @@ const rimraf = require('rimraf')
const Keypair = require('ppppp-keypair') const Keypair = require('ppppp-keypair')
const caps = require('ppppp-caps') const caps = require('ppppp-caps')
function setup() { async function setup() {
setup.counter ??= 0 setup.counter ??= 0
setup.counter += 1 setup.counter += 1
const path = Path.join(os.tmpdir(), 'ppppp-promise-' + setup.counter) const path = Path.join(os.tmpdir(), 'ppppp-promise-' + setup.counter)
rimraf.sync(path) rimraf.sync(path)
const keypair = Keypair.generate('ed25519', 'alice') const keypair = Keypair.generate('ed25519', 'alice')
const local = require('secret-stack/bare')({ caps }) const local = require('secret-stack/bare')()
.use(require('secret-stack/plugins/net')) .use(require('secret-stack/plugins/net'))
.use(require('secret-handshake-ext/secret-stack')) .use(require('secret-handshake-ext/secret-stack'))
.use(require('ppppp-db')) .use(require('ppppp-db'))
.use(require('../lib')) .use(require('../lib'))
.call(null, { .call(null, {
shse: { caps },
global: {
path, path,
keypair, keypair,
},
}) })
await local.db.loaded()
return { local, path, keypair } return { local, path, keypair }
} }
test('create()', async (t) => { test('create()', async (t) => {
const { local, path } = setup() const { local, path } = await setup()
const promise = { type: 'follow' } const promise = { type: 'follow' }
const token = await p(local.promise.create)(promise) const token = await p(local.promise.create)(promise)
@ -45,9 +50,9 @@ test('create()', async (t) => {
}) })
test('follow()', async (t) => { test('follow()', async (t) => {
const { local, path } = setup() const { local, path } = await setup()
assert.rejects(() => p(local.promise.follow)('randomnottoken', 'MY_ID')) assert.rejects(() => p(local.promise.follow)('randomnottoken', 'FRIEND_ID'))
const promise = { type: 'follow' } const promise = { type: 'follow' }
const token = await p(local.promise.create)(promise) const token = await p(local.promise.create)(promise)
@ -56,24 +61,24 @@ test('follow()', async (t) => {
const contentsBefore = fs.readFileSync(file, 'utf-8') const contentsBefore = fs.readFileSync(file, 'utf-8')
assert.strictEqual(contentsBefore, JSON.stringify([[token, promise]])) assert.strictEqual(contentsBefore, JSON.stringify([[token, promise]]))
const result1 = await p(local.promise.follow)(token, 'MY_ID') const result1 = await p(local.promise.follow)(token, 'FRIEND_ID')
assert.strictEqual(result1, true) assert.strictEqual(result1, true)
const contentsAfter = fs.readFileSync(file, 'utf-8') const contentsAfter = fs.readFileSync(file, 'utf-8')
assert.strictEqual(contentsAfter, '[]') assert.strictEqual(contentsAfter, '[]')
assert.rejects(() => p(local.promise.follow)(token, 'MY_ID')) assert.rejects(() => p(local.promise.follow)(token, 'FRIEND_ID'))
await p(local.close)() await p(local.close)()
}) })
test('accountAdd()', async (t) => { test('accountAdd()', async (t) => {
const { local, path, keypair } = setup() const { local, path, keypair } = await setup()
assert.rejects(() => p(local.promise.accountAdd)('randomnottoken', {})) assert.rejects(() => p(local.promise.accountAdd)('randomnottoken', {}))
const account = await p(local.db.account.findOrCreate)({ const account = await p(local.db.account.findOrCreate)({
domain: 'account', subdomain: 'account',
}) })
const promise = { type: 'account-add', account } const promise = { type: 'account-add', account }
@ -86,10 +91,10 @@ test('accountAdd()', async (t) => {
const dbBefore = [...local.db.msgs()].map(({ data }) => data) const dbBefore = [...local.db.msgs()].map(({ data }) => data)
assert.equal(dbBefore.length, 1) assert.equal(dbBefore.length, 1)
assert.equal(dbBefore[0].action, 'add') assert.equal(dbBefore[0].action, 'add')
assert.equal(dbBefore[0].add.key.algorithm, 'ed25519') assert.equal(dbBefore[0].key.algorithm, 'ed25519')
assert.equal(dbBefore[0].add.key.bytes, keypair.public) assert.equal(dbBefore[0].key.bytes, keypair.public)
assert.equal(dbBefore[0].add.key.purpose, 'sig') assert.equal(dbBefore[0].key.purpose, 'shs-and-sig')
assert(dbBefore[0].add.nonce) assert(dbBefore[0].nonce)
const keypair2 = Keypair.generate('ed25519', 'bob') const keypair2 = Keypair.generate('ed25519', 'bob')
const consent = local.db.account.consent({ account, keypair: keypair2 }) const consent = local.db.account.consent({ account, keypair: keypair2 })
@ -106,15 +111,15 @@ test('accountAdd()', async (t) => {
const dbAfter = [...local.db.msgs()].map(({ data }) => data) const dbAfter = [...local.db.msgs()].map(({ data }) => data)
assert.equal(dbAfter.length, 2) assert.equal(dbAfter.length, 2)
assert.equal(dbAfter[0].action, 'add') assert.equal(dbAfter[0].action, 'add')
assert.equal(dbAfter[0].add.key.algorithm, 'ed25519') assert.equal(dbAfter[0].key.algorithm, 'ed25519')
assert.equal(dbAfter[0].add.key.bytes, keypair.public) assert.equal(dbAfter[0].key.bytes, keypair.public)
assert.equal(dbAfter[0].add.key.purpose, 'sig') assert.equal(dbAfter[0].key.purpose, 'shs-and-sig')
assert(dbAfter[0].add.nonce) assert(dbAfter[0].nonce)
assert.equal(dbAfter[1].action, 'add') assert.equal(dbAfter[1].action, 'add')
assert.equal(dbAfter[1].add.key.algorithm, 'ed25519') assert.equal(dbAfter[1].key.algorithm, 'ed25519')
assert.equal(dbAfter[1].add.key.bytes, keypair2.public) assert.equal(dbAfter[1].key.bytes, keypair2.public)
assert.equal(dbAfter[1].add.key.purpose, 'sig') assert.equal(dbAfter[1].key.purpose, 'sig')
assert(dbAfter[1].add.consent) assert(dbAfter[1].consent)
const contentsAfter = fs.readFileSync(file, 'utf-8') const contentsAfter = fs.readFileSync(file, 'utf-8')
assert.strictEqual(contentsAfter, '[]') assert.strictEqual(contentsAfter, '[]')
@ -125,7 +130,7 @@ test('accountAdd()', async (t) => {
}) })
test('revoke()', async (t) => { test('revoke()', async (t) => {
const { local, path } = setup() const { local, path } = await setup()
const promise = { type: 'follow' } const promise = { type: 'follow' }
const token = await p(local.promise.create)(promise) const token = await p(local.promise.create)(promise)