From 8ab9792ae2f7ac5c277803894977547f428e8207 Mon Sep 17 00:00:00 2001 From: Felipe Forbeck Date: Mon, 2 Dec 2024 15:11:52 -0300 Subject: [PATCH] fix authorizeContentServe export --- packages/w3up-client/src/client.js | 142 +++++++++++---------- packages/w3up-client/src/index.js | 4 +- packages/w3up-client/test/client.test.js | 5 +- packages/w3up-client/test/mocks/service.js | 6 +- 4 files changed, 82 insertions(+), 75 deletions(-) diff --git a/packages/w3up-client/src/client.js b/packages/w3up-client/src/client.js index b331449a3..096dcdbce 100644 --- a/packages/w3up-client/src/client.js +++ b/packages/w3up-client/src/client.js @@ -314,80 +314,13 @@ export class Client extends Base { } for (const serviceConnection of options.authorizeGatewayServices) { - await this.authorizeContentServe(space, serviceConnection) + await authorizeContentServe(this, space, serviceConnection) } } return space } - /** - * Authorizes an audience to serve content from the provided space and record egress events. - * It also publishes the delegation to the content serve service. - * Delegates the following capabilities to the audience: - * - `space/content/serve/*` - * - * @param {import('./types.js').OwnedSpace} space - The space to authorize the audience for. - * @param {import('./types.js').ConnectionView} connection - The connection to the Content Serve Service that will handle, validate, and store the access/delegate UCAN invocation. - * @param {object} [options] - Options for the content serve authorization invocation. - * @param {`did:${string}:${string}`} [options.audience] - The Web DID of the audience (gateway or peer) to authorize. - * @param {number} [options.expiration] - The time at which the delegation expires in seconds from unix epoch. - */ - async authorizeContentServe(space, connection, options = {}) { - const currentSpace = this.currentSpace() - try { - // Set the current space to the space we are authorizing the gateway for, otherwise the delegation will fail - await this.setCurrentSpace(space.did()) - - /** @type {import('@ucanto/client').Principal<`did:${string}:${string}`>} */ - const audience = { - did: () => options.audience ?? connection.id.did(), - } - - // Grant the audience the ability to serve content from the space, it includes existing proofs automatically - const delegation = await this.createDelegation( - audience, - [SpaceCapabilities.contentServe.can], - { - expiration: options.expiration ?? Infinity, - } - ) - - // Publish the delegation to the content serve service - const accessProofs = this.proofs([ - { can: AccessCapabilities.access.can, with: space.did() }, - ]) - const verificationResult = await AccessCapabilities.delegate - .invoke({ - issuer: this._agent.issuer, - audience, - with: space.did(), - proofs: [...accessProofs, delegation], - nb: { - delegations: { - [delegation.cid.toString()]: delegation.cid, - }, - }, - }) - .execute(connection) - - /* c8 ignore next 8 - can't mock this error */ - if (verificationResult.out.error) { - throw new Error( - `failed to publish delegation for audience ${options.audience}: ${verificationResult.out.error.message}`, - { - cause: verificationResult.out.error, - } - ) - } - return { ok: { ...verificationResult.out.ok, delegation } } - } finally { - if (currentSpace) { - await this.setCurrentSpace(currentSpace.did()) - } - } - } - /** * Share an existing space with another Storacha account via email address delegation. * Delegates access to the space to the specified email account with the following permissions: @@ -619,3 +552,76 @@ export class Client extends Base { await this.capability.upload.remove(contentCID) } } + +/** + * Authorizes an audience to serve content from the provided space and record egress events. + * It also publishes the delegation to the content serve service. + * Delegates the following capabilities to the audience: + * - `space/content/serve/*` + * + * @param {Client} client - The w3up client instance. + * @param {import('./types.js').OwnedSpace} space - The space to authorize the audience for. + * @param {import('./types.js').ConnectionView} connection - The connection to the Content Serve Service that will handle, validate, and store the access/delegate UCAN invocation. + * @param {object} [options] - Options for the content serve authorization invocation. + * @param {`did:${string}:${string}`} [options.audience] - The Web DID of the audience (gateway or peer) to authorize. + * @param {number} [options.expiration] - The time at which the delegation expires in seconds from unix epoch. + */ +export const authorizeContentServe = async ( + client, + space, + connection, + options = {} +) => { + const currentSpace = client.currentSpace() + try { + // Set the current space to the space we are authorizing the gateway for, otherwise the delegation will fail + await client.setCurrentSpace(space.did()) + + /** @type {import('@ucanto/client').Principal<`did:${string}:${string}`>} */ + const audience = { + did: () => options.audience ?? connection.id.did(), + } + + // Grant the audience the ability to serve content from the space, it includes existing proofs automatically + const delegation = await client.createDelegation( + audience, + [SpaceCapabilities.contentServe.can], + { + expiration: options.expiration ?? Infinity, + } + ) + + // Publish the delegation to the content serve service + const accessProofs = client.proofs([ + { can: AccessCapabilities.access.can, with: space.did() }, + ]) + const verificationResult = await AccessCapabilities.delegate + .invoke({ + issuer: client.agent.issuer, + audience, + with: space.did(), + proofs: [...accessProofs, delegation], + nb: { + delegations: { + [delegation.cid.toString()]: delegation.cid, + }, + }, + }) + .execute(connection) + + /* c8 ignore next 8 - can't mock this error */ + if (verificationResult.out.error) { + throw new Error( + `failed to publish delegation for audience ${options.audience}: ${verificationResult.out.error.message}`, + { + cause: verificationResult.out.error, + } + ) + } + return { ok: { ...verificationResult.out.ok, delegation } } + } finally { + if (currentSpace) { + await client.setCurrentSpace(currentSpace.did()) + } + } +} diff --git a/packages/w3up-client/src/index.js b/packages/w3up-client/src/index.js index dbc59a80e..da1bcbb14 100644 --- a/packages/w3up-client/src/index.js +++ b/packages/w3up-client/src/index.js @@ -12,6 +12,7 @@ import { Client } from './client.js' export * as Result from './result.js' export * as Account from './account.js' export * from './ability.js' +export { authorizeContentServe } from './client.js' /** * Create a new w3up client. @@ -44,7 +45,4 @@ export async function create(options = {}) { return new Client(data, options) } -export const authorizeContentServe = - Client.prototype.authorizeContentServe.bind(Client.prototype) - export { Client } diff --git a/packages/w3up-client/test/client.test.js b/packages/w3up-client/test/client.test.js index 8b2285fda..b64463bf4 100644 --- a/packages/w3up-client/test/client.test.js +++ b/packages/w3up-client/test/client.test.js @@ -10,7 +10,7 @@ import { import { randomBytes, randomCAR } from './helpers/random.js' import { toCAR } from './helpers/car.js' import { File } from './helpers/shims.js' -import { Client } from '../src/client.js' +import { authorizeContentServe, Client } from '../src/client.js' import * as Test from './test.js' import { receiptsEndpoint } from './helpers/utils.js' import { Absentee } from '@ucanto/principal' @@ -595,7 +595,8 @@ export const testClient = { ).connection // Step 3: Alice authorizes the gateway to serve content from the space - const delegationResult = await aliceClient.authorizeContentServe( + const delegationResult = await authorizeContentServe( + aliceClient, spaceA, gatewayConnection ) diff --git a/packages/w3up-client/test/mocks/service.js b/packages/w3up-client/test/mocks/service.js index 62ee0c37d..4b8805fde 100644 --- a/packages/w3up-client/test/mocks/service.js +++ b/packages/w3up-client/test/mocks/service.js @@ -1,5 +1,6 @@ import * as Client from '@ucanto/client' import * as Server from '@ucanto/server' +import { HTTP } from '@ucanto/transport' import * as CAR from '@ucanto/transport/car' import * as AccessCaps from '@web3-storage/capabilities' @@ -23,8 +24,9 @@ export function getContentServeMockService(result = { ok: {} }) { * * @param {any} id * @param {any} service + * @param {string | undefined} [url] */ -export function getConnection(id, service) { +export function getConnection(id, service, url = undefined) { const server = Server.create({ id: id, service, @@ -34,7 +36,7 @@ export function getConnection(id, service) { const connection = Client.connect({ id: id, codec: CAR.outbound, - channel: server, + channel: url ? HTTP.open({ url: new URL(url) }) : server, }) return { connection }