tapimo 0.4.1 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/ApiKey.d.ts +1 -9
- package/dist/ApiKey.d.ts.map +1 -1
- package/dist/ApiKey.js +2 -3
- package/dist/ApiKey.js.map +1 -1
- package/dist/ApiKeys.d.ts +53 -27
- package/dist/ApiKeys.d.ts.map +1 -1
- package/dist/ApiKeys.js +32 -17
- package/dist/ApiKeys.js.map +1 -1
- package/dist/App.d.ts +2451 -1533
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js +23 -9
- package/dist/App.js.map +1 -1
- package/dist/Assets.d.ts +10 -0
- package/dist/Assets.d.ts.map +1 -1
- package/dist/Assets.js +4 -0
- package/dist/Assets.js.map +1 -1
- package/dist/Auth.d.ts +2 -1
- package/dist/Auth.d.ts.map +1 -1
- package/dist/Auth.js +2 -1
- package/dist/Auth.js.map +1 -1
- package/dist/Scope.d.ts +40 -15
- package/dist/Scope.d.ts.map +1 -1
- package/dist/Scope.js +44 -13
- package/dist/Scope.js.map +1 -1
- package/dist/admin/App.d.ts +560 -9
- package/dist/admin/App.d.ts.map +1 -1
- package/dist/admin/App.js +38 -6
- package/dist/admin/App.js.map +1 -1
- package/dist/admin/apps/api-keys.d.ts +10 -24
- package/dist/admin/apps/api-keys.d.ts.map +1 -1
- package/dist/admin/apps/api-keys.js +10 -3
- package/dist/admin/apps/api-keys.js.map +1 -1
- package/dist/admin/apps/organizations.d.ts +833 -0
- package/dist/admin/apps/organizations.d.ts.map +1 -0
- package/dist/admin/apps/organizations.js +733 -0
- package/dist/admin/apps/organizations.js.map +1 -0
- package/dist/admin/apps/scopes.d.ts +8 -0
- package/dist/admin/apps/scopes.d.ts.map +1 -1
- package/dist/admin/ui.generated.d.ts +1 -1
- package/dist/admin/ui.generated.d.ts.map +1 -1
- package/dist/admin/ui.generated.js +53 -13
- package/dist/admin/ui.generated.js.map +1 -1
- package/dist/analytics/Analytics.d.ts +10 -0
- package/dist/analytics/Analytics.d.ts.map +1 -1
- package/dist/analytics/Analytics.js +8 -0
- package/dist/analytics/Analytics.js.map +1 -1
- package/dist/analytics/tables/requestEvents.d.ts +9 -0
- package/dist/analytics/tables/requestEvents.d.ts.map +1 -1
- package/dist/analytics/tables/requestEvents.js +14 -0
- package/dist/analytics/tables/requestEvents.js.map +1 -1
- package/dist/apps/data/App.d.ts +1354 -984
- package/dist/apps/data/App.d.ts.map +1 -1
- package/dist/apps/data/FundingRouteProviders.d.ts +94 -5
- package/dist/apps/data/FundingRouteProviders.d.ts.map +1 -1
- package/dist/apps/data/FundingRouteProviders.js +196 -5
- package/dist/apps/data/FundingRouteProviders.js.map +1 -1
- package/dist/apps/data/routes/activities.d.ts +38 -38
- package/dist/apps/data/routes/addresses.d.ts +24 -24
- package/dist/apps/data/routes/blocks.d.ts +39 -39
- package/dist/apps/data/routes/exchanges.d.ts +453 -453
- package/dist/apps/data/routes/fee-amm.d.ts +101 -101
- package/dist/apps/data/routes/funding.d.ts +730 -138
- package/dist/apps/data/routes/funding.d.ts.map +1 -1
- package/dist/apps/data/routes/funding.js +234 -0
- package/dist/apps/data/routes/funding.js.map +1 -1
- package/dist/apps/data/routes/indexer.d.ts +5 -5
- package/dist/apps/data/routes/transactions.d.ts +281 -281
- package/dist/apps/data/routes/transfers.d.ts +58 -58
- package/dist/apps/data/routes/verified-tokens.d.ts +81 -81
- package/dist/apps/management/App.d.ts +1156 -615
- package/dist/apps/management/App.d.ts.map +1 -1
- package/dist/apps/management/App.js +2 -2
- package/dist/apps/management/Billing.d.ts +3 -0
- package/dist/apps/management/Billing.d.ts.map +1 -1
- package/dist/apps/management/Billing.js +7 -2
- package/dist/apps/management/Billing.js.map +1 -1
- package/dist/apps/management/routes/api-keys.d.ts +106 -90
- package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
- package/dist/apps/management/routes/api-keys.js +41 -30
- package/dist/apps/management/routes/api-keys.js.map +1 -1
- package/dist/apps/management/routes/billing.d.ts +278 -146
- package/dist/apps/management/routes/billing.d.ts.map +1 -1
- package/dist/apps/management/routes/billing.js +14 -14
- package/dist/apps/management/routes/billing.js.map +1 -1
- package/dist/apps/management/routes/invitations.d.ts +93 -20
- package/dist/apps/management/routes/invitations.d.ts.map +1 -1
- package/dist/apps/management/routes/invitations.js +12 -15
- package/dist/apps/management/routes/invitations.js.map +1 -1
- package/dist/apps/management/routes/me.d.ts +6 -6
- package/dist/apps/management/routes/members.d.ts +89 -18
- package/dist/apps/management/routes/members.d.ts.map +1 -1
- package/dist/apps/management/routes/members.js +10 -10
- package/dist/apps/management/routes/members.js.map +1 -1
- package/dist/apps/management/routes/orgs.d.ts +130 -70
- package/dist/apps/management/routes/orgs.d.ts.map +1 -1
- package/dist/apps/management/routes/orgs.js +19 -19
- package/dist/apps/management/routes/orgs.js.map +1 -1
- package/dist/apps/management/routes/projects.d.ts +137 -48
- package/dist/apps/management/routes/projects.d.ts.map +1 -1
- package/dist/apps/management/routes/projects.js +12 -13
- package/dist/apps/management/routes/projects.js.map +1 -1
- package/dist/apps/management/routes/scopes.d.ts +4 -18
- package/dist/apps/management/routes/scopes.d.ts.map +1 -1
- package/dist/apps/management/routes/scopes.js +6 -7
- package/dist/apps/management/routes/scopes.js.map +1 -1
- package/dist/apps/management/routes/usage.d.ts +63 -19
- package/dist/apps/management/routes/usage.d.ts.map +1 -1
- package/dist/apps/management/routes/usage.js +9 -10
- package/dist/apps/management/routes/usage.js.map +1 -1
- package/dist/apps/mcp.d.ts.map +1 -1
- package/dist/apps/mcp.js +35 -3
- package/dist/apps/mcp.js.map +1 -1
- package/dist/apps/relay/App.d.ts +26 -4
- package/dist/apps/relay/App.d.ts.map +1 -1
- package/dist/apps/relay/App.js +17 -14
- package/dist/apps/relay/App.js.map +1 -1
- package/dist/db/Db.d.ts +5 -3
- package/dist/db/Db.d.ts.map +1 -1
- package/dist/db/Db.js +52 -0
- package/dist/db/Db.js.map +1 -1
- package/dist/db/Schema.d.ts +13 -0
- package/dist/db/Schema.d.ts.map +1 -1
- package/dist/db/Schema.js +16 -1
- package/dist/db/Schema.js.map +1 -1
- package/dist/db/tables/adminAuditLogs.d.ts +27 -0
- package/dist/db/tables/adminAuditLogs.d.ts.map +1 -0
- package/dist/db/tables/adminAuditLogs.js +19 -0
- package/dist/db/tables/adminAuditLogs.js.map +1 -0
- package/dist/db/tables/invitations.d.ts +1 -1
- package/dist/db/tables/invitations.d.ts.map +1 -1
- package/dist/db/tables/memberships.d.ts +32 -4
- package/dist/db/tables/memberships.d.ts.map +1 -1
- package/dist/db/tables/memberships.js +31 -0
- package/dist/db/tables/memberships.js.map +1 -1
- package/dist/db/tables/organizations.d.ts +43 -1
- package/dist/db/tables/organizations.d.ts.map +1 -1
- package/dist/db/tables/organizations.js +103 -0
- package/dist/db/tables/organizations.js.map +1 -1
- package/dist/db/tables/projects.d.ts +25 -0
- package/dist/db/tables/projects.d.ts.map +1 -1
- package/dist/db/tables/projects.js +22 -0
- package/dist/db/tables/projects.js.map +1 -1
- package/dist/db/tables/webhookDeliveries.d.ts +26 -0
- package/dist/db/tables/webhookDeliveries.d.ts.map +1 -1
- package/dist/db/tables/webhookDeliveries.js +37 -0
- package/dist/db/tables/webhookDeliveries.js.map +1 -1
- package/dist/internal/Auth.d.ts +13 -18
- package/dist/internal/Auth.d.ts.map +1 -1
- package/dist/internal/Auth.js +78 -58
- package/dist/internal/Auth.js.map +1 -1
- package/dist/internal/Cursor.d.ts +1 -1
- package/dist/internal/Cursor.d.ts.map +1 -1
- package/dist/internal/Cursor.js +4 -0
- package/dist/internal/Cursor.js.map +1 -1
- package/dist/internal/EdgeCache.d.ts.map +1 -1
- package/dist/internal/EdgeCache.js +2 -0
- package/dist/internal/EdgeCache.js.map +1 -1
- package/dist/internal/FundingRoutes.d.ts +170 -0
- package/dist/internal/FundingRoutes.d.ts.map +1 -1
- package/dist/internal/FundingRoutes.js +68 -6
- package/dist/internal/FundingRoutes.js.map +1 -1
- package/dist/internal/Log.d.ts +16 -1
- package/dist/internal/Log.d.ts.map +1 -1
- package/dist/internal/Log.js +5 -3
- package/dist/internal/Log.js.map +1 -1
- package/package.json +2 -2
- package/src/ApiKey.ts +2 -3
- package/src/ApiKeys.ts +95 -17
- package/src/App.test.ts +44 -2
- package/src/App.ts +29 -9
- package/src/Assets.ts +8 -0
- package/src/Auth.test-d.ts +36 -0
- package/src/Auth.ts +20 -1
- package/src/Client.test-d.ts +64 -17
- package/src/Scope.test.ts +34 -0
- package/src/Scope.ts +53 -17
- package/src/admin/App.test.ts +33 -3
- package/src/admin/App.ts +49 -14
- package/src/admin/apps/api-keys.ts +10 -3
- package/src/admin/apps/organizations.test.ts +455 -0
- package/src/admin/apps/organizations.ts +953 -0
- package/src/admin/apps/scopes.test.ts +10 -0
- package/src/admin/ui/plugin.ts +1 -1
- package/src/admin/ui/src/components/MobileProfile.tsx +2 -0
- package/src/admin/ui/src/components/ThemeToggle.tsx +20 -0
- package/src/admin/ui/src/lib/api-keys.ts +36 -19
- package/src/admin/ui/src/lib/config.ts +2 -3
- package/src/admin/ui/src/lib/organizations.test.ts +89 -0
- package/src/admin/ui/src/lib/organizations.ts +262 -0
- package/src/admin/ui/src/lib/query-client.ts +3 -1
- package/src/admin/ui/src/lib/theme.ts +57 -0
- package/src/admin/ui/src/routeTree.gen.ts +47 -4
- package/src/admin/ui/src/routes/__root.tsx +18 -3
- package/src/admin/ui/src/routes/api-keys.tsx +52 -7
- package/src/admin/ui/src/routes/index.tsx +1 -1
- package/src/admin/ui/src/routes/organizations.tsx +158 -0
- package/src/admin/ui/src/routes/organizations_.$orgId.tsx +710 -0
- package/src/admin/ui/src/start.ts +6 -0
- package/src/admin/ui.generated.ts +63 -13
- package/src/analytics/Analytics.ts +25 -0
- package/src/analytics/tables/requestEvents.ts +33 -0
- package/src/apps/composition.test.ts +82 -2
- package/src/apps/data/FundingRouteProviders.test.ts +204 -0
- package/src/apps/data/FundingRouteProviders.ts +294 -9
- package/src/apps/data/routes/funding.test.ts +90 -23
- package/src/apps/data/routes/funding.ts +315 -0
- package/src/apps/management/App.ts +2 -2
- package/src/apps/management/Billing.ts +10 -2
- package/src/apps/management/routes/api-keys.test.ts +137 -1
- package/src/apps/management/routes/api-keys.ts +63 -38
- package/src/apps/management/routes/billing.ts +14 -14
- package/src/apps/management/routes/invitations.test.ts +102 -1
- package/src/apps/management/routes/invitations.ts +13 -15
- package/src/apps/management/routes/members.ts +10 -10
- package/src/apps/management/routes/orgs.test.ts +78 -16
- package/src/apps/management/routes/orgs.ts +21 -20
- package/src/apps/management/routes/projects.ts +12 -13
- package/src/apps/management/routes/scopes.test.ts +16 -0
- package/src/apps/management/routes/scopes.ts +6 -7
- package/src/apps/management/routes/usage.ts +9 -14
- package/src/apps/mcp.test.ts +25 -1
- package/src/apps/mcp.ts +43 -3
- package/src/apps/relay/App.test.ts +23 -18
- package/src/apps/relay/App.ts +35 -14
- package/src/db/Db.ts +59 -3
- package/src/db/Schema.ts +20 -1
- package/src/db/tables/adminAuditLogs.ts +47 -0
- package/src/db/tables/invitations.ts +1 -1
- package/src/db/tables/memberships.ts +59 -3
- package/src/db/tables/organizations.ts +148 -1
- package/src/db/tables/projects.ts +47 -0
- package/src/db/tables/webhookDeliveries.ts +83 -0
- package/src/internal/Auth.test.ts +174 -7
- package/src/internal/Auth.ts +90 -62
- package/src/internal/Cursor.test.ts +10 -0
- package/src/internal/Cursor.ts +5 -1
- package/src/internal/EdgeCache.test.ts +11 -0
- package/src/internal/EdgeCache.ts +2 -0
- package/src/internal/FundingRoutes.test.ts +26 -0
- package/src/internal/FundingRoutes.ts +187 -18
- package/src/internal/Log.test.ts +41 -3
- package/src/internal/Log.ts +23 -4
package/src/internal/Auth.ts
CHANGED
|
@@ -160,7 +160,7 @@ export type Principal =
|
|
|
160
160
|
orgId: string
|
|
161
161
|
/** Payment metadata for paid overflow requests. */
|
|
162
162
|
payment?: Payment | undefined
|
|
163
|
-
/**
|
|
163
|
+
/** Attributed project id, when present. */
|
|
164
164
|
projectId?: string | undefined
|
|
165
165
|
/** Principal kind. */
|
|
166
166
|
type: 'api_key'
|
|
@@ -205,9 +205,9 @@ export type Principal =
|
|
|
205
205
|
export type Variables = {
|
|
206
206
|
/** Auth context consumed by protected handlers. */
|
|
207
207
|
auth: Context
|
|
208
|
-
/** Caller's membership resolved by {@link ensureOrg}; absent for `super_admin`. */
|
|
208
|
+
/** Caller's membership resolved by {@link ensureOrg} or {@link ensureProject}; absent for API keys and `super_admin`. */
|
|
209
209
|
membership?: Memberships.Record | undefined
|
|
210
|
-
/** Organization resolved by {@link ensureOrg} for the current request. */
|
|
210
|
+
/** Organization resolved by {@link ensureOrg} or {@link ensureProject} for the current request. */
|
|
211
211
|
org?: Organizations.Record | undefined
|
|
212
212
|
/** Authenticated or anonymous caller. */
|
|
213
213
|
principal?: Principal | undefined
|
|
@@ -221,13 +221,7 @@ export function getPrincipal<environment extends Environment>(c: hono_Context<en
|
|
|
221
221
|
}
|
|
222
222
|
|
|
223
223
|
/**
|
|
224
|
-
* Guards
|
|
225
|
-
* are members of; the super admin reaches any. Missing or foreign
|
|
226
|
-
* organizations return `404`, so foreign org ids are indistinguishable from
|
|
227
|
-
* absent ones; a member below the required role gets `403` (the resource is
|
|
228
|
-
* visible to them, the action is not). The resolved organization and
|
|
229
|
-
* membership land on context — read them with {@link org} and
|
|
230
|
-
* {@link membership}.
|
|
224
|
+
* Guards `:orgId` routes and resolves the organization. Sessions require membership, API keys require ownership, and the super admin bypasses ownership.
|
|
231
225
|
*/
|
|
232
226
|
export function ensureOrg(options: ensureOrg.Options = {}): MiddlewareHandler {
|
|
233
227
|
const { role: minimum = 'member' } = options
|
|
@@ -243,11 +237,17 @@ export function ensureOrg(options: ensureOrg.Options = {}): MiddlewareHandler {
|
|
|
243
237
|
const orgId = c.req.param('orgId' as never) as string | undefined
|
|
244
238
|
if (!orgId) return notFound()
|
|
245
239
|
const principal = getPrincipal(c)
|
|
246
|
-
if (
|
|
240
|
+
if (
|
|
241
|
+
principal?.type !== 'api_key' &&
|
|
242
|
+
principal?.type !== 'session' &&
|
|
243
|
+
principal?.type !== 'super_admin'
|
|
244
|
+
)
|
|
245
|
+
return notFound()
|
|
246
|
+
if (principal.type === 'api_key' && principal.orgId !== orgId) return notFound()
|
|
247
247
|
const db = Db.get(contextDb(c))
|
|
248
248
|
const record = await Organizations.get(db, orgId)
|
|
249
249
|
if (!record) return notFound()
|
|
250
|
-
if (principal.type
|
|
250
|
+
if (principal.type === 'session') {
|
|
251
251
|
const member = await Memberships.get(db, orgId, principal.id)
|
|
252
252
|
if (!member) return notFound()
|
|
253
253
|
if (Memberships.rank[member.role] < Memberships.rank[minimum])
|
|
@@ -267,16 +267,13 @@ export function ensureOrg(options: ensureOrg.Options = {}): MiddlewareHandler {
|
|
|
267
267
|
export declare namespace ensureOrg {
|
|
268
268
|
/** Options for the org guard. */
|
|
269
269
|
type Options = {
|
|
270
|
-
/** Minimum role
|
|
270
|
+
/** Minimum session role; defaults to `member`. */
|
|
271
271
|
role?: Memberships.Role | undefined
|
|
272
272
|
}
|
|
273
273
|
}
|
|
274
274
|
|
|
275
275
|
/**
|
|
276
|
-
* Guards project
|
|
277
|
-
* which must run first. Projects resolve only within the scoped organization;
|
|
278
|
-
* missing or cross-org projects return `404`. The resolved project lands on
|
|
279
|
-
* context — read it with {@link project}.
|
|
276
|
+
* Guards `:projectId` routes and resolves the project. Flat routes authorize against its organization; nested routes preserve the scope from {@link ensureOrg}.
|
|
280
277
|
*/
|
|
281
278
|
export function ensureProject(): MiddlewareHandler {
|
|
282
279
|
// Typed loosely (like {@link policy}) so route chains keep their inferred
|
|
@@ -290,10 +287,32 @@ export function ensureProject(): MiddlewareHandler {
|
|
|
290
287
|
})
|
|
291
288
|
const projectId = c.req.param('projectId' as never) as string | undefined
|
|
292
289
|
if (!projectId) return notFound()
|
|
290
|
+
const principal = getPrincipal(c)
|
|
291
|
+
if (
|
|
292
|
+
principal?.type !== 'api_key' &&
|
|
293
|
+
principal?.type !== 'session' &&
|
|
294
|
+
principal?.type !== 'super_admin'
|
|
295
|
+
)
|
|
296
|
+
return notFound()
|
|
297
|
+
const db = Db.get(contextDb(c))
|
|
298
|
+
const record = await Projects.get(db, projectId)
|
|
299
|
+
if (!record) return notFound()
|
|
293
300
|
const scoped = c.get('org')
|
|
294
|
-
if (
|
|
295
|
-
|
|
296
|
-
|
|
301
|
+
if (scoped) {
|
|
302
|
+
if (record.orgId !== scoped.id) return notFound()
|
|
303
|
+
c.set('project', record)
|
|
304
|
+
await next()
|
|
305
|
+
return undefined
|
|
306
|
+
}
|
|
307
|
+
if (principal.type === 'api_key' && principal.orgId !== record.orgId) return notFound()
|
|
308
|
+
const organization = await Organizations.get(db, record.orgId)
|
|
309
|
+
if (!organization) return notFound()
|
|
310
|
+
if (principal.type === 'session') {
|
|
311
|
+
const member = await Memberships.get(db, record.orgId, principal.id)
|
|
312
|
+
if (!member) return notFound()
|
|
313
|
+
c.set('membership', member)
|
|
314
|
+
}
|
|
315
|
+
c.set('org', organization)
|
|
297
316
|
c.set('project', record)
|
|
298
317
|
await next()
|
|
299
318
|
return undefined
|
|
@@ -318,7 +337,7 @@ export function ensureOrgError(c: hono_Context) {
|
|
|
318
337
|
|
|
319
338
|
/** Typed project-scope error response for Hono client inference. Never reached at runtime. */
|
|
320
339
|
export function ensureProjectError(c: hono_Context) {
|
|
321
|
-
//
|
|
340
|
+
// Nested project routes run the org scope first, so either not-found can surface.
|
|
322
341
|
const code =
|
|
323
342
|
c.req.header('tempo-narrow-scope-code') === 'organization_not_found'
|
|
324
343
|
? ('organization_not_found' as const)
|
|
@@ -330,19 +349,20 @@ export function ensureProjectError(c: hono_Context) {
|
|
|
330
349
|
})
|
|
331
350
|
}
|
|
332
351
|
|
|
333
|
-
/** Reads the caller's membership resolved by
|
|
352
|
+
/** Reads the caller's membership resolved by the organization or project guard; `undefined` for API keys and `super_admin`. */
|
|
334
353
|
export function membership<environment extends Environment>(
|
|
335
354
|
c: hono_Context<environment>,
|
|
336
355
|
): Memberships.Record | undefined {
|
|
337
356
|
return c.get('membership')
|
|
338
357
|
}
|
|
339
358
|
|
|
340
|
-
/** Reads the organization resolved by
|
|
359
|
+
/** Reads the organization resolved by the organization or project guard; throws when neither middleware ran. */
|
|
341
360
|
export function org<environment extends Environment>(
|
|
342
361
|
c: hono_Context<environment>,
|
|
343
362
|
): Organizations.Record {
|
|
344
363
|
const record = c.get('org')
|
|
345
|
-
if (!record)
|
|
364
|
+
if (!record)
|
|
365
|
+
throw new Error('`ensureOrg` or `ensureProject` must run before reading the organization')
|
|
346
366
|
return record
|
|
347
367
|
}
|
|
348
368
|
|
|
@@ -351,7 +371,7 @@ export function project<environment extends Environment>(
|
|
|
351
371
|
c: hono_Context<environment>,
|
|
352
372
|
): Projects.Record {
|
|
353
373
|
const record = c.get('project')
|
|
354
|
-
if (!record) throw new Error('`
|
|
374
|
+
if (!record) throw new Error('`ensureProject` must run before reading the project')
|
|
355
375
|
return record
|
|
356
376
|
}
|
|
357
377
|
|
|
@@ -460,7 +480,9 @@ export function middleware<
|
|
|
460
480
|
const resolve = (() => {
|
|
461
481
|
const uncached = (c: hono_Context, token: string) => {
|
|
462
482
|
const kv = contextKv(c)
|
|
463
|
-
return kv
|
|
483
|
+
return kv
|
|
484
|
+
? ApiKeys.resolve(kv.store, token, { scopeCatalog: options.scopeCatalog })
|
|
485
|
+
: Promise.resolve(null)
|
|
464
486
|
}
|
|
465
487
|
if (options.apiKey === false || options.apiKey?.cache === false) return uncached
|
|
466
488
|
return cachedResolve(uncached, options.apiKey?.cache ?? {})
|
|
@@ -554,6 +576,8 @@ export declare namespace middleware {
|
|
|
554
576
|
| undefined
|
|
555
577
|
/** Rate-limit store options. */
|
|
556
578
|
rateLimit?: RateLimit.memory.Options | undefined
|
|
579
|
+
/** Scope catalog accepted while resolving API-key records. */
|
|
580
|
+
scopeCatalog?: Scope.Catalog | undefined
|
|
557
581
|
/** Session lane resolution — the session surface's capability; omit to leave the session lane closed. */
|
|
558
582
|
session?: Session | undefined
|
|
559
583
|
/**
|
|
@@ -678,7 +702,7 @@ function handlerError(description: string) {
|
|
|
678
702
|
* disables both.
|
|
679
703
|
*/
|
|
680
704
|
export function install(options: install.Options) {
|
|
681
|
-
const { db, kv } = options
|
|
705
|
+
const { db, kv, scopeCatalog } = options
|
|
682
706
|
const { session: options_session, ...options_middleware } =
|
|
683
707
|
options.auth === false ? {} : (options.auth ?? {})
|
|
684
708
|
if (options_session && !kv)
|
|
@@ -894,7 +918,11 @@ export function install(options: install.Options) {
|
|
|
894
918
|
const middleware_ =
|
|
895
919
|
options.auth === false
|
|
896
920
|
? undefined
|
|
897
|
-
: middleware({
|
|
921
|
+
: middleware({
|
|
922
|
+
...options_middleware,
|
|
923
|
+
...(app ? { session: app.session } : {}),
|
|
924
|
+
scopeCatalog,
|
|
925
|
+
})
|
|
898
926
|
|
|
899
927
|
return { app, middleware: middleware_ }
|
|
900
928
|
}
|
|
@@ -903,7 +931,7 @@ export declare namespace install {
|
|
|
903
931
|
/** Options for building the built-in auth pieces. */
|
|
904
932
|
type Options = {
|
|
905
933
|
auth:
|
|
906
|
-
| (Omit<middleware.Options, 'session'> & {
|
|
934
|
+
| (Omit<middleware.Options, 'scopeCatalog' | 'session'> & {
|
|
907
935
|
/** Session sign-in surface: mounts `/v1/auth/siwe` (challenge/verify/logout) and enables the session lane. Pass true for defaults or a config object. Requires `kv`. */
|
|
908
936
|
session?: Session | undefined
|
|
909
937
|
})
|
|
@@ -913,6 +941,8 @@ export declare namespace install {
|
|
|
913
941
|
db: Db.Source
|
|
914
942
|
/** KV state store, or undefined when key auth is closed. */
|
|
915
943
|
kv: { store: Store.State } | undefined
|
|
944
|
+
/** Scope catalog accepted while resolving API-key records. */
|
|
945
|
+
scopeCatalog: Scope.Catalog
|
|
916
946
|
}
|
|
917
947
|
|
|
918
948
|
/** Session sign-in surface configuration: `true` for defaults, or a config object. */
|
|
@@ -1229,12 +1259,20 @@ export function require<environment extends Environment = Environment>(
|
|
|
1229
1259
|
return createMiddleware<environment>(async (c, next) => {
|
|
1230
1260
|
const auth = c.get('auth')
|
|
1231
1261
|
const credential = getApiKeyCredential(c)
|
|
1232
|
-
|
|
1233
|
-
//
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1262
|
+
const hasQueryApiKey = new URL(c.req.url).searchParams.has('key')
|
|
1263
|
+
// Auth owns `key`; strict route schemas must not validate it.
|
|
1264
|
+
stripApiKeyQuery(c)
|
|
1265
|
+
const proceed = async () => {
|
|
1266
|
+
await next()
|
|
1267
|
+
// Query credential responses must not enter shared caches.
|
|
1268
|
+
const cacheControl = c.res.headers.get('Cache-Control')?.toLowerCase()
|
|
1269
|
+
if (
|
|
1270
|
+
hasQueryApiKey &&
|
|
1271
|
+
!cacheControl?.includes('no-store') &&
|
|
1272
|
+
!cacheControl?.includes('private')
|
|
1273
|
+
)
|
|
1274
|
+
c.res.headers.set('Cache-Control', 'private')
|
|
1275
|
+
}
|
|
1238
1276
|
if (credential === false)
|
|
1239
1277
|
return jsonError(c, {
|
|
1240
1278
|
code: 'api_key_malformed',
|
|
@@ -1248,7 +1286,7 @@ export function require<environment extends Environment = Environment>(
|
|
|
1248
1286
|
// It must never reach KV resolution or scope/quota checks.
|
|
1249
1287
|
if (auth.superAdmin && ApiKey.hash(credential.token) === auth.superAdmin.tokenHash) {
|
|
1250
1288
|
c.set('principal', { actor: 'super_admin', id: 'super_admin', type: 'super_admin' })
|
|
1251
|
-
return
|
|
1289
|
+
return proceed()
|
|
1252
1290
|
}
|
|
1253
1291
|
|
|
1254
1292
|
const apiKey = await auth.apiKey?.resolve(c, credential.token)
|
|
@@ -1260,7 +1298,7 @@ export function require<environment extends Environment = Environment>(
|
|
|
1260
1298
|
const session = await resolveSession(c, auth)
|
|
1261
1299
|
if (session) {
|
|
1262
1300
|
c.set('principal', session)
|
|
1263
|
-
return
|
|
1301
|
+
return proceed()
|
|
1264
1302
|
}
|
|
1265
1303
|
}
|
|
1266
1304
|
return jsonError(c, {
|
|
@@ -1319,7 +1357,7 @@ export function require<environment extends Environment = Environment>(
|
|
|
1319
1357
|
const throttled = apiKey.environment === 'sandbox' && apiKey.billingActive !== true
|
|
1320
1358
|
|
|
1321
1359
|
if (hasPayment && !throttled)
|
|
1322
|
-
return requirePayment(c,
|
|
1360
|
+
return requirePayment(c, proceed, {
|
|
1323
1361
|
auth,
|
|
1324
1362
|
payment: { reason: 'api_key_over_quota', type: 'mpp' },
|
|
1325
1363
|
policy,
|
|
@@ -1351,7 +1389,7 @@ export function require<environment extends Environment = Environment>(
|
|
|
1351
1389
|
// A throttled sandbox key gets a plain 429, never an MPP challenge —
|
|
1352
1390
|
// it must activate billing to lift the ceiling, not pay per request.
|
|
1353
1391
|
if (throttled) return rateLimitExceeded(c, result)
|
|
1354
|
-
return requirePaymentOrRateLimit(c,
|
|
1392
|
+
return requirePaymentOrRateLimit(c, proceed, {
|
|
1355
1393
|
auth,
|
|
1356
1394
|
payment: { reason: 'api_key_over_quota', type: 'mpp' },
|
|
1357
1395
|
policy,
|
|
@@ -1361,7 +1399,7 @@ export function require<environment extends Environment = Environment>(
|
|
|
1361
1399
|
}
|
|
1362
1400
|
}
|
|
1363
1401
|
|
|
1364
|
-
return
|
|
1402
|
+
return proceed()
|
|
1365
1403
|
}
|
|
1366
1404
|
|
|
1367
1405
|
// Cookie sessions carry no Authorization credential; resolve the session
|
|
@@ -1371,14 +1409,14 @@ export function require<environment extends Environment = Environment>(
|
|
|
1371
1409
|
const session = await resolveSession(c, auth)
|
|
1372
1410
|
if (session) {
|
|
1373
1411
|
c.set('principal', session)
|
|
1374
|
-
return
|
|
1412
|
+
return proceed()
|
|
1375
1413
|
}
|
|
1376
1414
|
}
|
|
1377
1415
|
|
|
1378
1416
|
const publicPrincipal = principalFromPublic(c)
|
|
1379
1417
|
if (hasPayment) {
|
|
1380
1418
|
c.set('principal', publicPrincipal)
|
|
1381
|
-
return requirePayment(c,
|
|
1419
|
+
return requirePayment(c, proceed, {
|
|
1382
1420
|
auth,
|
|
1383
1421
|
payment: { reason: 'public_over_quota', type: 'mpp' },
|
|
1384
1422
|
policy,
|
|
@@ -1395,11 +1433,11 @@ export function require<environment extends Environment = Environment>(
|
|
|
1395
1433
|
if (result) setRateLimitHeaders(c, result)
|
|
1396
1434
|
if (!result || result.allowed) {
|
|
1397
1435
|
c.set('principal', publicPrincipal)
|
|
1398
|
-
return
|
|
1436
|
+
return proceed()
|
|
1399
1437
|
}
|
|
1400
1438
|
|
|
1401
1439
|
c.set('principal', publicPrincipal)
|
|
1402
|
-
return requirePaymentOrRateLimit(c,
|
|
1440
|
+
return requirePaymentOrRateLimit(c, proceed, {
|
|
1403
1441
|
auth,
|
|
1404
1442
|
payment: { reason: 'public_over_quota', type: 'mpp' },
|
|
1405
1443
|
policy,
|
|
@@ -1410,7 +1448,7 @@ export function require<environment extends Environment = Environment>(
|
|
|
1410
1448
|
|
|
1411
1449
|
if (isPaymentEnabled(auth, policy)) {
|
|
1412
1450
|
c.set('principal', publicPrincipal)
|
|
1413
|
-
return requirePayment(c,
|
|
1451
|
+
return requirePayment(c, proceed, {
|
|
1414
1452
|
auth,
|
|
1415
1453
|
payment: { reason: 'public_over_quota', type: 'mpp' },
|
|
1416
1454
|
policy,
|
|
@@ -1574,10 +1612,8 @@ function getApiKeyCredential(c: hono_Context): ApiKeyCredential | false | null {
|
|
|
1574
1612
|
const header = c.req.header('tempo-api-key')
|
|
1575
1613
|
if (header) return { token: header }
|
|
1576
1614
|
|
|
1577
|
-
//
|
|
1578
|
-
//
|
|
1579
|
-
// carrying only a payment scheme falls through to the deprecated lanes below
|
|
1580
|
-
// (which find nothing) and resolves to no API-key credential.
|
|
1615
|
+
// Bearer supports clients unable to set the canonical header. Payment
|
|
1616
|
+
// credentials belong to MPP and must pass through untouched.
|
|
1581
1617
|
const authorization = c.req.header('authorization')
|
|
1582
1618
|
if (authorization && !Credential.extractPaymentScheme(authorization)) {
|
|
1583
1619
|
const match = /^Bearer\s+(.+)$/i.exec(authorization)
|
|
@@ -1585,26 +1621,18 @@ function getApiKeyCredential(c: hono_Context): ApiKeyCredential | false | null {
|
|
|
1585
1621
|
return { token: match[1] }
|
|
1586
1622
|
}
|
|
1587
1623
|
|
|
1588
|
-
//
|
|
1589
|
-
// keep authenticating: the `x-api-key` header and the `key` query parameter.
|
|
1624
|
+
// `x-api-key` remains a deprecated compatibility header.
|
|
1590
1625
|
const legacyHeader = c.req.header('x-api-key')
|
|
1591
1626
|
if (legacyHeader) return { token: legacyHeader }
|
|
1592
1627
|
|
|
1593
|
-
const
|
|
1594
|
-
if (
|
|
1628
|
+
const apiKey = c.req.query('key')
|
|
1629
|
+
if (apiKey) return { token: apiKey }
|
|
1595
1630
|
|
|
1596
1631
|
return null
|
|
1597
1632
|
}
|
|
1598
1633
|
|
|
1599
|
-
/**
|
|
1600
|
-
|
|
1601
|
-
* been read for authentication. Route query schemas are strict
|
|
1602
|
-
* (`z.strictObject`), so leaving `key` in the query string would make the
|
|
1603
|
-
* downstream validator reject the request as `query_invalid`. Stripping it
|
|
1604
|
-
* keeps the legacy `?key=...` auth lane working on strict-query endpoints
|
|
1605
|
-
* (e.g. `GET /v1/tokens`).
|
|
1606
|
-
*/
|
|
1607
|
-
function stripLegacyAuthQuery(c: hono_Context) {
|
|
1634
|
+
/** Removes the reserved auth query parameter before route validation. */
|
|
1635
|
+
function stripApiKeyQuery(c: hono_Context) {
|
|
1608
1636
|
const url = new URL(c.req.url)
|
|
1609
1637
|
if (!url.searchParams.has('key')) return
|
|
1610
1638
|
url.searchParams.delete('key')
|
|
@@ -18,6 +18,14 @@ describe('Cursor', () => {
|
|
|
18
18
|
])
|
|
19
19
|
})
|
|
20
20
|
|
|
21
|
+
test('round-trips a timestamp/id tuple', () => {
|
|
22
|
+
const token = Cursor.encode(['2026-01-01T00:00:00.000Z', 'org_abc123'])
|
|
23
|
+
expect(Cursor.decode(token, ['timestamp', 'id'])).toEqual([
|
|
24
|
+
'2026-01-01T00:00:00.000Z',
|
|
25
|
+
'org_abc123',
|
|
26
|
+
])
|
|
27
|
+
})
|
|
28
|
+
|
|
21
29
|
test('returns undefined for malformed, mis-typed, or wrong-arity tokens', () => {
|
|
22
30
|
expect(Cursor.decode('not-base64!!', ['int', 'int'])).toBeUndefined()
|
|
23
31
|
expect(Cursor.decode(btoa('{"oops":true}'), ['int', 'int'])).toBeUndefined()
|
|
@@ -27,6 +35,7 @@ describe('Cursor', () => {
|
|
|
27
35
|
expect(Cursor.decode(btoa('[-1,2]'), ['int', 'int'])).toBeUndefined()
|
|
28
36
|
expect(Cursor.decode(btoa('["x",2]'), ['uint', 'int'])).toBeUndefined()
|
|
29
37
|
expect(Cursor.decode(btoa('["0xnotanaddress",2]'), ['address', 'int'])).toBeUndefined()
|
|
38
|
+
expect(Cursor.decode(btoa('["org_abc\' OR 1=1"]'), ['id'])).toBeUndefined()
|
|
30
39
|
})
|
|
31
40
|
|
|
32
41
|
test('renders injection-safe SQL literals per field kind', () => {
|
|
@@ -35,6 +44,7 @@ describe('Cursor', () => {
|
|
|
35
44
|
expect(Cursor.literal('0xAbC0000000000000000000000000000000000000', 'address')).toBe(
|
|
36
45
|
"'0xabc0000000000000000000000000000000000000'",
|
|
37
46
|
)
|
|
47
|
+
expect(Cursor.literal('org_abc123', 'id')).toBe("'org_abc123'")
|
|
38
48
|
expect(Cursor.literal('2024-01-01T00:00:00Z', 'timestamp')).toBe("'2024-01-01T00:00:00Z'")
|
|
39
49
|
})
|
|
40
50
|
|
package/src/internal/Cursor.ts
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
export type Cursor = (string | number)[]
|
|
17
17
|
|
|
18
18
|
/** The kind of a cursor field, determining its validation and SQL literal form. */
|
|
19
|
-
export type Field = '
|
|
19
|
+
export type Field = 'address' | 'id' | 'int' | 'timestamp' | 'uint'
|
|
20
20
|
|
|
21
21
|
/** Encodes a positional tuple as an opaque, URL-safe token. */
|
|
22
22
|
export function encode(values: Cursor): string {
|
|
@@ -76,6 +76,8 @@ export function literal(value: string | number, field: Field): string {
|
|
|
76
76
|
return String(value)
|
|
77
77
|
case 'address':
|
|
78
78
|
return `'${String(value).toLowerCase()}'`
|
|
79
|
+
case 'id':
|
|
80
|
+
return `'${String(value)}'`
|
|
79
81
|
case 'timestamp':
|
|
80
82
|
return `'${String(value)}'`
|
|
81
83
|
}
|
|
@@ -135,6 +137,8 @@ function isValid(value: unknown, field: Field): boolean {
|
|
|
135
137
|
return typeof value === 'string' && /^\d+$/.test(value)
|
|
136
138
|
case 'address':
|
|
137
139
|
return typeof value === 'string' && /^0x[0-9a-fA-F]{40}$/.test(value)
|
|
140
|
+
case 'id':
|
|
141
|
+
return typeof value === 'string' && /^[A-Za-z0-9_-]+$/.test(value)
|
|
138
142
|
case 'timestamp':
|
|
139
143
|
return typeof value === 'string' && !Number.isNaN(Date.parse(value))
|
|
140
144
|
}
|
|
@@ -51,6 +51,17 @@ describe('middleware', () => {
|
|
|
51
51
|
expect(store.size).toMatchInlineSnapshot(`1`)
|
|
52
52
|
})
|
|
53
53
|
|
|
54
|
+
test('bypasses the cache for query API key credentials', async () => {
|
|
55
|
+
const { app, store, downstreamCalls } = createApp()
|
|
56
|
+
|
|
57
|
+
await app.request('/cached?key=opaque-secret')
|
|
58
|
+
await app.request('/cached?key=opaque-secret')
|
|
59
|
+
|
|
60
|
+
expect(downstreamCalls()).toMatchInlineSnapshot(`2`)
|
|
61
|
+
expect(store.gets).toMatchInlineSnapshot(`0`)
|
|
62
|
+
expect(store.size).toMatchInlineSnapshot(`0`)
|
|
63
|
+
})
|
|
64
|
+
|
|
54
65
|
test('refreshes the entry when the request sends `Cache-Control: no-cache`', async () => {
|
|
55
66
|
const { app, store, downstreamCalls } = createApp()
|
|
56
67
|
|
|
@@ -80,6 +80,8 @@ export function middleware<environment extends Env>(
|
|
|
80
80
|
store !== undefined &&
|
|
81
81
|
c.req.method === 'GET' &&
|
|
82
82
|
!c.req.raw.headers.has('range') &&
|
|
83
|
+
// Query credentials must reach auth and never enter cache keys.
|
|
84
|
+
!new URL(c.req.url).searchParams.has('key') &&
|
|
83
85
|
isCacheableRoute(c)
|
|
84
86
|
const key = eligible ? cacheKey(c.req.url) : undefined
|
|
85
87
|
|
|
@@ -9,6 +9,15 @@ const baseUsdcRequest = {
|
|
|
9
9
|
sourceToken: 'USDC',
|
|
10
10
|
} as const
|
|
11
11
|
|
|
12
|
+
const baseAcrossQuoteRequest = {
|
|
13
|
+
depositor: '0x1111111111111111111111111111111111111111',
|
|
14
|
+
provider: 'across',
|
|
15
|
+
sourceAmount: '1000000',
|
|
16
|
+
sourceAmountUnits: 'baseUnits',
|
|
17
|
+
sourceChain: 'base',
|
|
18
|
+
sourceToken: 'USDC',
|
|
19
|
+
} as const
|
|
20
|
+
|
|
12
21
|
describe('listRoutes', () => {
|
|
13
22
|
test('enriches static candidates with adapter quote probes', async () => {
|
|
14
23
|
let seen: FundingRoutes.QuoteProbeInput | undefined
|
|
@@ -200,6 +209,23 @@ describe('listRoutes', () => {
|
|
|
200
209
|
})
|
|
201
210
|
})
|
|
202
211
|
|
|
212
|
+
describe('quoteRoute', () => {
|
|
213
|
+
test('rejects requests without a configured quote capability', async () => {
|
|
214
|
+
await expect(FundingRoutes.quoteRoute(baseAcrossQuoteRequest)).rejects.toBeInstanceOf(
|
|
215
|
+
FundingRoutes.ProviderNotConfiguredError,
|
|
216
|
+
)
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
test('rejects requests outside curated inventory', async () => {
|
|
220
|
+
await expect(
|
|
221
|
+
FundingRoutes.quoteRoute({
|
|
222
|
+
...baseAcrossQuoteRequest,
|
|
223
|
+
sourceToken: 'USDT',
|
|
224
|
+
}),
|
|
225
|
+
).rejects.toBeInstanceOf(FundingRoutes.FundingRouteNotFoundError)
|
|
226
|
+
})
|
|
227
|
+
})
|
|
228
|
+
|
|
203
229
|
describe('parseInclude', () => {
|
|
204
230
|
test('parses comma-separated include values', () => {
|
|
205
231
|
expect(FundingRoutes.parseInclude('quotes,quotes')).toEqual(['quotes'])
|