Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(content-serve): delegate with token caveat #1603

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 44 additions & 3 deletions packages/capabilities/src/space.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,54 @@ 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(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this defined at this level?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that defining it at the top level could be overly restrictive. Allowing its use within the transport/http capability seems like a good approach, as it enables revocation without affecting the ability to serve content or record egress. @Peeja, what are your thoughts on this?

}),
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',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IDK if "transport" is useful, makes it very verbose.

Suggestion: space/content/serve/http

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. I'm not sure now who originally proposed transport and why, but I don't think there was strong reasoning that went with it as much as putting something down.

/**
* 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,
})

Expand Down
4 changes: 4 additions & 0 deletions packages/capabilities/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,9 @@ export type SpaceInfo = InferInvokedCapability<typeof SpaceCaps.info>
export type SpaceContentServe = InferInvokedCapability<
typeof SpaceCaps.contentServe
>
export type SpaceContentServeTransportHTTP = InferInvokedCapability<
typeof SpaceCaps.transportHttp
>
export type EgressRecord = InferInvokedCapability<typeof SpaceCaps.egressRecord>
export type EgressRecordSuccess = {
space: SpaceDID
Expand Down Expand Up @@ -898,6 +901,7 @@ export type ServiceAbilityArray = [
Space['can'],
SpaceInfo['can'],
SpaceContentServe['can'],
SpaceContentServeTransportHTTP['can'],
EgressRecord['can'],
Upload['can'],
UploadAdd['can'],
Expand Down
24 changes: 16 additions & 8 deletions packages/w3up-client/src/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -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<ConnectionView>} [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.
Expand Down Expand Up @@ -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,
})
}
}

Expand Down Expand Up @@ -565,6 +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.token] - The token to use for the content serve verification invocation.
*/
export const authorizeContentServe = async (
client,
Expand All @@ -582,14 +586,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: {
token: options.token ?? null,
},
proofs: client.proofs([
{ can: SpaceCapabilities.contentServe.can, with: space.did() },
]),
})

// Publish the delegation to the content serve service
const accessProofs = client.proofs([
Expand Down
Loading