From 00522ac3fbb6ec1ba6738553aa8eab74ee1f47d3 Mon Sep 17 00:00:00 2001 From: Felipe Forbeck Date: Mon, 9 Dec 2024 12:14:20 -0300 Subject: [PATCH 1/2] feat(content-serve): delegate with auth token caveat --- packages/capabilities/src/space.js | 3 +++ packages/w3up-client/src/client.js | 19 ++++++++++++------- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/packages/capabilities/src/space.js b/packages/capabilities/src/space.js index 0f638d3d6..a41a4aa64 100644 --- a/packages/capabilities/src/space.js +++ b/packages/capabilities/src/space.js @@ -72,6 +72,9 @@ export const allocate = capability({ export const contentServe = capability({ can: 'space/content/serve/*', with: SpaceDID, + nb: Schema.struct({ + authToken: Schema.string().optional(), + }), derives: equalWith, }) diff --git a/packages/w3up-client/src/client.js b/packages/w3up-client/src/client.js index 096dcdbce..d0690608b 100644 --- a/packages/w3up-client/src/client.js +++ b/packages/w3up-client/src/client.js @@ -565,6 +565,7 @@ export class Client extends Base { * @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. + * @param {string} [options.authToken] - The auth token to use for the content serve authorization invocation. */ export const authorizeContentServe = async ( client, @@ -582,14 +583,18 @@ export const authorizeContentServe = async ( 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( + const delegation = await SpaceCapabilities.contentServe.delegate({ + issuer: client.agent.issuer, audience, - [SpaceCapabilities.contentServe.can], - { - expiration: options.expiration ?? Infinity, - } - ) + with: space.did(), + expiration: options.expiration ?? Infinity, + nb: { + authToken: options.authToken, + }, + proofs: client.proofs([ + { can: SpaceCapabilities.contentServe.can, with: space.did() }, + ]), + }) // Publish the delegation to the content serve service const accessProofs = client.proofs([ From 3f76be3650ecba83572aada6b722e59bbc92b0ee Mon Sep 17 00:00:00 2001 From: Felipe Forbeck Date: Mon, 9 Dec 2024 13:55:30 -0300 Subject: [PATCH 2/2] added transport/http + fix caveats --- packages/capabilities/src/space.js | 46 +++++++++++++++++++++++++++--- packages/capabilities/src/types.ts | 4 +++ packages/w3up-client/src/client.js | 9 ++++-- 3 files changed, 52 insertions(+), 7 deletions(-) diff --git a/packages/capabilities/src/space.js b/packages/capabilities/src/space.js index a41a4aa64..1f0bbb216 100644 --- a/packages/capabilities/src/space.js +++ b/packages/capabilities/src/space.js @@ -65,15 +65,53 @@ export const allocate = capability({ }) /** - * The capability grants permission for all content serve operations that fall under the "space/content/serve" namespace. - * It can be derived from any of the `space/*` capability that has matching `with`. + * "Manage the serving of content owned by the subject Space." + * + * A Principal who may `space/content/serve/*` is permitted to perform all + * operations related to serving content owned by the Space, including actually + * serving it and recording egress charges. */ - export const contentServe = capability({ can: 'space/content/serve/*', + /** + * The Space which contains the content. This Space will be charged egress + * fees if content is actually retrieved by way of this invocation. + */ + with: SpaceDID, + nb: Schema.struct({ + /** The authorization token, if any, used for this request. */ + token: Schema.string().nullable(), + }), + derives: equalWith, +}) + +/** + * "Serve content owned by the subject Space over HTTP." + * + * A Principal who may `space/content/serve/transport/http` is permitted to + * serve any content owned by the Space, in the manner of an [IPFS Gateway]. The + * content may be a Blob stored by a Storage Node, or indexed content stored + * within such Blobs (ie, Shards). + * + * Note that the args do not currently specify *what* content should be served. + * Invoking this command does not currently *serve* the content in any way, but + * merely validates the authority to do so. Currently, the entirety of a Space + * must use the same authorization, thus the content does not need to be + * identified. In the future, this command may refer directly to a piece of + * content by CID. + * + * [IPFS Gateway]: https://specs.ipfs.tech/http-gateways/path-gateway/ + */ +export const transportHttp = capability({ + can: 'space/content/serve/transport/http', + /** + * The Space which contains the content. This Space will be charged egress + * fees if content is actually retrieved by way of this invocation. + */ with: SpaceDID, nb: Schema.struct({ - authToken: Schema.string().optional(), + /** The authorization token, if any, used for this request. */ + token: Schema.string().nullable(), }), derives: equalWith, }) diff --git a/packages/capabilities/src/types.ts b/packages/capabilities/src/types.ts index 354665924..b436cea20 100644 --- a/packages/capabilities/src/types.ts +++ b/packages/capabilities/src/types.ts @@ -277,6 +277,9 @@ export type SpaceInfo = InferInvokedCapability export type SpaceContentServe = InferInvokedCapability< typeof SpaceCaps.contentServe > +export type SpaceContentServeTransportHTTP = InferInvokedCapability< + typeof SpaceCaps.transportHttp +> export type EgressRecord = InferInvokedCapability export type EgressRecordSuccess = { space: SpaceDID @@ -898,6 +901,7 @@ export type ServiceAbilityArray = [ Space['can'], SpaceInfo['can'], SpaceContentServe['can'], + SpaceContentServeTransportHTTP['can'], EgressRecord['can'], Upload['can'], UploadAdd['can'], diff --git a/packages/w3up-client/src/client.js b/packages/w3up-client/src/client.js index d0690608b..8d65c4083 100644 --- a/packages/w3up-client/src/client.js +++ b/packages/w3up-client/src/client.js @@ -262,6 +262,7 @@ export class Client extends Base { * @property {Account.Account} [account] - The account configured as the recovery account for the space. * @property {Array} [authorizeGatewayServices] - The DID Key or DID Web of the Gateway to authorize to serve content from the created space. * @property {boolean} [skipGatewayAuthorization] - Whether to skip the Gateway authorization. It means that the content of the space will not be served by any Gateway. + * @property {string} [token] - The token to use for the content serve verification invocation. * * @param {string} name - The name of the space to create. * @param {SpaceCreateOptions} options - Options for the space creation. @@ -314,7 +315,9 @@ export class Client extends Base { } for (const serviceConnection of options.authorizeGatewayServices) { - await authorizeContentServe(this, space, serviceConnection) + await authorizeContentServe(this, space, serviceConnection, { + token: options.token, + }) } } @@ -565,7 +568,7 @@ export class Client extends Base { * @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. - * @param {string} [options.authToken] - The auth token to use for the content serve authorization invocation. + * @param {string} [options.token] - The token to use for the content serve verification invocation. */ export const authorizeContentServe = async ( client, @@ -589,7 +592,7 @@ export const authorizeContentServe = async ( with: space.did(), expiration: options.expiration ?? Infinity, nb: { - authToken: options.authToken, + token: options.token ?? null, }, proofs: client.proofs([ { can: SpaceCapabilities.contentServe.can, with: space.did() },