mirror of https://codeberg.org/pzp/pzp-db.git
add identity.consent() API
This commit is contained in:
parent
a3fcb641eb
commit
0899ac5818
67
lib/index.js
67
lib/index.js
|
@ -8,7 +8,9 @@ const b4a = require('b4a')
|
||||||
const base58 = require('bs58')
|
const base58 = require('bs58')
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const Obz = require('obz')
|
const Obz = require('obz')
|
||||||
|
const Keypair = require('ppppp-keypair')
|
||||||
const MsgV3 = require('./msg-v3')
|
const MsgV3 = require('./msg-v3')
|
||||||
|
const { SIGNATURE_TAG_IDENTITY_ADD, IDENTITY_SELF } = require('./msg-v3/constants')
|
||||||
const { ReadyGate } = require('./utils')
|
const { ReadyGate } = require('./utils')
|
||||||
const { decrypt } = require('./encryption')
|
const { decrypt } = require('./encryption')
|
||||||
|
|
||||||
|
@ -272,7 +274,7 @@ function initDB(peer, config) {
|
||||||
const tangle = new DBTangle(tangleRootHash, records())
|
const tangle = new DBTangle(tangleRootHash, records())
|
||||||
|
|
||||||
const pubkeys = new Set()
|
const pubkeys = new Set()
|
||||||
if (msg.metadata.identity && msg.metadata.identity !== 'self') {
|
if (msg.metadata.identity && msg.metadata.identity !== IDENTITY_SELF) {
|
||||||
const identityTangle = new DBTangle(msg.metadata.identity, records())
|
const identityTangle = new DBTangle(msg.metadata.identity, records())
|
||||||
if (!identityTangle.has(msg.metadata.identity)) {
|
if (!identityTangle.has(msg.metadata.identity)) {
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
|
@ -323,7 +325,7 @@ function initDB(peer, config) {
|
||||||
*/
|
*/
|
||||||
function getIdentityId(rec) {
|
function getIdentityId(rec) {
|
||||||
if (!rec.msg) return null
|
if (!rec.msg) return null
|
||||||
if (rec.msg.metadata.identity === 'self') {
|
if (rec.msg.metadata.identity === IDENTITY_SELF) {
|
||||||
for (const tangleId in rec.msg.metadata.tangles) {
|
for (const tangleId in rec.msg.metadata.tangles) {
|
||||||
return tangleId
|
return tangleId
|
||||||
}
|
}
|
||||||
|
@ -343,8 +345,8 @@ function initDB(peer, config) {
|
||||||
* @param {CB<string>} cb
|
* @param {CB<string>} cb
|
||||||
*/
|
*/
|
||||||
function findIdentity(opts, cb) {
|
function findIdentity(opts, cb) {
|
||||||
if (!opts.domain)
|
// prettier-ignore
|
||||||
return cb(new Error('identity.find() requires a `domain`'))
|
if (!opts.domain) return cb(new Error('identity.find() requires a `domain`'))
|
||||||
const keypair = opts?.keypair ?? config.keypair
|
const keypair = opts?.keypair ?? config.keypair
|
||||||
const domain = opts.domain
|
const domain = opts.domain
|
||||||
|
|
||||||
|
@ -354,7 +356,7 @@ function initDB(peer, config) {
|
||||||
if (!rec.msg) continue
|
if (!rec.msg) continue
|
||||||
if (!rec.msg.data) continue
|
if (!rec.msg.data) continue
|
||||||
if (
|
if (
|
||||||
rec.msg.metadata.identity === 'self' &&
|
rec.msg.metadata.identity === IDENTITY_SELF &&
|
||||||
rec.msg.data.add === keypair.public &&
|
rec.msg.data.add === keypair.public &&
|
||||||
rec.msg.metadata.domain === domain
|
rec.msg.metadata.domain === domain
|
||||||
) {
|
) {
|
||||||
|
@ -365,7 +367,7 @@ function initDB(peer, config) {
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
cb(new Error(`identity.find() failed to find ID in ${JSON.stringify(rec.msg)}`))
|
cb(new Error(`identity.find() failed to find ID in ${JSON.stringify(rec.msg)}`))
|
||||||
}
|
}
|
||||||
break
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
|
@ -382,8 +384,8 @@ function initDB(peer, config) {
|
||||||
* @param {CB<string>} cb
|
* @param {CB<string>} cb
|
||||||
*/
|
*/
|
||||||
function createIdentity(opts, cb) {
|
function createIdentity(opts, cb) {
|
||||||
if (!opts.domain)
|
// prettier-ignore
|
||||||
return cb(new Error('identity.create() requires a `domain`'))
|
if (!opts.domain) return cb(new Error('identity.create() requires a `domain`'))
|
||||||
const keypair = opts?.keypair ?? config.keypair
|
const keypair = opts?.keypair ?? config.keypair
|
||||||
const domain = opts.domain
|
const domain = opts.domain
|
||||||
|
|
||||||
|
@ -425,25 +427,59 @@ function initDB(peer, config) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {{ keypair: Keypair; identity: string; }} opts
|
* @param {{
|
||||||
|
* keypair?: Keypair;
|
||||||
|
* identity: string;
|
||||||
|
* }} opts
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function consentToIdentity(opts) {
|
||||||
|
// prettier-ignore
|
||||||
|
if (!opts.identity) throw new Error('identity.consent() requires an `identity`')
|
||||||
|
const keypair = opts?.keypair ?? config.keypair
|
||||||
|
|
||||||
|
const signableBuf = b4a.from(
|
||||||
|
SIGNATURE_TAG_IDENTITY_ADD + base58.decode(opts.identity),
|
||||||
|
'utf8'
|
||||||
|
)
|
||||||
|
return Keypair.sign(keypair, signableBuf)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {{
|
||||||
|
* keypair: Keypair;
|
||||||
|
* identity: string;
|
||||||
|
* consent: string
|
||||||
|
* }} opts
|
||||||
* @param {CB<Rec>} cb
|
* @param {CB<Rec>} cb
|
||||||
*/
|
*/
|
||||||
function addToIdentity(opts, cb) {
|
function addToIdentity(opts, cb) {
|
||||||
if (!opts?.keypair)
|
// prettier-ignore
|
||||||
return cb(new Error('identity.add() requires a `keypair`'))
|
if (!opts?.keypair) return cb(new Error('identity.add() requires a `keypair`'))
|
||||||
if (!opts?.identity)
|
// prettier-ignore
|
||||||
return cb(new Error('identity.add() requires a `identity`'))
|
if (!opts?.identity) return cb(new Error('identity.add() requires a `identity`'))
|
||||||
|
// prettier-ignore
|
||||||
|
if (!opts?.consent) return cb(new Error('identity.add() requires a `consent`'))
|
||||||
const addedKeypair = opts.keypair
|
const addedKeypair = opts.keypair
|
||||||
const signingKeypair = config.keypair
|
const signingKeypair = config.keypair
|
||||||
|
|
||||||
|
// Verify consent:
|
||||||
|
const signableBuf = b4a.from(
|
||||||
|
SIGNATURE_TAG_IDENTITY_ADD + base58.decode(opts.identity),
|
||||||
|
)
|
||||||
|
if (!Keypair.verify(addedKeypair, signableBuf, opts.consent)) {
|
||||||
|
// prettier-ignore
|
||||||
|
return cb(new Error('identity.add() failed because the consent is invalid'))
|
||||||
|
}
|
||||||
|
|
||||||
// Fill-in tangle opts:
|
// Fill-in tangle opts:
|
||||||
const tangles = populateTangles([opts.identity])
|
const tangles = populateTangles([opts.identity])
|
||||||
const fullOpts = {
|
const fullOpts = {
|
||||||
identity: 'self',
|
identity: IDENTITY_SELF,
|
||||||
identityTips: null,
|
identityTips: null,
|
||||||
tangles,
|
tangles,
|
||||||
keypair: signingKeypair,
|
keypair: signingKeypair,
|
||||||
data: { add: addedKeypair.public },
|
data: { add: addedKeypair.public, consent: opts.consent },
|
||||||
domain: 'identity',
|
domain: 'identity',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -652,6 +688,7 @@ function initDB(peer, config) {
|
||||||
create: createIdentity,
|
create: createIdentity,
|
||||||
findOrCreate: findOrCreateIdentity,
|
findOrCreate: findOrCreateIdentity,
|
||||||
add: addToIdentity,
|
add: addToIdentity,
|
||||||
|
consent: consentToIdentity,
|
||||||
},
|
},
|
||||||
feed: {
|
feed: {
|
||||||
publish: publishToFeed,
|
publish: publishToFeed,
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
module.exports = {
|
||||||
|
/** @type {'self'} */
|
||||||
|
IDENTITY_SELF: 'self',
|
||||||
|
|
||||||
|
SIGNATURE_TAG_MSG_V3: ':msg-v3:',
|
||||||
|
SIGNATURE_TAG_IDENTITY_ADD: ':identity-add:',
|
||||||
|
}
|
|
@ -17,6 +17,7 @@ const {
|
||||||
validateMsgHash,
|
validateMsgHash,
|
||||||
} = require('./validation')
|
} = require('./validation')
|
||||||
const Tangle = require('./tangle')
|
const Tangle = require('./tangle')
|
||||||
|
const { IDENTITY_SELF, SIGNATURE_TAG_MSG_V3 } = require('./constants')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {import('ppppp-keypair').Keypair} Keypair
|
* @typedef {import('ppppp-keypair').Keypair} Keypair
|
||||||
|
@ -57,8 +58,6 @@ const Tangle = require('./tangle')
|
||||||
* }} CreateOpts
|
* }} CreateOpts
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const IDENTITY_SELF = /** @type {const} */ 'self'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} id
|
* @param {string} id
|
||||||
* @param {string} domain
|
* @param {string} domain
|
||||||
|
@ -138,9 +137,11 @@ function create(opts) {
|
||||||
}
|
}
|
||||||
if ((err = validateData(msg))) throw err
|
if ((err = validateData(msg))) throw err
|
||||||
|
|
||||||
// TODO: add a label prefix to the metadata before signing
|
const signableBuf = b4a.from(
|
||||||
const metadataBuf = b4a.from(stringify(msg.metadata), 'utf8')
|
SIGNATURE_TAG_MSG_V3 + stringify(msg.metadata),
|
||||||
msg.sig = Keypair.sign(opts.keypair, metadataBuf)
|
'utf8'
|
||||||
|
)
|
||||||
|
msg.sig = Keypair.sign(opts.keypair, signableBuf)
|
||||||
|
|
||||||
return msg
|
return msg
|
||||||
}
|
}
|
||||||
|
@ -171,9 +172,11 @@ function createRoot(id, domain, keypair) {
|
||||||
sig: '',
|
sig: '',
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: add a label prefix to the metadata before signing
|
const signableBuf = b4a.from(
|
||||||
const metadataBuf = b4a.from(stringify(msg.metadata), 'utf8')
|
SIGNATURE_TAG_MSG_V3 + stringify(msg.metadata),
|
||||||
msg.sig = Keypair.sign(keypair, metadataBuf)
|
'utf8'
|
||||||
|
)
|
||||||
|
msg.sig = Keypair.sign(keypair, signableBuf)
|
||||||
|
|
||||||
return msg
|
return msg
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ const stringify = require('json-canon')
|
||||||
const Tangle = require('./tangle')
|
const Tangle = require('./tangle')
|
||||||
const representData = require('./represent-data')
|
const representData = require('./represent-data')
|
||||||
const isFeedRoot = require('./is-feed-root')
|
const isFeedRoot = require('./is-feed-root')
|
||||||
|
const { SIGNATURE_TAG_MSG_V3, IDENTITY_SELF } = require('./constants')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {import('.').Msg} Msg
|
* @typedef {import('.').Msg} Msg
|
||||||
|
@ -90,7 +91,7 @@ function validateIdentityPubkey(msg, pubkeys) {
|
||||||
|
|
||||||
if (
|
if (
|
||||||
msg.metadata.identity &&
|
msg.metadata.identity &&
|
||||||
msg.metadata.identity !== 'self' &&
|
msg.metadata.identity !== IDENTITY_SELF &&
|
||||||
!pubkeys.has(msg.pubkey)
|
!pubkeys.has(msg.pubkey)
|
||||||
) {
|
) {
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
|
@ -150,7 +151,10 @@ function validateSignature(msg) {
|
||||||
return `invalid message: sig "${sig}" should have been a base58 string\n` + JSON.stringify(msg)
|
return `invalid message: sig "${sig}" should have been a base58 string\n` + JSON.stringify(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
const signableBuf = b4a.from(stringify(msg.metadata), 'utf8')
|
const signableBuf = b4a.from(
|
||||||
|
SIGNATURE_TAG_MSG_V3 + stringify(msg.metadata),
|
||||||
|
'utf8'
|
||||||
|
)
|
||||||
const keypair = { curve: 'ed25519', public: msg.pubkey }
|
const keypair = { curve: 'ed25519', public: msg.pubkey }
|
||||||
const verified = Keypair.verify(keypair, signableBuf, sig)
|
const verified = Keypair.verify(keypair, signableBuf, sig)
|
||||||
if (!verified) {
|
if (!verified) {
|
||||||
|
|
|
@ -26,9 +26,12 @@ test('identity.add()', async (t) => {
|
||||||
domain: 'person',
|
domain: 'person',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const consent = peer.db.identity.consent({ identity: id, keypair: keypair2 })
|
||||||
|
|
||||||
const identityRec1 = await p(peer.db.identity.add)({
|
const identityRec1 = await p(peer.db.identity.add)({
|
||||||
identity: id,
|
identity: id,
|
||||||
keypair: keypair2,
|
keypair: keypair2,
|
||||||
|
consent,
|
||||||
})
|
})
|
||||||
assert.ok(identityRec1, 'identityRec1 exists')
|
assert.ok(identityRec1, 'identityRec1 exists')
|
||||||
const { hash, msg } = identityRec1
|
const { hash, msg } = identityRec1
|
||||||
|
@ -64,9 +67,12 @@ test('publish with a key in the identity', async (t) => {
|
||||||
domain: 'person',
|
domain: 'person',
|
||||||
})
|
})
|
||||||
const identityMsg0 = peer.db.get(identity)
|
const identityMsg0 = peer.db.get(identity)
|
||||||
|
|
||||||
|
const consent = peer.db.identity.consent({ identity, keypair: keypair2 })
|
||||||
const identityRec1 = await p(peer.db.identity.add)({
|
const identityRec1 = await p(peer.db.identity.add)({
|
||||||
identity,
|
identity,
|
||||||
keypair: keypair2,
|
keypair: keypair2,
|
||||||
|
consent,
|
||||||
})
|
})
|
||||||
|
|
||||||
const postRec = await p(peer.db.feed.publish)({
|
const postRec = await p(peer.db.feed.publish)({
|
||||||
|
|
|
@ -95,7 +95,7 @@ test('MsgV3.create()', (t) => {
|
||||||
)
|
)
|
||||||
assert.equal(
|
assert.equal(
|
||||||
msg1.sig,
|
msg1.sig,
|
||||||
'2FhnKsDKCxEV4JUM1nmPp3oSFJ8zL7r4SjMNogDHeAzCWQLgVmKiexgUDSE4k9C3eT4Uy3SZbBhRY75WJAqvtHHf',
|
'3ucLkFxXJkbX6N7qZQm5PNop2tQ5Z1E9oCVB4HCZjeD3Mn7EXMrgZzCDZfpLTVUUBRqSBQJFxL1j5jNWKFeidHgV',
|
||||||
'sig'
|
'sig'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -155,7 +155,7 @@ test('MsgV3.create()', (t) => {
|
||||||
)
|
)
|
||||||
assert.equal(
|
assert.equal(
|
||||||
msg2.sig,
|
msg2.sig,
|
||||||
'3B6WQvDdKvRhZeZDfE9LY4HrnhZTJHRJ86FazBg1xxco2S1eHG44UwR9TSpthiXQ1X51h2VeDeGPV6Fdma69BMN9',
|
'RtHPPccZNp6c65SnCrfjsNVB6We6G4Ja1oi68AdLuzxSjWNayxepagYJQwgP635E4b55xNGckMiFvJF9Vsn3oAi',
|
||||||
'sig'
|
'sig'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue