diff --git a/package.json b/package.json index 6c72138a..9e4ffdc8 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "devDependencies": { "mocha": "^10.1.0", "prettier": "2.7.1", - "typescript": "4.8.3" + "typescript": "^4.8.4" }, "prettier": { "trailingComma": "es5", diff --git a/packages/core/src/delegation.js b/packages/core/src/delegation.js index 0564afe4..07398831 100644 --- a/packages/core/src/delegation.js +++ b/packages/core/src/delegation.js @@ -130,6 +130,19 @@ export class Delegation { iterate() { return it(this) } + + /** + * @returns {API.DelegationJSON} + */ + toJSON() { + return /** @type {any} */ ({ + ...this.data.toJSON(), + '/': this.cid.toString(), + prf: this.proofs.map(proof => + isDelegation(proof) ? proof : { '/': proof.toString() } + ), + }) + } } /** diff --git a/packages/core/test/delegation.spec.js b/packages/core/test/delegation.spec.js index 5fa38308..da13091e 100644 --- a/packages/core/test/delegation.spec.js +++ b/packages/core/test/delegation.spec.js @@ -1,6 +1,6 @@ import { assert, test } from './test.js' -import { Delegation, UCAN, isDelegation, parseLink } from '../src/lib.js' -import { alice, bob, mallory, service } from './fixtures.js' +import { Delegation, UCAN, delegate, parseLink } from '../src/lib.js' +import { alice, bob, mallory, service as w3 } from './fixtures.js' import { base64 } from 'multiformats/bases/base64' const utf8 = new TextEncoder() @@ -132,3 +132,120 @@ test('delegation.data.toJSON with bytes', async () => { s: { '/': { bytes: base64.baseEncode(ucan.signature) } }, }) }) + +test('toJSON delegation', async () => { + const ucan = await delegate({ + issuer: alice, + audience: w3, + capabilities: [ + { + with: alice.did(), + can: 'test/echo', + nb: { + message: 'data:1', + }, + }, + ], + expiration: Infinity, + }) + + assert.deepEqual(toJSON(ucan), { + '/': ucan.cid.toString(), + v: ucan.version, + iss: alice.did(), + aud: w3.did(), + att: [ + { + nb: { + message: 'data:1', + }, + can: 'test/echo', + with: alice.did(), + }, + ], + exp: null, + prf: [], + s: { + '/': { bytes: base64.baseEncode(ucan.signature) }, + }, + }) +}) + +test('toJSON delegation chain', async () => { + const proof = await delegate({ + issuer: bob, + audience: alice, + capabilities: [ + { + with: bob.did(), + can: 'test/echo', + }, + ], + }) + + const proof2 = await delegate({ + issuer: mallory, + audience: alice, + capabilities: [ + { + with: mallory.did(), + can: 'test/echo', + }, + ], + }) + + const ucan = await delegate({ + issuer: alice, + audience: w3, + capabilities: [ + { + with: bob.did(), + can: 'test/echo', + nb: { + message: 'data:hi', + }, + }, + ], + proofs: [proof, proof2.cid], + }) + + assert.deepEqual(toJSON(ucan), { + '/': ucan.cid.toString(), + v: ucan.version, + iss: alice.did(), + aud: w3.did(), + att: [ + { + with: bob.did(), + can: 'test/echo', + nb: { + message: 'data:hi', + }, + }, + ], + exp: ucan.expiration, + prf: [ + { + '/': proof.cid.toString(), + iss: bob.did(), + aud: alice.did(), + att: [ + { + with: bob.did(), + can: 'test/echo', + }, + ], + exp: proof.expiration, + v: proof.version, + s: { '/': { bytes: base64.baseEncode(proof.signature) } }, + prf: [], + }, + { + '/': proof2.cid.toString(), + }, + ], + s: { + '/': { bytes: base64.baseEncode(ucan.signature) }, + }, + }) +}) diff --git a/packages/interface/src/lib.ts b/packages/interface/src/lib.ts index ad4ab2e7..f97d2b07 100644 --- a/packages/interface/src/lib.ts +++ b/packages/interface/src/lib.ts @@ -17,8 +17,13 @@ import { Principal, MulticodecCode, SigAlg, + ToJSON, + SignatureJSON, + JSONUnknown, + IntoJSON, + JSONObject, } from '@ipld/dag-ucan' -import { Link, Block as IPLDBlock } from 'multiformats' +import { Link, UnknownLink, Block as IPLDBlock, ToString } from 'multiformats' import * as UCAN from '@ipld/dag-ucan' import { CanIssue, @@ -59,6 +64,10 @@ export type { MultibaseEncoder, MulticodecCode, Principal, + ToJSON, + ToString, + UnknownLink, + JSONUnknown, } export * as UCAN from '@ipld/dag-ucan' @@ -161,8 +170,39 @@ export interface Delegation { iterate(): IterableIterator signature: Signature + version: UCAN.Version + + toJSON(): DelegationJSON } +export type DelegationJSON = ToJSON< + T, + { + '/': ToString + v: T['version'] + iss: DID + aud: DID + att: ToJSON< + T['capabilities'], + T['capabilities'] & + UCAN.Tuple<{ with: UCAN.Resource; can: UCAN.Ability; nb?: JSONObject }> + > + exp: T['expiration'] + nbf?: T['notBefore'] & {} + nnc?: T['nonce'] & {} + fct: ToJSON + prf: ProofJSON[] & JSONUnknown[] + s: SignatureJSON + } +> + +export type ProofJSON = DelegationJSON | LinkJSON + +export type LinkJSON = ToJSON< + T, + { '/': ToString } +> + /** * An Invocation represents a UCAN that can be presented to a service provider to * invoke or "exercise" a {@link Capability}. You can think of invocations as a diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8abaaed8..28ad5ae3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,11 +9,11 @@ importers: specifiers: mocha: ^10.1.0 prettier: 2.7.1 - typescript: 4.8.3 + typescript: ^4.8.4 devDependencies: mocha: 10.1.0 prettier: 2.7.1 - typescript: 4.8.3 + typescript: 4.9.4 packages/client: specifiers: @@ -48,7 +48,7 @@ importers: mocha: 10.1.0 nyc: 15.1.0 playwright-test: 8.1.1 - typescript: 4.8.4 + typescript: 4.9.4 packages/core: specifiers: @@ -81,7 +81,7 @@ importers: mocha: 10.1.0 nyc: 15.1.0 playwright-test: 8.1.1 - typescript: 4.8.4 + typescript: 4.9.4 packages/interface: specifiers: @@ -92,7 +92,7 @@ importers: '@ipld/dag-ucan': 3.1.1 multiformats: 10.0.2 devDependencies: - typescript: 4.8.4 + typescript: 4.9.4 packages/principal: specifiers: @@ -123,7 +123,7 @@ importers: mocha: 10.1.0 nyc: 15.1.0 playwright-test: 8.1.1 - typescript: 4.8.4 + typescript: 4.9.4 packages/server: specifiers: @@ -166,7 +166,7 @@ importers: multiformats: 10.0.2 nyc: 15.1.0 playwright-test: 8.1.1 - typescript: 4.8.4 + typescript: 4.9.4 packages/transport: specifiers: @@ -201,7 +201,7 @@ importers: mocha: 10.1.0 nyc: 15.1.0 playwright-test: 8.1.1 - typescript: 4.8.4 + typescript: 4.9.4 packages/validator: specifiers: @@ -240,7 +240,7 @@ importers: mocha: 10.1.0 nyc: 15.1.0 playwright-test: 8.1.1 - typescript: 4.8.4 + typescript: 4.9.4 packages: @@ -2969,14 +2969,8 @@ packages: is-typedarray: 1.0.0 dev: true - /typescript/4.8.3: - resolution: {integrity: sha512-goMHfm00nWPa8UvR/CPSvykqf6dVV8x/dp0c5mFTMTIu0u0FlGWRioyy7Nn0PGAdHxpJZnuO/ut+PpQ8UiHAig==} - engines: {node: '>=4.2.0'} - hasBin: true - dev: true - - /typescript/4.8.4: - resolution: {integrity: sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==} + /typescript/4.9.4: + resolution: {integrity: sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==} engines: {node: '>=4.2.0'} hasBin: true dev: true