From ca1b6da8463b080aa726c2611f641dcc338d5282 Mon Sep 17 00:00:00 2001 From: Felipe Forbeck Date: Tue, 26 Nov 2024 11:36:35 -0300 Subject: [PATCH] feat: authorize gateway --- packages/capabilities/src/types.ts | 24 +++++----- packages/w3up-client/src/client.js | 71 ++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 10 deletions(-) diff --git a/packages/capabilities/src/types.ts b/packages/capabilities/src/types.ts index f3d48c6af..354665924 100644 --- a/packages/capabilities/src/types.ts +++ b/packages/capabilities/src/types.ts @@ -131,16 +131,6 @@ export type UsageReport = InferInvokedCapability export type UsageReportSuccess = Record export type UsageReportFailure = Ucanto.Failure -export type EgressRecord = InferInvokedCapability -export type EgressRecordSuccess = { - space: SpaceDID - resource: UnknownLink - bytes: number - servedAt: ISO8601Date - cause: UnknownLink -} -export type EgressRecordFailure = ConsumerNotFound | Ucanto.Failure - export interface UsageData { /** Provider the report concerns, e.g. `did:web:web3.storage` */ provider: ProviderDID @@ -284,6 +274,18 @@ export type RateLimitListFailure = Ucanto.Failure // Space export type Space = InferInvokedCapability export type SpaceInfo = InferInvokedCapability +export type SpaceContentServe = InferInvokedCapability< + typeof SpaceCaps.contentServe +> +export type EgressRecord = InferInvokedCapability +export type EgressRecordSuccess = { + space: SpaceDID + resource: UnknownLink + bytes: number + servedAt: ISO8601Date + cause: UnknownLink +} +export type EgressRecordFailure = ConsumerNotFound | Ucanto.Failure // filecoin export interface DealMetadata { @@ -895,6 +897,8 @@ export type ServiceAbilityArray = [ ProviderAdd['can'], Space['can'], SpaceInfo['can'], + SpaceContentServe['can'], + EgressRecord['can'], Upload['can'], UploadAdd['can'], UploadGet['can'], diff --git a/packages/w3up-client/src/client.js b/packages/w3up-client/src/client.js index aebdd9d23..956cf2732 100644 --- a/packages/w3up-client/src/client.js +++ b/packages/w3up-client/src/client.js @@ -9,6 +9,7 @@ import { Index as IndexCapabilities, Upload as UploadCapabilities, Filecoin as FilecoinCapabilities, + Space as SpaceCapabilities, } from '@web3-storage/capabilities' import * as DIDMailto from '@web3-storage/did-mailto' import { Base } from './base.js' @@ -253,6 +254,9 @@ export class Client extends Base { * * @typedef {object} CreateOptions * @property {Account.Account} [account] + * @property {boolean} [unauthorizeGateway] - If true, the gateway will not be authorized to serve content from the space. + * @property {`did:web:${string}`} [gateway] - The gateway to be authorized to serve content from the space. If not provided, the default did:web:w3s.link gateway will be authorized. + * @property {number} [gatewayExpiration] - The time in seconds to expire the gateway authorization. * * @param {string} name * @param {CreateOptions} options @@ -291,9 +295,76 @@ export class Client extends Base { ) } } + + if (!options.unauthorizeGateway) { + await this.authorizeGateway(space, { + gateway: options.gateway, + expiration: options.gatewayExpiration, + }) + } + return space } + /** + * Authorizes a gateway to serve content from the provided space and record egress events. + * Delegates the following capabilities to the gateway: + * - `space/content/serve/*` + * + * @param {import('./types.js').OwnedSpace} space - The space to authorize the gateway for. + * @param {object} [options] - Options for the authorization. + * @param {`did:web:${string}`} [options.gateway] - The Web DID of the gateway to authorize. If not provided, the default `did:web:w3s.link` gateway will be authorized. + * @param {number} [options.expiration] - The time in seconds to expire the authorization. + * @returns {Promise} + */ + async authorizeGateway(space, options = {}) { + const currentSpace = this.currentSpace() + try { + if (currentSpace && currentSpace.did() !== space.did()) { + // Set the current space to the space we are authorizing the gateway for + await this.setCurrentSpace(space.did()) + } + + // Authorize the agent to access the space + const authProof = await space.createAuthorization(this.agent) + + // Create a delegation for the Gateway to serve content from the space + const delegation = await this.createDelegation( + /** @type {import('@ucanto/client').Principal<`did:${string}:${string}`>} */ + { + did: () => options.gateway ?? `did:web:w3s.link`, + }, + [SpaceCapabilities.contentServe.can], + { + expiration: options.expiration ?? Infinity, + proofs: [authProof], + } + ) + + // Authorize the gateway to serve content from the space + const result = await this.capability.access.delegate({ + delegations: [delegation], + }) + + if (result.error) { + throw new Error( + `failed to authorize gateway: ${result.error.message}`, + { + cause: result.error, + } + ) + } + + // TODO: save the delegation into the DelegationsStore + // delegation + } finally { + // Reset the current space to the original space + if (currentSpace && currentSpace.did() !== space.did()) { + 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: