OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-identity / test / atproto-provider.test.js

76 lines · javascript ref:
import test from 'brittle'
import { createTestDb } from './helpers.js'
import { IdentityRegistry } from '../lib/auth/identity-registry.js'
import { AtprotoProvider } from '../lib/auth/providers/atproto.js'

async function createRegistry () {
  const { store, db, cleanup } = await createTestDb()
  const registry = new IdentityRegistry(store, { db })
  await registry.ready()
  return { registry, cleanup: async () => { await registry.close(); await cleanup() } }
}

// A verified binding, as the injected atproto verifier would return it (from
// atproto-bridge/identity). Values are a real did:plc derived from a test mnemonic.
const BINDING = {
  did: 'did:plc:yu4kgcktm7qtrwo5ofyiaf4c',
  handle: 'alice.7rnx.net',
  didKey: 'did:key:zQ3shExampleSecp256k1DidKeyForTestOnly000000000000',
  ed25519PublicKey: 'de1ab94806ced05c64c0098de00ffadae052351b9fbdb6be4d645123ac9d8798'
}

test('atproto provider auto-registers a binding + reverse indexes resolve', async (t) => {
  const { registry, cleanup } = await createRegistry()
  const provider = new AtprotoProvider(registry, { verify: async (proof) => proof.binding })

  const result = await provider.authenticate({ proof: { binding: BINDING } })
  t.is(result.method, 'atproto')
  t.is(result.identityId, BINDING.did, 'identityId is the did:plc')

  const byDid = await registry.getIdentityByDid(BINDING.did)
  t.ok(byDid, 'resolvable by did')
  t.is(byDid.method, 'atproto')
  t.is(byDid.credentials.handle, BINDING.handle, 'handle persisted')
  t.is(byDid.credentials.ed25519PublicKey, BINDING.ed25519PublicKey, 'ed25519 binding link persisted')

  const byHandle = await registry.getIdentityByAtprotoHandle(BINDING.handle)
  t.ok(byHandle, 'resolvable by handle (the multi-user lookup)')
  t.is(byHandle.identityId, BINDING.did, 'handle -> did')

  await cleanup()
})

test('re-authenticate is idempotent (no duplicate identity)', async (t) => {
  const { registry, cleanup } = await createRegistry()
  const provider = new AtprotoProvider(registry, { verify: async (proof) => proof.binding })

  const a = await provider.authenticate({ proof: { binding: BINDING } })
  const b = await provider.authenticate({ proof: { binding: BINDING } })
  t.is(a.identityId, b.identityId, 'same identityId on repeat')

  const rec = await registry.getIdentityByDid(BINDING.did)
  t.is(rec.credentials.did, BINDING.did)

  await cleanup()
})

test('authenticate rejects an unverifiable proof', async (t) => {
  const { registry, cleanup } = await createRegistry()
  const provider = new AtprotoProvider(registry, { verify: async () => false })
  await t.exception(() => provider.authenticate({ proof: {} }), /Invalid atproto proof/)
  await cleanup()
})

test('existing methods are untouched (ssh-key still indexes/resolves)', async (t) => {
  const { registry, cleanup } = await createRegistry()
  await registry.registerIdentity({
    identityId: 'ssh-user-1',
    method: 'ssh-key',
    credentials: { fingerprint: 'SHA256:abc123' }
  })
  const byFp = await registry.getIdentityBySSHKey('SHA256:abc123')
  t.ok(byFp, 'ssh-key path unaffected by the atproto additions')
  t.is(byFp.identityId, 'ssh-user-1')
  await cleanup()
})