From 1abd7b34700573a722d7ae12ae143d410649c181 Mon Sep 17 00:00:00 2001 From: Travis Vachon Date: Tue, 17 Oct 2023 15:18:20 -0700 Subject: [PATCH] wip: initial work toward `revoke` command needs new versions of access-client and w3up-client from this PR to build cleanly: https://github.com/web3-storage/w3up/pull/975 --- README.md | 7 +++++++ bin.js | 6 ++++++ index.js | 34 ++++++++++++++++++++++++++++++++++ test/bin.spec.js | 33 ++++++++++++++++++++++++++++++++- test/helpers/mocks.js | 4 ++++ 5 files changed, 83 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d95c20b..8087345 100644 --- a/README.md +++ b/README.md @@ -184,6 +184,13 @@ List delegations created by this agent for others. * `--json` Format as newline delimited JSON + +### `w3 delegation revoke ` + +Revoke a delegation by CID. + +* `--proof` Name of a file containing the delegation and any additional proofs needed to prove authority to revoke + ### `w3 proof add ` Add a proof delegated to this agent. The proof is a CAR encoded delegation to _this_ agent. Note: you probably want to use `w3 space add` unless you know the delegation you received targets a resource _other_ than a w3 space. diff --git a/bin.js b/bin.js index fedb8f3..56a0f0e 100755 --- a/bin.js +++ b/bin.js @@ -14,6 +14,7 @@ import { spaceInfo, createDelegation, listDelegations, + revokeDelegation, addProof, listProofs, upload, @@ -117,6 +118,11 @@ cli.command('delegation ls') .option('--json', 'Format as newline delimited JSON') .action(listDelegations) +cli.command('delegation revoke ') + .describe('Revoke a delegation by CID.') + .option('-p, --proof', 'Name of a file containing the delegation and any additional proofs needed to prove authority to revoke') + .action(revokeDelegation) + cli.command('proof add ') .describe('Add a proof delegated to this agent.') .option('--json', 'Format as newline delimited JSON') diff --git a/index.js b/index.js index b31105d..7817217 100644 --- a/index.js +++ b/index.js @@ -378,6 +378,40 @@ export async function listDelegations (opts) { } } +/** + * @param {string} delegationCid + * @param {object} opts + * @param {string} [opts.proof] + */ +export async function revokeDelegation (delegationCid, opts) { + const client = await getClient() + let proof + try { + if (opts.proof) { + proof = await readProof(opts.proof) + } + } catch (/** @type {any} */err) { + console.log(`Error: ${err.message}`) + process.exit(1) + } + let cid + try { + // TODO: we should valiate that this is a UCANLink + cid = ucanto.parseLink(delegationCid.trim()) + } catch (/** @type {any} */err) { + console.error(`Error: ${delegationCid} is not a CID`) + process.exit(1) + } + try { + await client.revokeDelegation(/** @type {import('@ucanto/interface').UCANLink} */(cid), { proofs: proof ? [proof] : [] }) + console.log(`⁂ delegation ${delegationCid} revoked`) + } catch (/** @type {any} */err) { + console.error(`error trying to revoke ${delegationCid}: ${err.message}`) + console.error(err) + process.exit(1) + } +} + /** * @param {string} proofPath * @param {{ json?: boolean, 'dry-run'?: boolean }} [opts] diff --git a/test/bin.spec.js b/test/bin.spec.js index c35b8d1..01e7dff 100644 --- a/test/bin.spec.js +++ b/test/bin.spec.js @@ -11,6 +11,7 @@ import * as DID from '@ipld/dag-ucan/did' import * as StoreCapabilities from '@web3-storage/capabilities/store' import * as UploadCapabilities from '@web3-storage/capabilities/upload' import * as SpaceCapabilities from '@web3-storage/capabilities/space' +import * as UCANCapabilities from '@web3-storage/capabilities/ucan' import * as Link from 'multiformats/link' import { CarReader } from '@ipld/car' import { StoreConf } from '@web3-storage/access/stores/store-conf' @@ -39,7 +40,8 @@ test.beforeEach(async t => { const server = createServer({ id: serviceSigner, service, - codec: CAR.inbound + codec: CAR.inbound, + validateAuthorization: () => ok({}) }) setRequestListener(createHTTPListener(server)) } @@ -369,6 +371,35 @@ test('w3 delegation ls', async t => { t.is(delegationData.capabilities[0].can, '*') }) +test.only('w3 delegation revoke', async t => { + const env = t.context.env.alice + const service = mockService({ + ucan: { + revoke: provide(UCANCapabilities.revoke, () => { + return ok({ time: Date.now() }) + }) + } + }) + t.context.setService(service) + + await execa('./bin.js', ['space', 'create'], { env }) + + const bob = await Signer.generate() + await execa('./bin.js', ['delegation', 'create', bob.did(), '-c', '*'], { env }) + + const out1 = await execa('./bin.js', ['delegation', 'ls', '--json'], { env }) + const delegationData = JSON.parse(out1.stdout) + + // alice should be able to revoke the delegation she just created + const out2 = await execa('./bin.js', ['delegation', 'revoke', delegationData.cid], { env }) + t.regex(out2.stdout, new RegExp(`delegation ${delegationData.cid} revoked`)) + + // but bob should not be able to revoke alice's delegation + /** @type {any} */ + const out3 = await t.throwsAsync(() => execa('./bin.js', ['delegation', 'revoke', delegationData.cid], { env: t.context.env.bob })) + t.regex(out3.stderr, new RegExp(`error trying to revoke ${delegationData.cid}: could not find delegation ${delegationData.cid}`)) +}) + test('w3 space add', async t => { const aliceEnv = t.context.env.alice const bobEnv = t.context.env.bob diff --git a/test/helpers/mocks.js b/test/helpers/mocks.js index bb5a6f0..4f080b0 100644 --- a/test/helpers/mocks.js +++ b/test/helpers/mocks.js @@ -9,6 +9,7 @@ const notImplemented = () => { * store: Partial * upload: Partial * space: Partial + * ucan: Partial * }>} impl */ export function mockService (impl) { @@ -25,6 +26,9 @@ export function mockService (impl) { }, space: { info: withCallCount(impl.space?.info ?? notImplemented) + }, + ucan: { + revoke: withCallCount(impl.ucan?.revoke ?? notImplemented) } } }