OverviewFilesCommitsIssuesDocsReleasesPeers

trust-map / examples / cli.js

184 lines · javascript ref:
#!/usr/bin/env node
/**
 * Mini CLI example for trust-map
 *
 * Usage:
 *   node examples/cli.js init                                    Init storage
 *   node examples/cli.js grant <pubkey> <resource> <perms,...>   Grant perms
 *   node examples/cli.js revoke <pubkey> <resource> [perms,...]  Revoke
 *   node examples/cli.js check <pubkey> <resource> <perm>        Check
 *   node examples/cli.js list                                    List all
 *   node examples/cli.js for <pubkey>                            List for one pubkey
 *   node examples/cli.js demo                                    Generate keypairs and play
 *
 * Storage path: TRUST_MAP_STORE env var, default ./.trust-map-cli/
 *
 * NOTE: this CLI uses its OWN storage path by default. The web example
 * (examples/web.js) uses ./.trust-map-web/ — they don't share state.
 * RocksDB has a single-writer lock, so two processes can't open the same
 * Corestore at once. For shared state across CLI/web/native interfaces in
 * production, embed trust-map in a single daemon (e.g. pear-git-v3) and
 * have all clients talk to the daemon via hyper-svc (planned).
 */

import Corestore from 'corestore'
import crypto from 'hypercore-crypto'
import b4a from 'b4a'
import { TrustMap } from '../index.js'

const args = process.argv.slice(2)
const cmd = args[0]
const storagePath = process.env.TRUST_MAP_STORE || './.trust-map-cli'

async function open () {
  const store = new Corestore(storagePath)
  await store.ready()
  const core = store.get({ name: 'trust-map' })
  await core.ready()
  const tm = new TrustMap({ core })
  await tm.ready()
  return { tm, store, core }
}

async function close ({ tm, store, core }) {
  await tm.close()
  await core.close()
  await store.close()
}

async function main () {
  if (cmd === 'init') {
    const ctx = await open()
    console.log('Initialized trust map at: %s', storagePath)
    await close(ctx)
    return
  }

  if (cmd === 'grant') {
    const [, pubkey, resource, permsStr] = args
    if (!pubkey || !resource || !permsStr) {
      console.error('Usage: cli.js grant <pubkey> <resource> <perm1,perm2,...>')
      process.exit(1)
    }
    const perms = permsStr.split(',').map(s => s.trim())
    const ctx = await open()
    await ctx.tm.grant(pubkey, resource, perms)
    console.log('Granted %s on %s to %s', perms.join(','), resource, pubkey.slice(0, 16) + '...')
    await close(ctx)
    return
  }

  if (cmd === 'revoke') {
    const [, pubkey, resource, permsStr] = args
    if (!pubkey || !resource) {
      console.error('Usage: cli.js revoke <pubkey> <resource> [perm1,perm2,...]')
      process.exit(1)
    }
    const perms = permsStr ? permsStr.split(',').map(s => s.trim()) : undefined
    const ctx = await open()
    await ctx.tm.revoke(pubkey, resource, perms)
    console.log('Revoked %s on %s for %s', perms ? perms.join(',') : '(all)', resource, pubkey.slice(0, 16) + '...')
    await close(ctx)
    return
  }

  if (cmd === 'check') {
    const [, pubkey, resource, perm] = args
    if (!pubkey || !resource || !perm) {
      console.error('Usage: cli.js check <pubkey> <resource> <perm>')
      process.exit(1)
    }
    const ctx = await open()
    const ok = await ctx.tm.check(pubkey, resource, perm)
    console.log('check(%s, %s, %s) = %s', pubkey.slice(0, 16) + '...', resource, perm, ok ? 'YES' : 'NO')
    await close(ctx)
    return
  }

  if (cmd === 'list') {
    const ctx = await open()
    const all = await ctx.tm.list()
    if (all.length === 0) {
      console.log('(empty)')
    } else {
      console.log('Trust map (%d entries):', all.length)
      for (const e of all) {
        console.log('  %s  %s  %s', e.pubkey.slice(0, 16) + '...', e.resource.padEnd(20), e.perms.join(','))
      }
    }
    await close(ctx)
    return
  }

  if (cmd === 'for') {
    const pubkey = args[1]
    if (!pubkey) { console.error('Usage: cli.js for <pubkey>'); process.exit(1) }
    const ctx = await open()
    const entries = await ctx.tm.listForPubkey(pubkey)
    if (entries.length === 0) {
      console.log('No entries for %s', pubkey.slice(0, 16) + '...')
    } else {
      console.log('Entries for %s:', pubkey.slice(0, 16) + '...')
      for (const e of entries) {
        console.log('  %s: %s', e.resource, e.perms.join(','))
      }
    }
    await close(ctx)
    return
  }

  if (cmd === 'demo') {
    const ctx = await open()
    console.log('--- trust-map demo ---\n')

    const alice = b4a.toString(crypto.randomBytes(32), 'hex')
    const bob = b4a.toString(crypto.randomBytes(32), 'hex')
    const ci = b4a.toString(crypto.randomBytes(32), 'hex')

    console.log('Alice:    %s', alice)
    console.log('Bob:      %s', bob)
    console.log('CI bot:   %s\n', ci)

    console.log('1. Grant alice admin on repo-x')
    await ctx.tm.grant(alice, 'repo-x', ['read', 'write', 'admin'])

    console.log('2. Grant bob read+write on repo-x')
    await ctx.tm.grant(bob, 'repo-x', ['read', 'write'])

    console.log('3. Grant CI bot read on ALL repos (wildcard)')
    await ctx.tm.grant(ci, '*', ['read'])
    console.log()

    console.log('Checks:')
    console.log('  alice can admin repo-x:    %s', await ctx.tm.check(alice, 'repo-x', 'admin'))
    console.log('  bob can write repo-x:      %s', await ctx.tm.check(bob, 'repo-x', 'write'))
    console.log('  bob can admin repo-x:      %s (should be NO)', await ctx.tm.check(bob, 'repo-x', 'admin'))
    console.log('  ci can read repo-y:        %s (via wildcard)', await ctx.tm.check(ci, 'repo-y', 'read'))
    console.log('  ci can write repo-y:       %s (should be NO)', await ctx.tm.check(ci, 'repo-y', 'write'))
    console.log()

    console.log('4. Revoke bob\'s write on repo-x')
    await ctx.tm.revoke(bob, 'repo-x', ['write'])
    console.log('  bob can write repo-x:      %s (should be NO now)', await ctx.tm.check(bob, 'repo-x', 'write'))
    console.log('  bob can read repo-x:       %s (still YES)', await ctx.tm.check(bob, 'repo-x', 'read'))

    await close(ctx)
    return
  }

  console.log('trust-map example CLI')
  console.log('  init                                       Initialize storage')
  console.log('  grant <pubkey> <resource> <perms,...>      Grant permissions')
  console.log('  revoke <pubkey> <resource> [perms,...]     Revoke permissions')
  console.log('  check <pubkey> <resource> <perm>           Check a permission')
  console.log('  list                                       List all entries')
  console.log('  for <pubkey>                               List entries for one pubkey')
  console.log('  demo                                       Run a full demo with random keypairs')
  console.log('')
  console.log('Storage: TRUST_MAP_STORE env var (default: ./.trust-map-cli)')
  console.log('Note: this CLI does NOT share state with examples/web.js — they use')
  console.log('different storage paths because RocksDB only allows one writer.')
}

main().catch((err) => { console.error(err.message); process.exit(1) })