From f83c6891b15c86227a76a3891c6b8068a1337acd Mon Sep 17 00:00:00 2001 From: Felipe Forbeck Date: Thu, 7 Nov 2024 16:31:20 -0300 Subject: [PATCH] add proofs and identity to the ctx --- package-lock.json | 8 +-- package.json | 2 +- scripts/delegate-serve.js | 14 +++-- src/index.js | 6 +- src/middleware/index.js | 3 + src/middleware/withAuthorizedSpace.js | 32 ++++++---- src/middleware/withAuthorizedSpace.types.ts | 8 ++- src/middleware/withDelegationStubs.js | 31 +++++----- ...ithUcantoClient.js => withEgressClient.js} | 58 ++++++++----------- src/middleware/withEgressClient.types.ts | 20 +++++++ src/middleware/withEgressTracker.js | 28 +++++++-- src/middleware/withEgressTracker.types.ts | 8 +-- src/middleware/withGatewayIdentity.js | 23 ++++++++ src/middleware/withGatewayIdentity.types.ts | 12 ++++ src/middleware/withRateLimit.js | 33 +++++++---- src/middleware/withRateLimit.types.ts | 8 ++- src/middleware/withUcantoClient.types.ts | 24 -------- .../unit/middleware/withEgressTracker.spec.js | 8 +-- tsconfig.json | 3 +- wrangler.toml | 5 +- 20 files changed, 205 insertions(+), 129 deletions(-) rename src/middleware/{withUcantoClient.js => withEgressClient.js} (57%) create mode 100644 src/middleware/withEgressClient.types.ts create mode 100644 src/middleware/withGatewayIdentity.js create mode 100644 src/middleware/withGatewayIdentity.types.ts delete mode 100644 src/middleware/withUcantoClient.types.ts diff --git a/package-lock.json b/package-lock.json index 59d432a..8ebb69d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,7 +44,7 @@ "standard": "^17.1.0", "tree-kill": "^1.2.2", "typescript": "^5.6.3", - "wrangler": "^3.84.1" + "wrangler": "^3.85.0" } }, "node_modules/@achingbrain/ip-address": { @@ -13192,9 +13192,9 @@ "dev": true }, "node_modules/wrangler": { - "version": "3.84.1", - "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-3.84.1.tgz", - "integrity": "sha512-w27/QpIk2qz6aMIVi9T8cDcXMvh/RXjcL+vf4o5J2GpQAE4U7wTCNHyaY9H3oTJWRN97KqCAEbiHBNtTKoUJEw==", + "version": "3.85.0", + "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-3.85.0.tgz", + "integrity": "sha512-r5YCWUaF4ApLnloNE6jHHgRYdFzYHoajTlC1tns42UzQ2Ls63VAqD3b0cxOqzDUfmlSb3skpmu0B0Ssi3QWPAg==", "dev": true, "dependencies": { "@cloudflare/kv-asset-handler": "0.3.4", diff --git a/package.json b/package.json index f743849..cacee3f 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ "standard": "^17.1.0", "tree-kill": "^1.2.2", "typescript": "^5.6.3", - "wrangler": "^3.84.1" + "wrangler": "^3.85.0" }, "standard": { "ignore": [ diff --git a/scripts/delegate-serve.js b/scripts/delegate-serve.js index 3276a1d..c8b9b7e 100644 --- a/scripts/delegate-serve.js +++ b/scripts/delegate-serve.js @@ -7,14 +7,16 @@ const cli = sade('delegate-serve.js [token]') cli .describe( - `Delegates ${serve.star.can} to the Gateway for , 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.` + `Delegates ${serve.star.can} to the Gateway for , 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. If the GATEWAY_PRINCIPAL_KEY environment variable is not set, a new key pair will be generated.` ) .action(async (space, token) => { const client = await getClient() + const signer = + process.env.GATEWAY_PRINCIPAL_KEY + ? ed25519.Signer.parse(process.env.GATEWAY_PRINCIPAL_KEY) + : await ed25519.Signer.generate() - const gatewayIdentity = (await ed25519.Signer.generate()).withDID( - 'did:web:w3s.link' - ) + const gatewayIdentity = signer.withDID('did:web:w3s.link') const delegation = await serve.star.delegate({ issuer: client.agent.issuer, @@ -32,6 +34,10 @@ cli const carResult = await delegation.archive() if (carResult.error) throw carResult.error + process.stdout.write(`Issuer: ${client.agent.issuer.did()}\n`) + process.stdout.write(`Audience: ${gatewayIdentity.did()}\n`) + process.stdout.write(`Space: ${space}\n`) + process.stdout.write(`Token: ${token ?? 'none'}\n`) process.stdout.write(Buffer.from(carResult.ok).toString('base64url')) }) diff --git a/src/index.js b/src/index.js index 5754fbd..43e8479 100644 --- a/src/index.js +++ b/src/index.js @@ -28,6 +28,8 @@ import { } from './middleware/index.js' import { instrument } from '@microlabs/otel-cf-workers' import { NoopSpanProcessor } from '@opentelemetry/sdk-trace-base' +import { withEgressClient } from './middleware/withEgressClient.js' +import { withGatewayIdentity } from './middleware/withGatewayIdentity.js' /** * @import { @@ -57,12 +59,14 @@ const handler = { createWithHttpMethod('GET', 'HEAD'), withAuthToken, withLocator, + withGatewayIdentity, withDelegationStubs, // Rate-limit requests withRateLimit, - // Track egress bytes + // Track Egress + withEgressClient, withEgressTracker, // Fetch data diff --git a/src/middleware/index.js b/src/middleware/index.js index 78fd617..873177c 100644 --- a/src/middleware/index.js +++ b/src/middleware/index.js @@ -6,4 +6,7 @@ export { withVersionHeader } from './withVersionHeader.js' 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 const GATEWAY_DID = 'did:web:w3s.link' \ No newline at end of file diff --git a/src/middleware/withAuthorizedSpace.js b/src/middleware/withAuthorizedSpace.js index 7b51c99..846a1c8 100644 --- a/src/middleware/withAuthorizedSpace.js +++ b/src/middleware/withAuthorizedSpace.js @@ -2,6 +2,7 @@ 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 { Schema } from '@ucanto/client' /** * @import * as Ucanto from '@ucanto/interface' @@ -27,7 +28,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) @@ -57,17 +58,19 @@ export function withAuthorizedSpace (handler) { try { // First space to successfully authorize is the one we'll use. - const space = await Promise.any( + const { space: selectedSpace, delegationProofs } = await Promise.any( spaces.map(async (space) => { const result = await authorize(space, ctx) if (result.error) throw result.error - return space + return result.ok }) ) + debugger return handler(request, env, { ...ctx, - space, - locator: spaceScopedLocator(locator, space) + space: selectedSpace, + delegationProofs, + locator: spaceScopedLocator(locator, selectedSpace) }) } catch (error) { // If all Spaces failed to authorize, throw the first error. @@ -90,7 +93,7 @@ export function withAuthorizedSpace (handler) { * * @param {Ucanto.DID} space * @param {AuthTokenContext & DelegationsStorageContext} ctx - * @returns {Promise>} + * @returns {Promise>} */ const authorize = async (space, ctx) => { // Look up delegations that might authorize us to serve the content. @@ -101,7 +104,7 @@ const authorize = async (space, ctx) => { }) if (relevantDelegationsResult.error) return relevantDelegationsResult - + // Create an invocation of the serve capability. const invocation = await serve.transportHttp .invoke({ @@ -111,23 +114,30 @@ const authorize = async (space, ctx) => { nb: { token: ctx.authToken }, - proofs: relevantDelegationsResult.ok + proofs: relevantDelegationsResult.ok, }) .delegate() // Validate the invocation. + debugger const accessResult = await access(invocation, { capability: serve.transportHttp, authority: ctx.gatewayIdentity, principal: Verifier, - validateAuthorization: () => ok({}) + validateAuthorization: () => ok({}), + resolveDIDKey: () => Schema.ok(ctx.gatewayIdentity.toDIDKey()), }) - + debugger if (accessResult.error) { return accessResult } - return { ok: {} } + return { + ok: { + space, + delegationProofs: relevantDelegationsResult.ok + } + } } /** diff --git a/src/middleware/withAuthorizedSpace.types.ts b/src/middleware/withAuthorizedSpace.types.ts index 9050003..9437d27 100644 --- a/src/middleware/withAuthorizedSpace.types.ts +++ b/src/middleware/withAuthorizedSpace.types.ts @@ -1,10 +1,12 @@ 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 { - /** The identity of the gateway itself */ - gatewayIdentity: Ucanto.Signer +export interface DelegationsStorageContext + extends MiddlewareContext, + GatewayIdentityContext { delegationsStorage: DelegationsStorage + delegationProofs?: Ucanto.Delegation[] } export interface SpaceContext extends MiddlewareContext { diff --git a/src/middleware/withDelegationStubs.js b/src/middleware/withDelegationStubs.js index 4683dd5..411114c 100644 --- a/src/middleware/withDelegationStubs.js +++ b/src/middleware/withDelegationStubs.js @@ -1,8 +1,5 @@ -import * as ed25519 from '@ucanto/principal/ed25519' import { Delegation } from '@ucanto/core' -const GATEWAY_DID = 'did:web:w3s.link' - /** * @import * as Ucanto from '@ucanto/interface' * @import { @@ -11,6 +8,7 @@ const GATEWAY_DID = 'did:web:w3s.link' * } from '@web3-storage/gateway-lib' * @import { DelegationsStorageContext } from './withAuthorizedSpace.types.js' * @import { LocatorContext } from './withLocator.types.js' + * @import { GatewayIdentityContext } from './withGatewayIdentity.types.js' */ /** @@ -25,7 +23,7 @@ const GATEWAY_DID = 'did:web:w3s.link' * @type {( * Middleware< * MiddlewareContext & LocatorContext & DelegationsStorageContext, - * MiddlewareContext & LocatorContext, + * MiddlewareContext & LocatorContext & GatewayIdentityContext, * {} * > * )} @@ -52,26 +50,23 @@ export const withDelegationStubs = (handler) => async (request, env, ctx) => { return handler(request, env, { ...ctx, delegationsStorage: { find: async () => ({ ok: stubDelegations }) }, - // NOTE: It doesn't actually matter right now what key the `gatewayIdentity` - // uses, because we don't need anyone else to execute its invocations. - gatewayIdentity: (await ed25519.Signer.generate()).withDID(GATEWAY_DID), locator: stubSpace && isDIDKey(stubSpace) ? { - locate: async (digest, options) => { - const locateResult = await ctx.locator.locate(digest, options) - if (locateResult.error) return locateResult - return { - ok: { - ...locateResult.ok, - site: locateResult.ok.site.map((site) => ({ - ...site, - space: stubSpace - })) - } + locate: async (digest, options) => { + const locateResult = await ctx.locator.locate(digest, options) + if (locateResult.error) return locateResult + return { + ok: { + ...locateResult.ok, + site: locateResult.ok.site.map((site) => ({ + ...site, + space: stubSpace + })) } } } + } : ctx.locator }) } diff --git a/src/middleware/withUcantoClient.js b/src/middleware/withEgressClient.js similarity index 57% rename from src/middleware/withUcantoClient.js rename to src/middleware/withEgressClient.js index caaae43..9a2b99e 100644 --- a/src/middleware/withUcantoClient.js +++ b/src/middleware/withEgressClient.js @@ -7,33 +7,33 @@ import { Space } from '@web3-storage/capabilities' /** * @import { Middleware } from '@web3-storage/gateway-lib' - * @typedef {import('./withUcantoClient.types.ts').UcantoClientContext} UcantoClientContext - * @typedef {import('./withUcantoClient.types.ts').Environment} Environment + * @typedef {import('./withEgressClient.types.js').EgressClientContext} EgressClientContext + * @typedef {import('./withEgressClient.types.js').Environment} Environment */ /** - * The UCantoClient handler exposes the methods to invoke capabilities on the Upload API. + * The EgressClient handler exposes the methods to invoke capabilities on the Upload API. * - * @type {Middleware} + * @type {Middleware} */ -export function withUcantoClient (handler) { +export function withEgressClient(handler) { return async (req, env, ctx) => { - const ucantoClient = await create(env) + const egressClient = await create(env, ctx) - return handler(req, env, { ...ctx, ucantoClient }) + return handler(req, env, { ...ctx, egressClient }) } } /** - * Creates a UCantoClient instance with the given environment and establishes a connection to the UCanto Server. + * Creates a EgressClient instance with the given environment and establishes a connection to the UCanto Server. * * @param {Environment} env - * @returns {Promise} + * @param {import('./withEgressClient.types.js').EgressClientContext} ctx + * @returns {Promise} */ -async function create (env) { - const service = Verifier.parse(env.SERVICE_ID) - const principal = Signer.parse(env.SIGNER_PRINCIPAL_KEY) - const { connection } = await connect(env.UPLOAD_API_URL, principal) +async function create(env, ctx) { + const principalSigner = ctx.gatewaySigner + const { connection } = await connect(env.UPLOAD_API_URL, principalSigner) return { /** @@ -46,19 +46,8 @@ async function create (env) { * @returns {Promise} */ record: async (space, resource, bytes, servedAt) => - egressRecord(space, resource, bytes, servedAt, principal, service, connection), + egressRecord(space, resource, bytes, servedAt, connection, ctx), - /** - * TODO: implement this function - * - * @param {string} authToken - * @returns {Promise} - */ - getTokenMetadata: async (authToken) => { - // TODO I think this needs to check the content claims service (?) for any claims relevant to this token - // TODO do we have a plan for this? need to ask Hannah if the indexing service covers this? - return undefined - } } } @@ -69,7 +58,7 @@ async function create (env) { * @param {import('@ucanto/principal/ed25519').EdSigner} principal * */ -async function connect (serverUrl, principal) { +async function connect(serverUrl, principal) { const connection = await UCantoClient.connect({ id: principal, codec: CAR.outbound, @@ -80,32 +69,33 @@ async function connect (serverUrl, principal) { } /** - * Records the egress bytes in the UCanto Server by invoking the `Usage.record` capability. + * Records the egress bytes in the UCanto Server by invoking the `Space.egressRecord` capability. * * @param {import('@ucanto/principal/ed25519').DIDKey} space - The Space DID where the content was served * @param {import('@ucanto/principal/ed25519').UnknownLink} resource - The link to the resource that was served * @param {number} bytes - The number of bytes served * @param {Date} servedAt - The timestamp of when the content was served - * @param {import('@ucanto/principal/ed25519').EdSigner} principal - The principal signer - * @param {Signer.Verifier} service - The service verifier * @param {any} connection - The connection to execute the command + * @param {import('./withEgressClient.types.js').EgressClientContext} ctx - The egress client context * @returns {Promise} */ -async function egressRecord (space, resource, bytes, servedAt, principal, service, connection) { +async function egressRecord(space, resource, bytes, servedAt, connection, ctx) { + debugger const res = await Space.egressRecord .invoke({ - issuer: principal, - audience: service, + issuer: ctx.gatewayIdentity, + audience: ctx.gatewayIdentity, // TODO should it be the upload service DID? with: SpaceDID.from(space), nb: { resource, bytes, servedAt: Math.floor(servedAt.getTime() / 1000) - } + }, + proofs: ctx.delegationProofs ? ctx.delegationProofs : [] }) .execute(connection) if (res.out.error) { - console.error('Failed to record egress', res.out.error) + console.error(`Failed to record egress for space ${space}`, res.out.error) } } diff --git a/src/middleware/withEgressClient.types.ts b/src/middleware/withEgressClient.types.ts new file mode 100644 index 0000000..c6ac088 --- /dev/null +++ b/src/middleware/withEgressClient.types.ts @@ -0,0 +1,20 @@ +import { Environment as MiddlewareEnvironment, Context as MiddlewareContext } from '@web3-storage/gateway-lib' +import { DIDKey, UnknownLink } from '@ucanto/principal/ed25519' +import { GatewayIdentityContext } from './withGatewayIdentity.types.js' +import { DelegationsStorageContext } from './withAuthorizedSpace.types.js' + +export interface Environment extends MiddlewareEnvironment { + GATEWAY_PRINCIPAL_KEY: string + UPLOAD_API_URL: string +} + +export interface EgressClientContext + extends MiddlewareContext, + GatewayIdentityContext, + DelegationsStorageContext { + egressClient: EgressClient +} + +export interface EgressClient { + record: (space: DIDKey, resource: UnknownLink, bytes: number, servedAt: Date) => Promise +} diff --git a/src/middleware/withEgressTracker.js b/src/middleware/withEgressTracker.js index 1ffc6cf..1c254a6 100644 --- a/src/middleware/withEgressTracker.js +++ b/src/middleware/withEgressTracker.js @@ -12,12 +12,23 @@ * * @type {Middleware} */ -export function withEgressTracker (handler) { +export function withEgressTracker(handler) { return async (req, env, ctx) => { if (env.FF_EGRESS_TRACKER_ENABLED !== 'true') { return handler(req, env, ctx) } + // If the space is not defined, it is a legacy request and we can't track egress + const space = ctx.space + if (!space) { + return handler(req, env, ctx) + } + + if (!ctx.egressClient) { + console.error('EgressClient is not defined') + return handler(req, env, ctx) + } + const response = await handler(req, env, ctx) if (!response.ok || !response.body) { return response @@ -25,10 +36,15 @@ export function withEgressTracker (handler) { const responseBody = response.body.pipeThrough( createByteCountStream((totalBytesServed) => { - // Non-blocking call to the accounting service to record egress if (totalBytesServed > 0) { + // Non-blocking call to the accounting service to record egress ctx.waitUntil( - ctx.ucantoClient.record(ctx.space, ctx.dataCid, totalBytesServed, new Date()) + ctx.egressClient.record( + /** @type {import('@ucanto/principal/ed25519').DIDKey} */(space), + ctx.dataCid, + totalBytesServed, + new Date() + ) ) } }) @@ -49,7 +65,7 @@ export function withEgressTracker (handler) { * @template {Uint8Array} T * @returns {TransformStream} - The created TransformStream. */ -function createByteCountStream (onClose) { +function createByteCountStream(onClose) { let totalBytes = 0 return new TransformStream({ @@ -59,7 +75,7 @@ function createByteCountStream (onClose) { * If an error occurs, it signals an error to the controller and logs it. * The bytes are not counted in case of enqueuing an error. */ - async transform (chunk, controller) { + async transform(chunk, controller) { try { controller.enqueue(chunk) totalBytes += chunk.byteLength @@ -76,7 +92,7 @@ function createByteCountStream (onClose) { * If an error occurs, the egress is not recorded. * NOTE: The flush function is NOT called in case of a stream error. */ - async flush () { + async flush() { onClose(totalBytes) } }) diff --git a/src/middleware/withEgressTracker.types.ts b/src/middleware/withEgressTracker.types.ts index 6954b89..53a3edd 100644 --- a/src/middleware/withEgressTracker.types.ts +++ b/src/middleware/withEgressTracker.types.ts @@ -1,12 +1,10 @@ import { IpfsUrlContext, Environment as MiddlewareEnvironment } from '@web3-storage/gateway-lib' -import { UCantoClient } from './withUcantoClient.types.js' -import { DIDKey } from '@ucanto/principal/ed25519' +import { EgressClientContext } from './withEgressClient.types.js' +import { SpaceContext } from './withAuthorizedSpace.types.js' export interface Environment extends MiddlewareEnvironment { FF_EGRESS_TRACKER_ENABLED: string } -export interface Context extends IpfsUrlContext { - space: DIDKey - ucantoClient: UCantoClient +export interface Context extends IpfsUrlContext, SpaceContext, EgressClientContext { } diff --git a/src/middleware/withGatewayIdentity.js b/src/middleware/withGatewayIdentity.js new file mode 100644 index 0000000..0c5978c --- /dev/null +++ b/src/middleware/withGatewayIdentity.js @@ -0,0 +1,23 @@ +import { Signer } from '@ucanto/principal/ed25519' +import { GATEWAY_DID } from './index.js' +import { ed25519 } from '@ucanto/principal' + +/** + * @typedef {import('./withGatewayIdentity.types.js').GatewayIdentityContext} GatewayIdentityContext + * @typedef {import('./withGatewayIdentity.types.js').Environment} Environment + */ + +/** + * The GatewayIdentity handler adds the gateway identity to the context. + * + * @type {import('@web3-storage/gateway-lib').Middleware} + */ +export function withGatewayIdentity(handler) { + return async (req, env, ctx) => { + const gatewaySigner = env.GATEWAY_PRINCIPAL_KEY + ? ed25519.Signer.parse(env.GATEWAY_PRINCIPAL_KEY) + : await ed25519.Signer.generate() + debugger + return handler(req, env, { ...ctx, gatewaySigner, gatewayIdentity: gatewaySigner.withDID(GATEWAY_DID) }) + } +} diff --git a/src/middleware/withGatewayIdentity.types.ts b/src/middleware/withGatewayIdentity.types.ts new file mode 100644 index 0000000..538761e --- /dev/null +++ b/src/middleware/withGatewayIdentity.types.ts @@ -0,0 +1,12 @@ +import { Environment as MiddlewareEnvironment, Context as MiddlewareContext } from '@web3-storage/gateway-lib' +import * as Ucanto from '@ucanto/interface' +import { EdSigner } from '@ucanto/principal/ed25519' + +export interface Environment extends MiddlewareEnvironment { + GATEWAY_PRINCIPAL_KEY: string +} + +export interface GatewayIdentityContext extends MiddlewareContext { + gatewaySigner: EdSigner + gatewayIdentity: Ucanto.Signer +} diff --git a/src/middleware/withRateLimit.js b/src/middleware/withRateLimit.js index 9854d7d..f4f8337 100644 --- a/src/middleware/withRateLimit.js +++ b/src/middleware/withRateLimit.js @@ -21,7 +21,7 @@ import { RATE_LIMIT_EXCEEDED } from '../constants.js' * * @type {Middleware} */ -export function withRateLimit (handler) { +export function withRateLimit(handler) { return async (req, env, ctx) => { if (env.FF_RATE_LIMITER_ENABLED !== 'true') { return handler(req, env, ctx) @@ -42,7 +42,7 @@ export function withRateLimit (handler) { * @param {RateLimiterContext} ctx * @returns {RateLimitService} */ -function create (env, ctx) { +function create(env, ctx) { return { /** * @param {import('multiformats/cid').CID} cid @@ -82,7 +82,7 @@ function create (env, ctx) { * @returns {Promise} * @throws {Error} if no rate limit API is found */ -async function isRateLimited (rateLimitAPI, cid) { +async function isRateLimited(rateLimitAPI, cid) { if (!rateLimitAPI) { throw new Error('no rate limit API found') } @@ -99,9 +99,9 @@ async function isRateLimited (rateLimitAPI, cid) { * @param {Environment} env * @param {string} authToken * @param {RateLimiterContext} ctx - * @returns {Promise} + * @returns {Promise} */ -async function getTokenMetadata (env, authToken, ctx) { +async function getTokenMetadata(env, authToken, ctx) { const cachedValue = await env.AUTH_TOKEN_METADATA.get(authToken) // TODO: we should implement an SWR pattern here - record an expiry in the metadata and if the expiry has passed, re-validate the cache after // returning the value @@ -109,7 +109,7 @@ async function getTokenMetadata (env, authToken, ctx) { return decode(cachedValue) } - const tokenMetadata = await ctx.ucantoClient.getTokenMetadata(authToken) + const tokenMetadata = await locateTokenMetadata(authToken) if (tokenMetadata) { // NOTE: non-blocking call to the auth token metadata cache ctx.waitUntil(env.AUTH_TOKEN_METADATA.put(authToken, encode(tokenMetadata))) @@ -121,18 +121,31 @@ async function getTokenMetadata (env, authToken, ctx) { /** * @param {string} s - * @returns {import('./withUcantoClient.types.ts').TokenMetadata} + * @returns {import('./withRateLimit.types.js').TokenMetadata} */ -function decode (s) { +function decode(s) { // TODO should this be dag-json? return JSON.parse(s) } /** - * @param {import('./withUcantoClient.types.ts').TokenMetadata} m + * @param {import('./withRateLimit.types.js').TokenMetadata} m * @returns {string} */ -function encode (m) { +function encode(m) { // TODO should this be dag-json? return JSON.stringify(m) } + + +/** + * TODO: implement this function + * + * @param {string} authToken + * @returns {Promise} + */ +async function locateTokenMetadata(authToken) { + // TODO I think this needs to check the content claims service (?) for any claims relevant to this token + // TODO do we have a plan for this? need to ask Hannah if the indexing service covers this? + return undefined +} \ No newline at end of file diff --git a/src/middleware/withRateLimit.types.ts b/src/middleware/withRateLimit.types.ts index e72e768..284bfc8 100644 --- a/src/middleware/withRateLimit.types.ts +++ b/src/middleware/withRateLimit.types.ts @@ -2,7 +2,7 @@ import { CID } from '@web3-storage/gateway-lib/handlers' import { IpfsUrlContext, Environment as MiddlewareEnvironment } from '@web3-storage/gateway-lib' import { KVNamespace, RateLimit } from '@cloudflare/workers-types' import { RATE_LIMIT_EXCEEDED } from '../constants.js' -import { UCantoClient } from './withUcantoClient.types.js' +import { EgressClient } from './withEgressClient.types.js' export interface Environment extends MiddlewareEnvironment { RATE_LIMITER: RateLimit @@ -10,9 +10,13 @@ export interface Environment extends MiddlewareEnvironment { FF_RATE_LIMITER_ENABLED: string } +export interface TokenMetadata { + locationClaim?: unknown // TODO: figure out the right type to use for this - we probably need it for the private data case to verify auth + invalid?: boolean +} + export interface Context extends IpfsUrlContext { authToken: string | null - ucantoClient: UCantoClient } export type RateLimitExceeded = typeof RATE_LIMIT_EXCEEDED[keyof typeof RATE_LIMIT_EXCEEDED] diff --git a/src/middleware/withUcantoClient.types.ts b/src/middleware/withUcantoClient.types.ts deleted file mode 100644 index 232e1f3..0000000 --- a/src/middleware/withUcantoClient.types.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Environment as MiddlewareEnvironment, Context as MiddlewareContext } from '@web3-storage/gateway-lib' -import { DIDKey, UnknownLink } from '@ucanto/principal/ed25519' - -export interface Environment extends MiddlewareEnvironment { - SERVICE_ID: string - SIGNER_PRINCIPAL_KEY: string - UPLOAD_API_URL: string -} - -export interface UcantoClientContext extends MiddlewareContext { - ucantoClient?: UCantoClient -} - -export interface TokenMetadata { - locationClaim?: unknown // TODO: figure out the right type to use for this - we probably need it for the private data case to verify auth - invalid?: boolean -} - -export interface UCantoClient { - record: (space: DIDKey, resource: UnknownLink, bytes: number, servedAt: Date) => Promise - getTokenMetadata: (token: string) => Promise -} - - diff --git a/test/unit/middleware/withEgressTracker.spec.js b/test/unit/middleware/withEgressTracker.spec.js index d7de042..673e9c6 100644 --- a/test/unit/middleware/withEgressTracker.spec.js +++ b/test/unit/middleware/withEgressTracker.spec.js @@ -29,7 +29,7 @@ const recordEgressMock = sinon.fake() /** * Mock implementation of the AccountingService. * - * @returns {import('../../../src/middleware/withUcantoClient.types.js').UCantoClient} + * @returns {import('../../../src/middleware/withEgressClient.types.js').EgressClient} */ const UCantoClient = () => { if (process.env.DEBUG) { @@ -50,7 +50,7 @@ const ctx = waitUntil: sinon.stub().returns(undefined), path: '', searchParams: new URLSearchParams(), - ucantoClient: UCantoClient() + egressClient: UCantoClient() }) describe('withEgressTracker', async () => { @@ -421,8 +421,8 @@ describe('withEgressTracker', async () => { const request = new Request('http://doesnt-matter.com/') const response = await handler(request, env, { ...ctx, - ucantoClient: { - ...ctx.ucantoClient, + egressClient: { + ...ctx.egressClient, // Simulate an error in the ucanto client record method record: async () => { throw new Error('ucanto client error') } } diff --git a/tsconfig.json b/tsconfig.json index 5c8d202..ed30c85 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,6 +15,7 @@ "skipLibCheck": true, "resolveJsonModule": true, "lib": ["ES2022", "DOM"], - "target": "ES2022" + "target": "ES2022", + "sourceMap": true } } diff --git a/wrangler.toml b/wrangler.toml index 2a54526..0daa705 100644 --- a/wrangler.toml +++ b/wrangler.toml @@ -96,6 +96,7 @@ CONTENT_CLAIMS_SERVICE_URL = "https://dev.claims.web3.storage" [env.fforbeck] name = "freeway-fforbeck" workers_dev = true +upload_source_maps = true # Personal Account # account_id = "9e46c5ddfefedb9bae5d81a0dd911e5a" # Company Account @@ -107,8 +108,10 @@ r2_buckets = [ [env.fforbeck.vars] DEBUG = "true" FF_RATE_LIMITER_ENABLED = "false" -FF_EGRESS_TRACKER_ENABLED = "false" +FF_EGRESS_TRACKER_ENABLED = "true" CONTENT_CLAIMS_SERVICE_URL = "https://staging.claims.web3.storage" +GATEWAY_PRINCIPAL_KEY = "MgCaNpGXCEX0+BxxE4SjSStrxU9Ru/Im+HGNQ/JJx3lDoI+0B3NWjWW3G8OzjbazZjanjM3kgfcZbvpyxv20jHtmcTtg=" +UPLOAD_API_URL = "https://staging.up.web3.storage" [[env.fforbeck.unsafe.bindings]] name = "RATE_LIMITER"