From 275bc2439d81d0822c03ac62ba56f63d965d2622 Mon Sep 17 00:00:00 2001 From: Irakli Gozalishvili Date: Fri, 20 Jan 2023 15:35:35 -0800 Subject: [PATCH] feat: support execution of materialized invocations (#199) * feat: support execution of delegations * chore: add one more test --- packages/client/test/client.spec.js | 39 ++++++++++++- packages/core/src/delegation.js | 4 ++ packages/core/test/delegation.spec.js | 19 +++++++ packages/interface/src/lib.ts | 8 ++- packages/transport/src/car.js | 2 +- packages/transport/src/jwt.js | 2 +- packages/transport/test/car.spec.js | 81 +++++++++++++-------------- packages/transport/test/jwt.spec.js | 72 ++++++++++-------------- 8 files changed, 139 insertions(+), 88 deletions(-) diff --git a/packages/client/test/client.spec.js b/packages/client/test/client.spec.js index 09ce87e4..9f6dc830 100644 --- a/packages/client/test/client.spec.js +++ b/packages/client/test/client.spec.js @@ -7,7 +7,7 @@ import * as Service from './service.js' import { alice, bob, mallory, service as w3 } from './fixtures.js' import fetch from '@web-std/fetch' -test('encode inovocation', async () => { +test('encode invocation', async () => { /** @type {Client.ConnectionView} */ const connection = Client.connect({ id: w3, @@ -225,3 +225,40 @@ test('execute', async () => { url: 'http://localhost:9090/', }) }) + +test('execute with delegations', async () => { + const car = await CAR.codec.write({ + roots: [await CBOR.codec.write({ hello: 'world ' })], + }) + + const add = Client.invoke({ + issuer: bob, + audience: w3, + capability: { + can: 'store/add', + with: bob.did(), + nb: { link: car.cid }, + }, + proofs: [], + }) + + const [e1] = await connection.execute(await add.delegate()) + + assert.deepEqual(e1, { + error: true, + name: 'UnknownDIDError', + message: `DID ${bob.did()} has no account`, + did: bob.did(), + }) + + // fake register alice + service.access.accounts.register(bob.did(), 'did:email:bob@web.mail', car.cid) + + const [r1] = await connection.execute(await add.delegate()) + assert.deepEqual(r1, { + with: bob.did(), + link: car.cid, + status: 'upload', + url: 'http://localhost:9090/', + }) +}) diff --git a/packages/core/src/delegation.js b/packages/core/src/delegation.js index 07398831..58c2e485 100644 --- a/packages/core/src/delegation.js +++ b/packages/core/src/delegation.js @@ -131,6 +131,10 @@ export class Delegation { return it(this) } + delegate() { + return this + } + /** * @returns {API.DelegationJSON} */ diff --git a/packages/core/test/delegation.spec.js b/packages/core/test/delegation.spec.js index da13091e..52be65d4 100644 --- a/packages/core/test/delegation.spec.js +++ b/packages/core/test/delegation.spec.js @@ -249,3 +249,22 @@ test('toJSON delegation chain', async () => { }, }) }) + +test('.delegate() return same value', async () => { + const ucan = await delegate({ + issuer: alice, + audience: w3, + capabilities: [ + { + with: alice.did(), + can: 'test/echo', + nb: { + message: 'data:1', + }, + }, + ], + expiration: Infinity, + }) + + assert.equal(ucan.delegate(), ucan) +}) diff --git a/packages/interface/src/lib.ts b/packages/interface/src/lib.ts index f97d2b07..310694cc 100644 --- a/packages/interface/src/lib.ts +++ b/packages/interface/src/lib.ts @@ -173,6 +173,7 @@ export interface Delegation { version: UCAN.Version toJSON(): DelegationJSON + delegate(): Await> } export type DelegationJSON = ToJSON< @@ -229,13 +230,14 @@ export interface InvocationOptions capability: C } -export interface IssuedInvocation - extends DelegationOptions<[C]> { - readonly issuer: Signer +export interface IssuedInvocation { + readonly issuer: Principal readonly audience: Principal readonly capabilities: [C] readonly proofs: Proof[] + + delegate(): Await> } export type ServiceInvocation< diff --git a/packages/transport/src/car.js b/packages/transport/src/car.js index 05842571..b0cfc172 100644 --- a/packages/transport/src/car.js +++ b/packages/transport/src/car.js @@ -20,7 +20,7 @@ export const encode = async (invocations, options) => { const roots = [] const blocks = new Map() for (const invocation of invocations) { - const delegation = await Delegation.delegate(invocation, options) + const delegation = await invocation.delegate() roots.push(delegation.root) for (const block of delegation.export()) { blocks.set(block.cid.toString(), block) diff --git a/packages/transport/src/jwt.js b/packages/transport/src/jwt.js index f4bdf254..06656973 100644 --- a/packages/transport/src/jwt.js +++ b/packages/transport/src/jwt.js @@ -21,7 +21,7 @@ export const encode = async batch => { /** @type {string[]} */ const body = [] for (const invocation of batch) { - const delegation = await Delegation.delegate(invocation) + const delegation = await invocation.delegate() body.push(`${delegation.cid}`) for (const proof of iterate(delegation)) { diff --git a/packages/transport/test/car.spec.js b/packages/transport/test/car.spec.js index 737acf37..14e195d4 100644 --- a/packages/transport/test/car.spec.js +++ b/packages/transport/test/car.spec.js @@ -1,7 +1,14 @@ import { test, assert } from './test.js' import * as CAR from '../src/car.js' import * as CBOR from '../src/cbor.js' -import { delegate, Delegation, UCAN, parseLink, isLink } from '@ucanto/core' +import { + delegate, + invoke, + Delegation, + UCAN, + parseLink, + isLink, +} from '@ucanto/core' import * as UTF8 from '../src/utf8.js' import { alice, bob, mallory, service } from './fixtures.js' import { CarReader } from '@ipld/car/reader' @@ -15,18 +22,16 @@ test('encode / decode', async () => { const expiration = 1654298135 const request = await CAR.encode([ - { + invoke({ issuer: alice, audience: bob, - capabilities: [ - { - can: 'store/add', - with: alice.did(), - }, - ], + capability: { + can: 'store/add', + with: alice.did(), + }, expiration, proofs: [], - }, + }), ]) assert.deepEqual(request.headers, { @@ -58,17 +63,15 @@ test('encode / decode', async () => { test('decode requires application/car contet type', async () => { const { body } = await CAR.encode([ - { + invoke({ issuer: alice, audience: bob, - capabilities: [ - { - can: 'store/add', - with: alice.did(), - }, - ], + capability: { + can: 'store/add', + with: alice.did(), + }, proofs: [], - }, + }), ]) try { @@ -87,18 +90,16 @@ test('decode requires application/car contet type', async () => { test('accepts Content-Type as well', async () => { const expiration = UCAN.now() + 90 const request = await CAR.encode([ - { + invoke({ issuer: alice, audience: bob, - capabilities: [ - { - can: 'store/add', - with: alice.did(), - }, - ], + capability: { + can: 'store/add', + with: alice.did(), + }, proofs: [], expiration, - }, + }), ]) const [invocation] = await CAR.decode({ @@ -121,6 +122,8 @@ test('accepts Content-Type as well', async () => { expiration, }) + assert.deepEqual({ ...request }, { ...(await CAR.encode([delegation])) }) + assert.deepEqual(invocation.bytes, delegation.bytes) }) @@ -139,18 +142,16 @@ test('delegated proofs', async () => { const expiration = UCAN.now() + 90 const outgoing = await CAR.encode([ - { + invoke({ issuer: bob, audience: service, - capabilities: [ - { - can: 'store/add', - with: alice.did(), - }, - ], + capability: { + can: 'store/add', + with: alice.did(), + }, proofs: [proof], expiration, - }, + }), ]) const reader = await CarReader.fromBytes(outgoing.body) @@ -194,18 +195,16 @@ test('omit proof', async () => { const expiration = UCAN.now() + 90 const outgoing = await CAR.encode([ - { + invoke({ issuer: bob, audience: service, - capabilities: [ - { - can: 'store/add', - with: alice.did(), - }, - ], + capability: { + can: 'store/add', + with: alice.did(), + }, proofs: [proof.cid], expiration, - }, + }), ]) const reader = await CarReader.fromBytes(outgoing.body) diff --git a/packages/transport/test/jwt.spec.js b/packages/transport/test/jwt.spec.js index 36b58e75..9c03c82c 100644 --- a/packages/transport/test/jwt.spec.js +++ b/packages/transport/test/jwt.spec.js @@ -1,6 +1,6 @@ import { test, assert } from './test.js' import * as JWT from '../src/jwt.js' -import { delegate, Delegation, UCAN } from '@ucanto/core' +import { delegate, invoke, Delegation, UCAN } from '@ucanto/core' import * as UTF8 from '../src/utf8.js' import { alice, bob, mallory, service } from './fixtures.js' import * as API from '@ucanto/interface' @@ -19,18 +19,16 @@ test('encode / decode', async () => { const { cid, jwt } = fixtures.basic const request = await JWT.encode([ - { + invoke({ issuer: alice, audience: bob, - capabilities: [ - { - can: 'store/add', - with: alice.did(), - }, - ], + capability: { + can: 'store/add', + with: alice.did(), + }, expiration: NOW, proofs: [], - }, + }), ]) const expect = { @@ -94,18 +92,16 @@ test('delegated proofs', async () => { const expiration = UCAN.now() + 90 const outgoing = await JWT.encode([ - { + invoke({ issuer: bob, audience: service, - capabilities: [ - { - can: 'store/add', - with: alice.did(), - }, - ], + capability: { + can: 'store/add', + with: alice.did(), + }, proofs: [proof], expiration, - }, + }), ]) assert.equal(Object.keys(outgoing.headers).length, 3) @@ -145,18 +141,16 @@ test('omit proof', async () => { const expiration = UCAN.now() + 90 const outgoing = await JWT.encode([ - { + invoke({ issuer: bob, audience: service, - capabilities: [ - { - can: 'store/add', - with: alice.did(), - }, - ], + capability: { + can: 'store/add', + with: alice.did(), + }, proofs: [proof.cid], expiration, - }, + }), ]) assert.equal(Object.keys(outgoing.headers).length, 2) @@ -196,18 +190,16 @@ test('thorws on invalid heard', async () => { const expiration = UCAN.now() + 90 const request = await JWT.encode([ - { + invoke({ issuer: bob, audience: service, - capabilities: [ - { - can: 'store/add', - with: alice.did(), - }, - ], + capability: { + can: 'store/add', + with: alice.did(), + }, proofs: [proof], expiration, - }, + }), ]) const { [`x-auth-${proof.cid}`]: jwt, ...headers } = request.headers @@ -242,18 +234,16 @@ test('leaving out root throws', async () => { const expiration = UCAN.now() + 90 const request = await JWT.encode([ - { + invoke({ issuer: bob, audience: service, - capabilities: [ - { - can: 'store/add', - with: alice.did(), - }, - ], + capability: { + can: 'store/add', + with: alice.did(), + }, proofs: [proof], expiration, - }, + }), ]) const { cid } = await delegate({