Skip to content

Commit 4dc1905

Browse files
committed
Use wildcard capability
1 parent 4e285b4 commit 4dc1905

File tree

8 files changed

+2864
-110
lines changed

8 files changed

+2864
-110
lines changed

package-lock.json

Lines changed: 2788 additions & 46 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
"@web3-storage/content-claims": "^5.0.0",
5656
"@web3-storage/public-bucket": "^1.1.0",
5757
"@web3-storage/upload-client": "^16.1.1",
58+
"@web3-storage/w3cli": "^7.8.2",
5859
"carstream": "^2.1.0",
5960
"chai": "^5.1.1",
6061
"esbuild": "^0.18.20",

scripts/delegate-serve.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
declare module '@web3-storage/w3cli/lib.js' {
2+
import { Client } from '@web3-storage/w3up-client'
3+
export declare function getClient(): Promise<Client>
4+
}

scripts/delegate-serve.js

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,30 @@
11
import sade from 'sade'
22
import { getClient } from '@web3-storage/w3cli/lib.js'
3-
import { serve } from '../src/middleware/withAuthorizedSpace.js'
43
import * as ed25519 from '@ucanto/principal/ed25519'
5-
6-
/** @import { Client } from '@web3-storage/w3up-client' */
4+
import * as serve from '../src/capabilities/serve.js'
75

86
const cli = sade('delegate-serve.js <space> [token]')
97

108
cli
119
.describe(
12-
`Delegates ${serve.can} to the Gateway for <space>, with an optional token. Outputs a base64url string suitable for the stub_delegation query parameter. Pipe the output to pbcopy or similar for the quickest workflow.`
10+
`Delegates ${serve.star.can} to the Gateway for <space>, with an optional token. Outputs a base64url string suitable for the stub_delegation query parameter. Pipe the output to pbcopy or similar for the quickest workflow.`
1311
)
1412
.action(async (space, token) => {
15-
/** @type {Client} */
1613
const client = await getClient()
1714

1815
const gatewayIdentity = (await ed25519.Signer.generate()).withDID(
1916
'did:web:w3s.link'
2017
)
2118

22-
const delegation = await serve.delegate({
19+
const delegation = await serve.star.delegate({
2320
issuer: client.agent.issuer,
2421
audience: gatewayIdentity,
2522
with: space,
2623
nb: { token: token ?? null },
2724
expiration: Infinity,
2825
proofs: client.proofs([
2926
{
30-
can: serve.can,
27+
can: serve.star.can,
3128
with: space
3229
}
3330
])

src/capabilities/serve.js

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { capability, Schema, DID, nullable, string } from '@ucanto/validator'
2+
3+
/**
4+
* "Manage the serving of content owned by the subject Space."
5+
*
6+
* A Principal who may `space/content/serve/*` is permitted to perform all
7+
* operations related to serving content owned by the Space, including actually
8+
* serving it and recording egress charges.
9+
*/
10+
export const star = capability({
11+
can: 'space/content/serve/*',
12+
/**
13+
* The Space which contains the content. This Space will be charged egress
14+
* fees if content is actually retrieved by way of this invocation.
15+
*/
16+
with: DID,
17+
nb: Schema.struct({
18+
/** The authorization token, if any, used for this request. */
19+
token: nullable(string())
20+
})
21+
})
22+
23+
/**
24+
* "Serve content owned by the subject Space over HTTP."
25+
*
26+
* A Principal who may `space/content/serve/transport/http` is permitted to
27+
* serve any content owned by the Space, in the manner of an [IPFS Gateway]. The
28+
* content may be a Blob stored by a Storage Node, or indexed content stored
29+
* within such Blobs (ie, Shards).
30+
*
31+
* Note that the args do not currently specify *what* content should be served.
32+
* Invoking this command does not currently *serve* the content in any way, but
33+
* merely validates the authority to do so. Currently, the entirety of a Space
34+
* must use the same authorization, thus the content does not need to be
35+
* identified. In the future, this command may refer directly to a piece of
36+
* content by CID.
37+
*
38+
* [IPFS Gateway]: https://specs.ipfs.tech/http-gateways/path-gateway/
39+
*/
40+
export const transportHttp = capability({
41+
can: 'space/content/serve/transport/http',
42+
/**
43+
* The Space which contains the content. This Space will be charged egress
44+
* fees if content is actually retrieved by way of this invocation.
45+
*/
46+
with: DID,
47+
nb: Schema.struct({
48+
/** The authorization token, if any, used for this request. */
49+
token: nullable(string())
50+
})
51+
})

src/middleware/withAuthorizedSpace.js

Lines changed: 5 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,7 @@
11
import { Verifier } from '@ucanto/principal'
2-
import {
3-
capability,
4-
Schema,
5-
DID,
6-
nullable,
7-
string,
8-
ok,
9-
access,
10-
Unauthorized
11-
} from '@ucanto/validator'
2+
import { ok, access, Unauthorized } from '@ucanto/validator'
123
import { HttpError } from '@web3-storage/gateway-lib/util'
4+
import * as serve from '../capabilities/serve.js'
135

146
/**
157
* @import * as Ucanto from '@ucanto/interface'
@@ -20,36 +12,6 @@ import { HttpError } from '@web3-storage/gateway-lib/util'
2012
* @import { SpaceContext, DelegationsStorageContext } from './withAuthorizedSpace.types.js'
2113
*/
2214

23-
/**
24-
* "Serve content owned by the subject Space."
25-
*
26-
* A Principal who may `space/content/serve` is permitted to serve any
27-
* content owned by the Space, in the manner of an [IPFS Gateway]. The
28-
* content may be a Blob stored by a Storage Node, or indexed content stored
29-
* within such Blobs (ie, Shards).
30-
*
31-
* Note that the args do not currently specify *what* content should be
32-
* served. Invoking this command does not currently *serve* the content in
33-
* any way, but merely validates the authority to do so. Currently, the
34-
* entirety of a Space must use the same authorization, thus the content does
35-
* not need to be identified. In the future, this command may refer directly
36-
* to a piece of content by CID.
37-
*
38-
* [IPFS Gateway]: https://specs.ipfs.tech/http-gateways/path-gateway/
39-
*/
40-
export const serve = capability({
41-
can: 'space/content/serve',
42-
/**
43-
* The Space which contains the content. This Space will be charged egress
44-
* fees if content is actually retrieved by way of this invocation.
45-
*/
46-
with: DID,
47-
nb: Schema.struct({
48-
/** The authorization token, if any, used for this request. */
49-
token: nullable(string())
50-
})
51-
})
52-
5315
/**
5416
* Attempts to locate the {@link IpfsUrlContext.dataCid}. If it's able to,
5517
* attempts to authorize the request to access the data.
@@ -134,14 +96,14 @@ const authorize = async (space, ctx) => {
13496
// Look up delegations that might authorize us to serve the content.
13597
const relevantDelegationsResult = await ctx.delegationsStorage.find({
13698
audience: ctx.gatewayIdentity.did(),
137-
can: 'space/content/serve',
99+
can: serve.transportHttp.can,
138100
with: space
139101
})
140102

141103
if (relevantDelegationsResult.error) return relevantDelegationsResult
142104

143105
// Create an invocation of the serve capability.
144-
const invocation = await serve
106+
const invocation = await serve.transportHttp
145107
.invoke({
146108
issuer: ctx.gatewayIdentity,
147109
audience: ctx.gatewayIdentity,
@@ -155,7 +117,7 @@ const authorize = async (space, ctx) => {
155117

156118
// Validate the invocation.
157119
const accessResult = await access(invocation, {
158-
capability: serve,
120+
capability: serve.transportHttp,
159121
authority: ctx.gatewayIdentity,
160122
principal: Verifier,
161123
validateAuthorization: () => ok({})

test/unit/middleware/withAuthorizedSpace.spec.js

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,14 @@ import { describe, it } from 'mocha'
77
import { expect } from 'chai'
88
import sinon from 'sinon'
99
import * as ed25519 from '@ucanto/principal/ed25519'
10-
import {
11-
serve,
12-
withAuthorizedSpace
13-
} from '../../../src/middleware/withAuthorizedSpace.js'
10+
import { withAuthorizedSpace } from '../../../src/middleware/withAuthorizedSpace.js'
1411
import * as Digest from 'multiformats/hashes/digest'
1512
import { base64 } from 'multiformats/bases/base64'
1613
import { rejection } from './util/rejection.js'
1714
import { expectToBeInstanceOf } from './util/expectToBeInstanceOf.js'
1815
import { HttpError } from '@web3-storage/gateway-lib/util'
1916
import { createTestCID } from './util/createTestCID.js'
17+
import * as serve from '../../../src/capabilities/serve.js'
2018

2119
/**
2220
* @import { MultihashDigest } from 'multiformats'
@@ -129,7 +127,7 @@ describe('withAuthorizedSpace', async () => {
129127
error: undefined
130128
}),
131129
delegationsStorage: createDelegationStorage([
132-
await serve.delegate({
130+
await serve.transportHttp.delegate({
133131
issuer: space,
134132
audience: gatewayIdentity,
135133
with: space.did(),
@@ -173,7 +171,7 @@ describe('withAuthorizedSpace', async () => {
173171
error: undefined
174172
}),
175173
delegationsStorage: createDelegationStorage([
176-
await serve.delegate({
174+
await serve.transportHttp.delegate({
177175
issuer: space,
178176
audience: gatewayIdentity,
179177
with: space.did(),
@@ -219,7 +217,7 @@ describe('withAuthorizedSpace', async () => {
219217
error: undefined
220218
}),
221219
delegationsStorage: createDelegationStorage([
222-
await serve.delegate({
220+
await serve.transportHttp.delegate({
223221
issuer: space,
224222
audience: gatewayIdentity,
225223
with: space.did(),
@@ -270,14 +268,14 @@ describe('withAuthorizedSpace', async () => {
270268
error: undefined
271269
}),
272270
delegationsStorage: createDelegationStorage([
273-
await serve.delegate({
271+
await serve.transportHttp.delegate({
274272
issuer: space1,
275273
audience: gatewayIdentity,
276274
with: space1.did(),
277275
nb: { token: 'space1-token' }
278276
}),
279277
// No authorization for space2
280-
await serve.delegate({
278+
await serve.transportHttp.delegate({
281279
issuer: space3,
282280
audience: gatewayIdentity,
283281
with: space3.did(),
@@ -395,7 +393,7 @@ describe('withAuthorizedSpace', async () => {
395393
}
396394
}),
397395
delegationsStorage: createDelegationStorage([
398-
await serve.delegate({
396+
await serve.transportHttp.delegate({
399397
issuer: space,
400398
audience: gatewayIdentity,
401399
with: space.did(),
@@ -439,7 +437,7 @@ describe('withAuthorizedSpace', async () => {
439437
}
440438
}),
441439
delegationsStorage: createDelegationStorage([
442-
await serve.delegate({
440+
await serve.transportHttp.delegate({
443441
issuer: space,
444442
audience: gatewayIdentity,
445443
with: space.did(),
@@ -482,7 +480,7 @@ describe('withAuthorizedSpace', async () => {
482480
}
483481
}),
484482
delegationsStorage: createDelegationStorage([
485-
await serve.delegate({
483+
await serve.transportHttp.delegate({
486484
issuer: space,
487485
audience: gatewayIdentity,
488486
with: space.did(),

tsconfig.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
21
{
3-
"include": ["src", "test"],
2+
"include": ["src", "test", "scripts"],
43
"compilerOptions": {
54
"declaration": true,
65
"declarationMap": true,

0 commit comments

Comments
 (0)