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')
/**
* @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
* @typedef {{type: 'account-add', account: string}} AccountAddPromise
* @typedef {FollowPromise | AccountAddPromise} PPromise
* @param {Config} config
* @returns {asserts config is ExpectedConfig}
*/
const FILENAME = 'promises.json'
function assertValidConfig(config) {
if (typeof config.global?.path !== 'string') {
throw new Error('promise plugin requires config.global.path')
}
}
module.exports = {
name: 'promise',
@ -43,11 +57,13 @@ module.exports = {
},
/**
* @param {any} local
* @param {any} config
* @param {{ db: PPPPPDB | null }} peer
* @param {Config} config
*/
init(local, config) {
const devicePromisesFile = Path.join(config.path, FILENAME)
init(peer, config) {
assertDBPlugin(peer)
assertValidConfig(config)
const devicePromisesFile = Path.join(config.global.path, 'promises.json')
const promises = /** @type {Map<string, PPromise>} */ (new Map())
let loaded = false
@ -164,22 +180,60 @@ module.exports = {
setTimeout(() => accountAdd(token, addition, cb), 100)
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 (
!addition?.key?.purpose ||
!addition?.key?.algorithm ||
!addition?.key?.bytes ||
!addition?.consent ||
addition?.key?.purpose !== 'sig' ||
addition?.key?.algorithm !== 'ed25519'
!addition?.key?.bytes
) {
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
}
if (!promises.has(token)) {
cb(new Error('Invalid token'))
return
}
const promise = /** @type {AccountAddPromise} */ (promises.get(token))
const { type, account } = promise
if (type !== 'account-add') {
@ -187,18 +241,17 @@ module.exports = {
return
}
const keypair = { curve: 'ed25519', public: addition.key.bytes }
if (local.db.account.has({ account, keypair })) {
const keypair = {
curve: /**@type {const}*/ ('ed25519'),
public: addition.key.bytes,
}
if (peer.db.account.has({ account, keypair })) {
cb(null, false)
return
}
local.db.account.add(
peer.db.account.add(
{ account, keypair, consent: addition.consent },
/**
* @param {Error | null} err
* @param {any} rec
*/
(err, rec) => {
if (err) return cb(err)
promises.delete(token)

View File

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

View File

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