OverviewFilesCommitsIssuesDocsReleasesPeers

favela-host / test / multi-did-ops.test.js

54 lines · javascript ref:
// The atproto ops on a node that holds SEVERAL identities.
//
// ⛔ WHY. Every did-* op read `cfg.dids[0]`. The desktop app on a server boots hpp's own config, which
// holds wj62 AND loser-club (hyper-social #77 §5, change 5), so "this node's identity" was whichever
// happened to be first — and did-carry / did-adopt / did-submit are PERMANENT appends to a global log.
// The rule is pickNode's (lib/project.js): an explicit `did` wins, then the designated credential
// (identity.md §5.1), then the only one; with several and none chosen a WRITE refuses rather than
// guessing. did-status only reads, so it answers for the chosen one and lists them all.

import test from 'brittle'
import fs from 'bare-fs'
import os from 'bare-os'
import process from 'bare-process'
import { join } from 'bare-path'
import { inProcessClient } from '../index.js'
import { atprotoEntry, atprotoConfigPath } from '../lib/atproto.js'

const PASS = 'a passphrase long enough'

async function twoDids (t) {
  const root = join(os.tmpdir(), 'fh-multi-' + Date.now() + '-' + Math.random().toString(16).slice(2))
  const storage = join(root, 'storage')
  fs.mkdirSync(storage, { recursive: true })
  const realHome = process.env.HOME
  process.env.HOME = join(root, 'home')
  fs.mkdirSync(process.env.HOME, { recursive: true })
  t.teardown(() => { process.env.HOME = realHome })
  const node = { storage }
  const client = inProcessClient(node)
  // hpp's mint refuses a second identity in one config (by design), so the second entry is written the
  // way a co-owner's config gets one — appended to `dids`. Only selection is under test here.
  const a = await client.call('did-mint', { passphrase: PASS })
  const cfgPath = atprotoConfigPath(node)
  const cfg = JSON.parse(fs.readFileSync(cfgPath, 'utf8'))
  const b = 'did:plc:secondidentityforselect'
  cfg.dids.push({ ...cfg.dids[0], did: b })
  fs.writeFileSync(cfgPath, JSON.stringify(cfg, null, 2))
  return { node, client, a: a.did, b }
}

test('did-status lists every identity and answers for the one asked about', async (t) => {
  const { client, a, b } = await twoDids(t)
  const st = await client.call('did-status')
  t.alike(st.dids, [a, b], 'both identities listed')
  t.is((await client.call('did-status', { did: b })).did, b, 'an explicit did is answered for')
})

test('a write with several identities and none chosen REFUSES instead of using the first', async (t) => {
  const { node, b } = await twoDids(t)
  t.exception(() => atprotoEntry(node, {}, { write: true }), /several identities/)
  t.is(atprotoEntry(node, { did: b }, { write: true }).entry.did, b, 'an explicit did is used')
  t.exception(() => atprotoEntry(node, { did: 'did:plc:notmine' }, { write: true }), /not an identity/)
})