Skip to content

Commit

Permalink
finding delegations in kv
Browse files Browse the repository at this point in the history
  • Loading branch information
fforbeck committed Dec 4, 2024
1 parent adfc6eb commit 7a55163
Show file tree
Hide file tree
Showing 13 changed files with 168 additions and 174 deletions.
10 changes: 4 additions & 6 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ import {
withEgressClient,
withAuthorizedSpace,
withLocator,
withDelegationStubs,
withUcanInvocationHandler
withUcanInvocationHandler,
withDelegationsStorage
} from './middleware/index.js'
import { instrument } from '@microlabs/otel-cf-workers'
import { NoopSpanProcessor } from '@opentelemetry/sdk-trace-base'
Expand All @@ -52,10 +52,11 @@ const middleware = composeMiddleware(
// Prepare the Context for all types of requests
withCdnCache,
withContext,
withCorsHeaders, // TODO: do we need Cors preflight?
withCorsHeaders, // TODO: do we need to add a Cors preflight handler?
withVersionHeader,
withErrorHandler,
withGatewayIdentity,
withDelegationsStorage,
withHttpMethods('GET', 'HEAD', 'POST'),

// Handle UCAN invocations (POST requests only)
Expand All @@ -65,9 +66,6 @@ const middleware = composeMiddleware(
withParsedIpfsUrl,
withAuthToken,
withLocator,

// TODO: replace this with a handler to fetch the real delegations
withDelegationStubs,

// Rate-limit requests
withRateLimit,
Expand Down
4 changes: 2 additions & 2 deletions src/middleware/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ export { withAuthorizedSpace } from './withAuthorizedSpace.js'
export { withLocator } from './withLocator.js'
export { withEgressTracker } from './withEgressTracker.js'
export { withEgressClient } from './withEgressClient.js'
export { withDelegationStubs } from './withDelegationStubs.js'
export { withGatewayIdentity } from './withGatewayIdentity.js'
export { withUcanInvocationHandler } from './withUcanInvocationHandler.js'
export { withUcanInvocationHandler } from './withUcanInvocationHandler.js'
export { withDelegationsStorage } from './withDelegationsStorage.js'
25 changes: 11 additions & 14 deletions src/middleware/withAuthorizedSpace.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@ import { Verifier } from '@ucanto/principal'
import { ok, access, Unauthorized } from '@ucanto/validator'
import { HttpError } from '@web3-storage/gateway-lib/util'
import * as serve from '../capabilities/serve.js'
import { SpaceDID } from '@web3-storage/capabilities/utils'

/**
* @import * as Ucanto from '@ucanto/interface'
* @import { Locator } from '@web3-storage/blob-fetcher'
* @import { IpfsUrlContext, Middleware } from '@web3-storage/gateway-lib'
* @import { LocatorContext } from './withLocator.types.js'
* @import { AuthTokenContext } from './withAuthToken.types.js'
* @import { SpaceContext, DelegationsStorageContext } from './withAuthorizedSpace.types.js'
* @import { SpaceContext } from './withAuthorizedSpace.types.js'
* @import { DelegationsStorageContext } from './withDelegationsStorage.types.js'
*/

/**
Expand All @@ -27,7 +29,7 @@ import * as serve from '../capabilities/serve.js'
* >
* )}
*/
export function withAuthorizedSpace (handler) {
export function withAuthorizedSpace(handler) {
return async (request, env, ctx) => {
const { locator, dataCid } = ctx
const locRes = await locator.locate(dataCid.multihash)
Expand All @@ -47,7 +49,7 @@ export function withAuthorizedSpace (handler) {
ctx.authToken === null

if (shouldServeLegacy) {
return handler(request, env, { ...ctx, space: null })
return handler(request, env, { ...ctx, space: undefined })
}

// These Spaces all have the content we're to serve, if we're allowed to.
Expand All @@ -66,7 +68,7 @@ export function withAuthorizedSpace (handler) {
)
return handler(request, env, {
...ctx,
space: selectedSpace,
space: SpaceDID.from(selectedSpace),
delegationProofs,
locator: spaceScopedLocator(locator, selectedSpace)
})
Expand All @@ -89,18 +91,13 @@ export function withAuthorizedSpace (handler) {
* authorizing delegations in the
* {@link DelegationsStorageContext.delegationsStorage}.
*
* @param {Ucanto.DID} space
* @param {import('@web3-storage/capabilities/types').SpaceDID} space
* @param {AuthTokenContext & DelegationsStorageContext} ctx
* @returns {Promise<Ucanto.Result<{space: Ucanto.DID, delegationProofs: Ucanto.Delegation[]}, Ucanto.Failure>>}
* @returns {Promise<Ucanto.Result<{space: import('@web3-storage/capabilities/types').SpaceDID, delegationProofs: Ucanto.Delegation[]}, Ucanto.Failure>>}
*/
const authorize = async (space, ctx) => {
// Look up delegations that might authorize us to serve the content.
const relevantDelegationsResult = await ctx.delegationsStorage.find({
audience: ctx.gatewayIdentity.did(),
can: serve.transportHttp.can,
with: space
})

const relevantDelegationsResult = await ctx.delegationsStorage.find(space)
if (relevantDelegationsResult.error) return relevantDelegationsResult

// Create an invocation of the serve capability.
Expand All @@ -112,7 +109,7 @@ const authorize = async (space, ctx) => {
nb: {
token: ctx.authToken
},
proofs: relevantDelegationsResult.ok
proofs: [relevantDelegationsResult.ok]
})
.delegate()

Expand All @@ -130,7 +127,7 @@ const authorize = async (space, ctx) => {
return {
ok: {
space,
delegationProofs: relevantDelegationsResult.ok
delegationProofs: [relevantDelegationsResult.ok]
}
}
}
Expand Down
37 changes: 3 additions & 34 deletions src/middleware/withAuthorizedSpace.types.ts
Original file line number Diff line number Diff line change
@@ -1,44 +1,13 @@
import * as Ucanto from '@ucanto/interface'
import { Context as MiddlewareContext } from '@web3-storage/gateway-lib'
import { GatewayIdentityContext as GatewayIdentityContext } from './withGatewayIdentity.types.js'

export interface DelegationsStorageContext
extends MiddlewareContext,
GatewayIdentityContext {
delegationsStorage: DelegationsStorage
/**
* The delegation proofs to use for the egress record
* The proofs must be valid for the space and the owner of the space
* must have delegated the right to the Gateway to serve content and record egress traffic.
* The `space/content/serve/*` capability must be granted to the Gateway Web DID.
*/
delegationProofs: Ucanto.Delegation[]
}
import { SpaceDID } from '@web3-storage/capabilities/types'

export interface SpaceContext extends MiddlewareContext {
space: Ucanto.DID | null
space?: SpaceDID
}

// TEMP: https://github.com/storacha/blob-fetcher/pull/13/files
declare module '@web3-storage/blob-fetcher' {
interface Site {
space?: Ucanto.DID
space?: SpaceDID
}
}

// TEMP

export interface Query {
audience?: Ucanto.DID
can: string
with: Ucanto.Resource
}

export interface DelegationsStorage {
/**
* find all items that match the query
*/
find: (
query: Query
) => Promise<Ucanto.Result<Ucanto.Delegation[], Ucanto.Failure>>
}
81 changes: 0 additions & 81 deletions src/middleware/withDelegationStubs.js

This file was deleted.

79 changes: 79 additions & 0 deletions src/middleware/withDelegationsStorage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { Delegation } from '@ucanto/core'
import { DelegationFailure } from './withDelegationsStorage.types.js'

/**
* @import * as Ucanto from '@ucanto/interface'
* @import {
* Middleware,
* } from '@web3-storage/gateway-lib'
* @import { DelegationsStorageContext, Environment } from './withDelegationsStorage.types.js'
*/

/**
* Provides a delegations storage in the application context
*
* @type {(
* Middleware<DelegationsStorageContext, DelegationsStorageContext, Environment>
* )}
*/
export const withDelegationsStorage = (handler) => async (request, env, ctx) => {
return handler(request, env, {
...ctx,
delegationsStorage: createStorage(env),
delegationProofs: [], // Delegation proofs are set by withAuthorizedSpace handler
locator: ctx.locator
})
}

/**
* @param {Environment} env
* @returns {import('./withDelegationsStorage.types.js').DelegationsStorage}
*/
function createStorage(env) {
return {
/**
* Finds the delegation proofs for the given space
*
* @param {import('@web3-storage/capabilities/types').SpaceDID} space
* @returns {Promise<Ucanto.Result<Ucanto.Delegation<Ucanto.Capabilities>, Ucanto.Failure>>}
*/
find: async (space) => {
if (!space) return { error: { name: 'MissingSpace', message: 'No space provided' } }
const delegation = await env.CONTENT_SERVE_DELEGATIONS_STORE.get(space, 'arrayBuffer')
if (!delegation) return { error: { name: 'DelegationNotFound', message: `No delegation found for space ${space}` } }
const res = await Delegation.extract(new Uint8Array(delegation))
if (res.error) return res
return { ok: res.ok }
},

/**
* Stores the delegation proofs for the given space.
* If the delegation has an expiration, it will be stored with an expiration time in seconds since unix epoch.
*
* @param {import('@web3-storage/capabilities/types').SpaceDID} space
* @param {Ucanto.Delegation<Ucanto.Capabilities>} delegation
* @returns {Promise<Ucanto.Result<Ucanto.Unit, Ucanto.Failure>>}
*/
store: async (space, delegation) => {
try {
let options = {}
if (delegation.expiration && delegation.expiration > 0 && delegation.expiration !== Infinity) {
// expire the key-value pair when the delegation expires (seconds since epoch)
options = { expiration: delegation.expiration }
}

const value = await delegation.archive()
if (value.error) return value

await env.CONTENT_SERVE_DELEGATIONS_STORE.put(space, value.ok.buffer, options)
return { ok: {} }
} catch (error) {
const message = `error while storing delegation for space ${space}`
console.error(message, error)
return {
error: new DelegationFailure(message)
}
}
}
}
}
55 changes: 55 additions & 0 deletions src/middleware/withDelegationsStorage.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import * as Ucanto from '@ucanto/interface'
import { Environment as MiddlewareEnvironment, Context as MiddlewareContext } from '@web3-storage/gateway-lib'
import { KVNamespace } from '@cloudflare/workers-types'
import { SpaceDID } from '@web3-storage/capabilities/types'
import { Failure } from '@ucanto/core'
import { GatewayIdentityContext } from './withGatewayIdentity.types.js'
import { LocatorContext } from './withLocator.types.js'

export interface Environment extends MiddlewareEnvironment {
CONTENT_SERVE_DELEGATIONS_STORE: KVNamespace
}

export class DelegationFailure extends Failure {
get name() {
return /** @type {const} */ ('DelegationFailure')
}
}

export interface DelegationsStorageContext
extends MiddlewareContext,
LocatorContext,
GatewayIdentityContext {
delegationsStorage: DelegationsStorage
/**
* The delegation proofs to use for the egress record
* The proofs must be valid for the space and the owner of the space
* must have delegated the right to the Gateway to serve content and record egress traffic.
* The `space/content/serve/*` capability must be granted to the Gateway Web DID.
*/
delegationProofs: Ucanto.Delegation[]
}

export interface DelegationsStorage {
/**
* Finds the delegation proofs for the given space
*
* @param {import('@web3-storage/capabilities/types').SpaceDID} space
* @returns {Promise<Ucanto.Result<Ucanto.Delegation<Ucanto.Capabilities>, Ucanto.Failure>>}
*/
find: (
space: SpaceDID
) => Promise<Ucanto.Result<Ucanto.Delegation<Ucanto.Capabilities>, Ucanto.Failure>>

/**
* Stores the delegation proofs for the given space
*
* @param {import('@web3-storage/capabilities/types').SpaceDID} space
* @param {Ucanto.Delegation<Ucanto.Capabilities>} delegation
* @returns {Promise<Ucanto.Result<Ucanto.Unit, Ucanto.Failure>>}
*/
store: (
space: SpaceDID,
delegation: Ucanto.Delegation<Ucanto.Capabilities>
) => Promise<Ucanto.Result<Ucanto.Unit, Ucanto.Failure>>
}
Loading

0 comments on commit 7a55163

Please sign in to comment.