// Asking this membrane to carry an identity, with no operator in the loop.
//
// ⛔ WHAT WAS MISSING. allowFront() only un-denies; a DID enters the fronted set through add(),
// reachable only from config at boot or from the owner-gated control socket. So a peer holding a
// perfectly good identity had no way to ask to be carried — the same gap ap-bridge had for names.
//
// ⚠ The proof here is NOT what protects the identity: this membrane is keyless and refuses any
// commit that does not verify against the PLC-resolved key, so naming someone else's identity would
// only make it serve their real, signed repo. The proof bounds ABUSE — carrying an identity costs a
// topic join and replication for ever.
import test from 'brittle'
import b4a from 'b4a'
import IdentityKey from 'keet-identity-key'
import crypto from 'hypercore-crypto'
import { signAttested } from 'favela-identity/lib/attestation.js'
import { Admission } from 'favela-identity/lib/admission.js'
import { frontHandlers } from '../../lib/fronting.js'
const ID = 'did:plc:abcdefghijklmnopqrstuvwx'
async function identity () {
const idk = await IdentityKey.from({ mnemonic: IdentityKey.generateMnemonic() })
const kp = crypto.keyPair()
const proof = await IdentityKey.bootstrap({ identity: idk.identityKeyPair }, kp.publicKey)
return { rootHex: b4a.toString(idk.identityKeyPair.publicKey, 'hex'), cred: { keyPair: kp, proof: b4a.toString(proof, 'base64') } }
}
function fakePeers () {
const added = []
return { added, add: (id) => { added.push(id) }, isFronted: (id) => added.includes(id), resolveHandle: () => null }
}
const deps = (extra = {}) => ({ peers: fakePeers(), admission: new Admission(), ...extra })
test('★★ a peer asks to be carried, and is', async (t) => {
const d = deps()
const h = frontHandlers(d)
const me = await identity()
const { nonce } = await h.challenge()
const proof = signAttested(d.admission.frontBytes({ nonce, id: ID }), me.cred)
const r = await h.front({ id: ID, root: me.rootHex, nonce, proof })
t.is(r.status, 200)
t.ok(r.body.fronted)
t.alike(d.peers.added, [ID], 'and the router was told to carry it')
})
test('⛔ an unproven request carries nothing', async (t) => {
const d = deps()
const h = frontHandlers(d)
const me = await identity()
t.is((await h.front({ id: ID, root: me.rootHex })).status, 403, 'no challenge, no proof')
t.alike(d.peers.added, [], 'nothing was fronted')
const { nonce } = await h.challenge()
const other = await identity()
const proof = signAttested(d.admission.frontBytes({ nonce, id: ID }), other.cred)
const r = await h.front({ id: ID, root: me.rootHex, nonce, proof })
t.is(r.status, 403, 'a proof from somebody else')
t.is(r.body.error, 'proof does not attest that identity')
t.alike(d.peers.added, [])
})
test('⛔ a proof cannot be replayed for a different identity', async (t) => {
const d = deps()
const h = frontHandlers(d)
const me = await identity()
const { nonce } = await h.challenge()
const proof = signAttested(d.admission.frontBytes({ nonce, id: ID }), me.cred)
t.is((await h.front({ id: 'did:plc:somebodyelseentirely00', root: me.rootHex, nonce, proof })).status, 403)
t.alike(d.peers.added, [])
})
// ⚠ A peer that retried after a dropped connection must not be told it did something wrong.
test('★ asking twice is not an error', async (t) => {
const d = deps()
const h = frontHandlers(d)
const me = await identity()
for (let i = 0; i < 2; i++) {
const { nonce } = await h.challenge()
const proof = signAttested(d.admission.frontBytes({ nonce, id: ID }), me.cred)
const r = await h.front({ id: ID, root: me.rootHex, nonce, proof })
t.is(r.status, 200)
if (i === 1) t.ok(r.body.already, 'and it says it was already carried')
}
})
// ⛔ frontedDids is CONFIG; this is not. A restart that silently unfronted everyone who asked would
// make the whole door a lie.
test('⛔ what a peer asked for is persisted, and it says so when it cannot be', async (t) => {
const saved = []
const d = deps({ onFronted: async (x) => { saved.push(x) } })
const h = frontHandlers(d)
const me = await identity()
let { nonce } = await h.challenge()
let proof = signAttested(d.admission.frontBytes({ nonce, id: ID }), me.cred)
let r = await h.front({ id: ID, root: me.rootHex, nonce, proof })
t.ok(r.body.durable)
t.is(saved.length, 1)
t.is(saved[0].root, me.rootHex, 'and WHO asked is recorded, not only what')
const d2 = deps({ onFronted: async () => { throw new Error('disk full') } })
const h2 = frontHandlers(d2)
;({ nonce } = await h2.challenge())
proof = signAttested(d2.admission.frontBytes({ nonce, id: ID }), me.cred)
r = await h2.front({ id: ID, root: me.rootHex, nonce, proof })
t.is(r.status, 202, 'carried, and honest that it will not survive a restart')
t.absent(r.body.durable)
t.ok(d2.peers.isFronted(ID), 'it IS fronted — sending the peer away would be the wrong lie')
})
test('⛔ a bridge that does not take requests says so', async (t) => {
const d = deps()
const h = frontHandlers({ ...d, policy: 'closed' })
const me = await identity()
const { nonce } = await h.challenge()
const proof = signAttested(d.admission.frontBytes({ nonce, id: ID }), me.cred)
t.is((await h.front({ id: ID, root: me.rootHex, nonce, proof })).status, 403)
t.alike(d.peers.added, [])
})
// ⛔ THE MEMBRANE GRANTS THE HANDLE, and hands back what the peer must put on-chain next. hpp's mint
// names this step: the handle and service endpoint "arrive LATER, as a PLC update op, when an invite
// from a bridge is accepted". Without these two values coming back, the peer has nothing to adopt.
test('★★ fronting grants a handle and names the endpoint to adopt', async (t) => {
const saved = []
const d = deps({ userDomain: '.7rnx.net', pdsUrl: 'https://pds.7rnx.net', onFronted: async (x) => saved.push(x) })
const h = frontHandlers(d)
const me = await identity()
const offer = await h.policy()
t.is(offer.userDomain, '.7rnx.net', 'a peer can see the suffix BEFORE committing to a permanent append')
t.is(offer.pdsUrl, 'https://pds.7rnx.net')
const { nonce } = await h.challenge()
const proof = signAttested(d.admission.frontBytes({ nonce, id: ID }), me.cred)
const r = await h.front({ id: ID, name: 'alice', root: me.rootHex, nonce, proof })
t.is(r.status, 200)
t.is(r.body.handle, 'alice.7rnx.net', 'granted under the membrane domain, not chosen by the peer')
t.is(r.body.pdsUrl, 'https://pds.7rnx.net', 'and the endpoint the DID must point at')
t.is(saved[0].handle, 'alice.7rnx.net', 'persisted, or a restart forgets the name it granted')
})
test('⛔ a handle held by somebody else is refused, not reassigned', async (t) => {
const d = deps({ userDomain: '.7rnx.net' })
d.peers.resolveHandle = (h) => (h === 'alice.7rnx.net' ? 'did:plc:somebodyelse00000000000' : null)
const h = frontHandlers(d)
const me = await identity()
const { nonce } = await h.challenge()
const proof = signAttested(d.admission.frontBytes({ nonce, id: ID }), me.cred)
const r = await h.front({ id: ID, name: 'alice', root: me.rootHex, nonce, proof })
t.is(r.status, 409)
t.is(r.body.error, 'taken')
t.alike(d.peers.added, [], 'and nothing was fronted')
})
test('⛔ a garbage handle is refused before anything is recorded', async (t) => {
const d = deps({ userDomain: '.7rnx.net' })
const h = frontHandlers(d)
const me = await identity()
const { nonce } = await h.challenge()
const proof = signAttested(d.admission.frontBytes({ nonce, id: ID }), me.cred)
t.is((await h.front({ id: ID, name: 'not a handle', root: me.rootHex, nonce, proof })).status, 400)
t.alike(d.peers.added, [])
})