tapimo 0.7.2 → 0.8.0
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 +10 -7
- package/dist/ApiKey.d.ts +3 -0
- package/dist/ApiKey.d.ts.map +1 -1
- package/dist/ApiKey.js +10 -0
- package/dist/ApiKey.js.map +1 -1
- package/dist/ApiKeys.d.ts +7 -3
- package/dist/ApiKeys.d.ts.map +1 -1
- package/dist/ApiKeys.js +9 -2
- package/dist/ApiKeys.js.map +1 -1
- package/dist/App.d.ts +20434 -19484
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js +62 -4
- package/dist/App.js.map +1 -1
- package/dist/admin/App.d.ts +84 -74
- package/dist/admin/App.d.ts.map +1 -1
- package/dist/admin/apps/api-keys.d.ts +14 -1
- package/dist/admin/apps/api-keys.d.ts.map +1 -1
- package/dist/admin/apps/api-keys.js +6 -2
- package/dist/admin/apps/api-keys.js.map +1 -1
- package/dist/admin/apps/organizations.d.ts +2 -0
- package/dist/admin/apps/organizations.d.ts.map +1 -1
- package/dist/admin/apps/verified-tokens.d.ts +168 -168
- package/dist/admin/ui.generated.d.ts +1 -1
- package/dist/admin/ui.generated.js +67 -67
- package/dist/admin/ui.generated.js.map +1 -1
- package/dist/analytics/Analytics.d.ts +2 -0
- package/dist/analytics/Analytics.d.ts.map +1 -1
- package/dist/analytics/Analytics.js +3 -0
- package/dist/analytics/Analytics.js.map +1 -1
- package/dist/apps/data/App.d.ts +10305 -12785
- package/dist/apps/data/App.d.ts.map +1 -1
- package/dist/apps/data/App.js +4 -8
- package/dist/apps/data/App.js.map +1 -1
- package/dist/apps/data/routes/activities.d.ts +24 -24
- package/dist/apps/data/routes/addresses.d.ts +288 -288
- package/dist/apps/data/routes/balances.d.ts +6 -6
- package/dist/apps/data/routes/blocks.d.ts +39 -39
- package/dist/apps/data/routes/coingecko.d.ts +170 -170
- package/dist/apps/data/routes/earn.d.ts +436 -6
- package/dist/apps/data/routes/earn.d.ts.map +1 -1
- package/dist/apps/data/routes/earn.js +631 -4
- package/dist/apps/data/routes/earn.js.map +1 -1
- package/dist/apps/data/routes/exchanges.d.ts +10 -10
- package/dist/apps/data/routes/fee-amm.d.ts +2 -2
- package/dist/apps/data/routes/indexer.d.ts +6 -6
- package/dist/apps/data/routes/indexer.js +4 -1
- package/dist/apps/data/routes/indexer.js.map +1 -1
- package/dist/apps/data/routes/receipts.d.ts +235 -235
- package/dist/apps/data/routes/rpc.d.ts +1 -1
- package/dist/apps/data/routes/rpc.js +4 -1
- package/dist/apps/data/routes/rpc.js.map +1 -1
- package/dist/apps/data/routes/tokenlist.d.ts +36 -36
- package/dist/apps/data/routes/tokens.d.ts +6 -6
- package/dist/apps/data/routes/transactions.d.ts +2 -2
- package/dist/apps/data/routes/transfers.d.ts +1 -1
- package/dist/apps/data/routes/valuation.d.ts +1 -1
- package/dist/apps/data/routes/verified-tokens.d.ts +36 -36
- package/dist/apps/data/routes/webhooks.d.ts +9 -9
- package/dist/apps/data/routes/zones.d.ts +14 -14
- package/dist/apps/funding/Access.d.ts +16 -0
- package/dist/apps/funding/Access.d.ts.map +1 -0
- package/dist/apps/funding/Access.js +22 -0
- package/dist/apps/funding/Access.js.map +1 -0
- package/dist/apps/{data/routes/funding.d.ts → funding/App.d.ts} +1717 -3035
- package/dist/apps/funding/App.d.ts.map +1 -0
- package/dist/apps/funding/App.js +46 -0
- package/dist/apps/funding/App.js.map +1 -0
- package/dist/apps/funding/Inventory.d.ts +5 -0
- package/dist/apps/funding/Inventory.d.ts.map +1 -0
- package/dist/apps/funding/Inventory.js +25 -0
- package/dist/apps/funding/Inventory.js.map +1 -0
- package/dist/apps/funding/ProviderAttempts.d.ts +5 -0
- package/dist/apps/funding/ProviderAttempts.d.ts.map +1 -0
- package/dist/apps/funding/ProviderAttempts.js +24 -0
- package/dist/apps/funding/ProviderAttempts.js.map +1 -0
- package/dist/apps/funding/index.d.ts +2 -0
- package/dist/apps/funding/index.d.ts.map +1 -0
- package/dist/apps/funding/index.js +2 -0
- package/dist/apps/funding/index.js.map +1 -0
- package/dist/apps/funding/routes/chains.d.ts +169 -0
- package/dist/apps/funding/routes/chains.d.ts.map +1 -0
- package/dist/apps/funding/routes/chains.js +80 -0
- package/dist/apps/funding/routes/chains.js.map +1 -0
- package/dist/apps/funding/routes/deposit-addresses.d.ts +2388 -0
- package/dist/apps/funding/routes/deposit-addresses.d.ts.map +1 -0
- package/dist/apps/funding/routes/deposit-addresses.js +860 -0
- package/dist/apps/funding/routes/deposit-addresses.js.map +1 -0
- package/dist/apps/funding/routes/providers.d.ts +91 -0
- package/dist/apps/funding/routes/providers.d.ts.map +1 -0
- package/dist/apps/funding/routes/providers.js +56 -0
- package/dist/apps/funding/routes/providers.js.map +1 -0
- package/dist/apps/funding/routes/quotes.d.ts +426 -0
- package/dist/apps/funding/routes/quotes.d.ts.map +1 -0
- package/dist/apps/funding/routes/quotes.js +188 -0
- package/dist/apps/funding/routes/quotes.js.map +1 -0
- package/dist/apps/funding/routes/transfers.d.ts +1999 -0
- package/dist/apps/funding/routes/transfers.d.ts.map +1 -0
- package/dist/apps/funding/routes/transfers.js +614 -0
- package/dist/apps/funding/routes/transfers.js.map +1 -0
- package/dist/apps/index.d.ts +1 -0
- package/dist/apps/index.d.ts.map +1 -1
- package/dist/apps/index.js +1 -0
- package/dist/apps/index.js.map +1 -1
- package/dist/apps/management/App.d.ts +430 -165
- package/dist/apps/management/App.d.ts.map +1 -1
- package/dist/apps/management/routes/api-keys.d.ts +298 -21
- package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
- package/dist/apps/management/routes/api-keys.js +75 -1
- package/dist/apps/management/routes/api-keys.js.map +1 -1
- package/dist/apps/management/routes/billing.d.ts +6 -6
- package/dist/apps/management/routes/faucet.d.ts +35 -35
- package/dist/apps/management/routes/invitations.d.ts +48 -48
- package/dist/apps/management/routes/invite-links.d.ts +38 -38
- package/dist/apps/management/routes/members.d.ts +69 -69
- package/dist/apps/management/routes/orgs.d.ts +37 -37
- package/dist/apps/management/routes/projects.d.ts +25 -25
- package/dist/apps/management/routes/scopes.d.ts +1 -1
- package/dist/apps/management/routes/usage.d.ts +72 -72
- package/dist/apps/management/routes/verified-token-requests.d.ts +18 -18
- package/dist/apps/management/routes/webhooks.d.ts +128 -128
- package/dist/apps/mcp.d.ts +2 -3
- package/dist/apps/mcp.d.ts.map +1 -1
- package/dist/apps/mcp.js +16 -14
- package/dist/apps/mcp.js.map +1 -1
- package/dist/apps/metadata.d.ts +4 -0
- package/dist/apps/metadata.d.ts.map +1 -1
- package/dist/apps/metadata.js +10 -0
- package/dist/apps/metadata.js.map +1 -1
- package/dist/apps/mpp/App.d.ts +2 -2
- package/dist/cloudflare.d.ts +50 -0
- package/dist/cloudflare.d.ts.map +1 -1
- package/dist/cloudflare.js +178 -0
- package/dist/cloudflare.js.map +1 -1
- package/dist/db/Db.d.ts +2 -0
- package/dist/db/Db.d.ts.map +1 -1
- package/dist/db/Db.js +37 -0
- package/dist/db/Db.js.map +1 -1
- package/dist/db/Schema.d.ts +12 -0
- package/dist/db/Schema.d.ts.map +1 -1
- package/dist/db/Schema.js +20 -0
- package/dist/db/Schema.js.map +1 -1
- package/dist/db/tables/fundingDepositAddresses.d.ts +13 -0
- package/dist/db/tables/fundingDepositAddresses.d.ts.map +1 -1
- package/dist/db/tables/fundingDepositAddresses.js +12 -0
- package/dist/db/tables/fundingDepositAddresses.js.map +1 -1
- package/dist/db/tables/fundingDepositRequestObservations.d.ts +45 -0
- package/dist/db/tables/fundingDepositRequestObservations.d.ts.map +1 -0
- package/dist/db/tables/fundingDepositRequestObservations.js +46 -0
- package/dist/db/tables/fundingDepositRequestObservations.js.map +1 -0
- package/dist/db/tables/fundingDeposits.d.ts +57 -1
- package/dist/db/tables/fundingDeposits.d.ts.map +1 -1
- package/dist/db/tables/fundingDeposits.js +82 -0
- package/dist/db/tables/fundingDeposits.js.map +1 -1
- package/dist/internal/Auth.d.ts +11 -11
- package/dist/internal/Auth.d.ts.map +1 -1
- package/dist/internal/Auth.js +41 -15
- package/dist/internal/Auth.js.map +1 -1
- package/dist/internal/Cache.js +1 -1
- package/dist/internal/Cache.js.map +1 -1
- package/dist/internal/EdgeCache.d.ts +2 -2
- package/dist/internal/EdgeCache.d.ts.map +1 -1
- package/dist/internal/EdgeCache.js +10 -4
- package/dist/internal/EdgeCache.js.map +1 -1
- package/dist/internal/IpAllowlist.d.ts +15 -0
- package/dist/internal/IpAllowlist.d.ts.map +1 -0
- package/dist/internal/IpAllowlist.js +27 -0
- package/dist/internal/IpAllowlist.js.map +1 -0
- package/dist/internal/OpenApi.d.ts +1 -1
- package/dist/internal/OpenApi.js +1 -1
- package/dist/internal/OpenApi.js.map +1 -1
- package/dist/internal/Tidx.d.ts +6 -0
- package/dist/internal/Tidx.d.ts.map +1 -1
- package/dist/internal/Tidx.js +13 -3
- package/dist/internal/Tidx.js.map +1 -1
- package/dist/internal/Viem.d.ts +8 -0
- package/dist/internal/Viem.d.ts.map +1 -1
- package/dist/internal/Viem.js +12 -5
- package/dist/internal/Viem.js.map +1 -1
- package/dist/internal/funding/Deposit.d.ts +42 -0
- package/dist/internal/funding/Deposit.d.ts.map +1 -1
- package/dist/internal/funding/Deposit.js +37 -5
- package/dist/internal/funding/Deposit.js.map +1 -1
- package/dist/internal/funding/Evidence.d.ts +1 -5
- package/dist/internal/funding/Evidence.d.ts.map +1 -1
- package/dist/internal/funding/Evidence.js +4 -3
- package/dist/internal/funding/Evidence.js.map +1 -1
- package/dist/internal/funding/Provider.d.ts +102 -0
- package/dist/internal/funding/Provider.d.ts.map +1 -1
- package/dist/internal/funding/Provider.js +5 -0
- package/dist/internal/funding/Provider.js.map +1 -1
- package/dist/internal/funding/Reconciliation.d.ts +16 -2
- package/dist/internal/funding/Reconciliation.d.ts.map +1 -1
- package/dist/internal/funding/Reconciliation.js +257 -48
- package/dist/internal/funding/Reconciliation.js.map +1 -1
- package/dist/internal/funding/SourceObservation.d.ts +108 -0
- package/dist/internal/funding/SourceObservation.d.ts.map +1 -0
- package/dist/internal/funding/SourceObservation.js +143 -0
- package/dist/internal/funding/SourceObservation.js.map +1 -0
- package/dist/internal/funding/Subsidy.d.ts +2 -2
- package/dist/internal/funding/Subsidy.d.ts.map +1 -1
- package/dist/internal/funding/Subsidy.js +1 -1
- package/dist/internal/funding/Subsidy.js.map +1 -1
- package/dist/internal/funding/index.d.ts +1 -0
- package/dist/internal/funding/index.d.ts.map +1 -1
- package/dist/internal/funding/index.js +1 -0
- package/dist/internal/funding/index.js.map +1 -1
- package/dist/internal/funding/providers/relay.d.ts +43 -1
- package/dist/internal/funding/providers/relay.d.ts.map +1 -1
- package/dist/internal/funding/providers/relay.js +434 -27
- package/dist/internal/funding/providers/relay.js.map +1 -1
- package/package.json +3 -3
- package/src/ApiKey.test.ts +35 -0
- package/src/ApiKey.ts +27 -0
- package/src/ApiKeys.test.ts +29 -0
- package/src/ApiKeys.ts +11 -3
- package/src/App.test.ts +23 -5
- package/src/App.ts +76 -5
- package/src/Client.test-d.ts +171 -19
- package/src/admin/apps/api-keys.test.ts +25 -1
- package/src/admin/apps/api-keys.ts +6 -2
- package/src/admin/ui.generated.ts +67 -67
- package/src/analytics/Analytics.ts +4 -0
- package/src/apps/composition.test.ts +100 -2
- package/src/apps/data/App.test.ts +0 -12
- package/src/apps/data/App.ts +4 -8
- package/src/apps/data/routes/earn.test.ts +283 -0
- package/src/apps/data/routes/earn.ts +800 -3
- package/src/apps/data/routes/indexer.ts +2 -1
- package/src/apps/data/routes/rpc.ts +2 -0
- package/src/apps/data/routes/tokens.test.ts +6 -8
- package/src/apps/funding/Access.ts +26 -0
- package/src/apps/funding/App.test.ts +78 -0
- package/src/apps/funding/App.ts +63 -0
- package/src/apps/funding/Inventory.ts +30 -0
- package/src/apps/funding/ProviderAttempts.ts +26 -0
- package/src/apps/funding/index.ts +1 -0
- package/src/apps/funding/routes/chains.test.ts +78 -0
- package/src/apps/funding/routes/chains.ts +101 -0
- package/src/apps/funding/routes/deposit-addresses.test.ts +1628 -0
- package/src/apps/funding/routes/deposit-addresses.ts +1015 -0
- package/src/apps/funding/routes/providers.test.ts +165 -0
- package/src/apps/funding/routes/providers.ts +73 -0
- package/src/apps/funding/routes/quotes.test.ts +511 -0
- package/src/apps/funding/routes/quotes.ts +205 -0
- package/src/apps/funding/routes/transfers.test.ts +1255 -0
- package/src/apps/funding/routes/transfers.ts +704 -0
- package/src/apps/index.ts +1 -0
- package/src/apps/management/routes/api-keys.test.ts +98 -0
- package/src/apps/management/routes/api-keys.ts +96 -1
- package/src/apps/mcp.test.ts +17 -2
- package/src/apps/mcp.ts +18 -13
- package/src/apps/metadata.ts +12 -0
- package/src/apps/mpp/internal/Policy.test.ts +7 -1
- package/src/cloudflare.ts +279 -0
- package/src/db/Db.ts +62 -0
- package/src/db/Schema.ts +24 -0
- package/src/db/tables/fundingDepositAddresses.ts +28 -0
- package/src/db/tables/fundingDepositRequestObservations.test.ts +106 -0
- package/src/db/tables/fundingDepositRequestObservations.ts +99 -0
- package/src/db/tables/fundingDeposits.test.ts +138 -0
- package/src/db/tables/fundingDeposits.ts +145 -1
- package/src/internal/Auth.test.ts +177 -5
- package/src/internal/Auth.ts +48 -18
- package/src/internal/Cache.test.ts +37 -3
- package/src/internal/Cache.ts +1 -1
- package/src/internal/EdgeCache.test.ts +7 -7
- package/src/internal/EdgeCache.ts +13 -4
- package/src/internal/IpAllowlist.test.ts +52 -0
- package/src/internal/IpAllowlist.ts +39 -0
- package/src/internal/Log.test.ts +7 -1
- package/src/internal/OpenApi.ts +1 -1
- package/src/internal/Tidx.test.ts +28 -0
- package/src/internal/Tidx.ts +19 -3
- package/src/internal/Viem.test.ts +28 -0
- package/src/internal/Viem.ts +21 -5
- package/src/internal/funding/Deposit.ts +74 -5
- package/src/internal/funding/Evidence.test.ts +2 -2
- package/src/internal/funding/Evidence.ts +4 -9
- package/src/internal/funding/Provider.ts +115 -0
- package/src/internal/funding/Reconciliation.local.test.ts +7 -0
- package/src/internal/funding/Reconciliation.test.ts +1052 -35
- package/src/internal/funding/Reconciliation.ts +328 -56
- package/src/internal/funding/SourceObservation.test.ts +301 -0
- package/src/internal/funding/SourceObservation.ts +267 -0
- package/src/internal/funding/Subsidy.ts +2 -2
- package/src/internal/funding/index.test.ts +1 -0
- package/src/internal/funding/index.ts +1 -0
- package/src/internal/funding/providers/relay.test.ts +94 -4
- package/src/internal/funding/providers/relay.ts +528 -27
- package/dist/apps/data/routes/funding.d.ts.map +0 -1
- package/dist/apps/data/routes/funding.js +0 -1557
- package/dist/apps/data/routes/funding.js.map +0 -1
- package/src/apps/data/routes/funding.test.ts +0 -2994
- package/src/apps/data/routes/funding.ts +0 -1821
package/src/apps/index.ts
CHANGED
|
@@ -256,6 +256,99 @@ describe('keys', () => {
|
|
|
256
256
|
expect(empty).toEqual({ data: [] })
|
|
257
257
|
})
|
|
258
258
|
|
|
259
|
+
test('creates, reads, updates, enforces, and clears an IP allowlist', async () => {
|
|
260
|
+
const state = TestApp.kvStore()
|
|
261
|
+
const app = TestApp.create({
|
|
262
|
+
// Avoid waiting for the accepted KV and isolate-cache propagation delay.
|
|
263
|
+
auth: { cache: false, superAdmin: { secret } },
|
|
264
|
+
kv: { store: state },
|
|
265
|
+
session: { wallet: { origin } },
|
|
266
|
+
}).get(
|
|
267
|
+
'/custom/allowlist-test',
|
|
268
|
+
Auth.policy({ apiKey: { scopes: ['data:read'] } }),
|
|
269
|
+
OpenApi.describeRoute({
|
|
270
|
+
operationId: 'getAllowlistTest',
|
|
271
|
+
responses: { 200: { description: 'Allowlist test response.' } },
|
|
272
|
+
summary: 'Test allowlist',
|
|
273
|
+
}),
|
|
274
|
+
(c) => c.json({ ok: true }, 200),
|
|
275
|
+
)
|
|
276
|
+
const cookie = await session(app)
|
|
277
|
+
const { org, project } = await fixture(app, cookie)
|
|
278
|
+
const base = `/v1/orgs/${org.id}/projects/${project.id}/api-keys`
|
|
279
|
+
const created = await TestApp.json(
|
|
280
|
+
await app.request(
|
|
281
|
+
base,
|
|
282
|
+
superAdminJson('POST', {
|
|
283
|
+
allowedIps: ['203.0.113.0/24'],
|
|
284
|
+
scopes: ['data:read'],
|
|
285
|
+
}),
|
|
286
|
+
),
|
|
287
|
+
Keys.schema.createApiKey.Response,
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
expect(created.allowedIps).toEqual(['203.0.113.0/24'])
|
|
291
|
+
const authenticate = (address?: string) =>
|
|
292
|
+
app.request('/custom/allowlist-test', {
|
|
293
|
+
headers: {
|
|
294
|
+
...(address ? { 'cf-connecting-ip': address } : {}),
|
|
295
|
+
'tempo-api-key': created.token,
|
|
296
|
+
},
|
|
297
|
+
})
|
|
298
|
+
const listed = await TestApp.json(
|
|
299
|
+
await app.request(base, { headers: { cookie } }),
|
|
300
|
+
Keys.schema.listApiKeys.Response,
|
|
301
|
+
)
|
|
302
|
+
expect(listed.data[0]?.allowedIps).toEqual(['203.0.113.0/24'])
|
|
303
|
+
|
|
304
|
+
expect((await authenticate('203.0.113.7')).status).toBe(200)
|
|
305
|
+
const denied = await authenticate('198.51.100.7')
|
|
306
|
+
expect(denied.status).toBe(403)
|
|
307
|
+
expect(await denied.json()).toMatchObject({ error: { code: 'api_key_ip_forbidden' } })
|
|
308
|
+
|
|
309
|
+
const updated = await TestApp.json(
|
|
310
|
+
await app.request(
|
|
311
|
+
`${base}/${created.id}`,
|
|
312
|
+
json('PATCH', cookie, { allowedIps: ['198.51.100.7'] }),
|
|
313
|
+
),
|
|
314
|
+
Keys.schema.Key,
|
|
315
|
+
)
|
|
316
|
+
expect(updated.allowedIps).toEqual(['198.51.100.7'])
|
|
317
|
+
|
|
318
|
+
expect((await authenticate('203.0.113.7')).status).toBe(403)
|
|
319
|
+
expect((await authenticate('198.51.100.7')).status).toBe(200)
|
|
320
|
+
|
|
321
|
+
const cleared = await TestApp.json(
|
|
322
|
+
await app.request(`${base}/${created.id}`, json('PATCH', cookie, { allowedIps: [] })),
|
|
323
|
+
Keys.schema.Key,
|
|
324
|
+
)
|
|
325
|
+
expect(cleared.allowedIps).toEqual([])
|
|
326
|
+
expect((await authenticate()).status).toBe(200)
|
|
327
|
+
})
|
|
328
|
+
|
|
329
|
+
test('rejects malformed IP allowlists on create and update', async () => {
|
|
330
|
+
const app = createApp()
|
|
331
|
+
const cookie = await session(app)
|
|
332
|
+
const { org, project } = await fixture(app, cookie)
|
|
333
|
+
const base = `/v1/orgs/${org.id}/projects/${project.id}/api-keys`
|
|
334
|
+
|
|
335
|
+
const invalidCreate = await app.request(
|
|
336
|
+
base,
|
|
337
|
+
json('POST', cookie, { allowedIps: ['203.0.113.0/33'], scopes: ['data:read'] }),
|
|
338
|
+
)
|
|
339
|
+
expect(invalidCreate.status).toBe(400)
|
|
340
|
+
|
|
341
|
+
const created = await TestApp.json(
|
|
342
|
+
await app.request(base, json('POST', cookie, { scopes: ['data:read'] })),
|
|
343
|
+
Keys.schema.createApiKey.Response,
|
|
344
|
+
)
|
|
345
|
+
const invalidUpdate = await app.request(
|
|
346
|
+
`${base}/${created.id}`,
|
|
347
|
+
json('PATCH', cookie, { allowedIps: ['*'] }),
|
|
348
|
+
)
|
|
349
|
+
expect(invalidUpdate.status).toBe(400)
|
|
350
|
+
})
|
|
351
|
+
|
|
259
352
|
test('rejects the wildcard scope at mint', async () => {
|
|
260
353
|
const app = createApp()
|
|
261
354
|
const cookie = await session(app)
|
|
@@ -427,6 +520,11 @@ describe('keys', () => {
|
|
|
427
520
|
{ headers: { cookie: owner }, method: 'DELETE' },
|
|
428
521
|
)
|
|
429
522
|
expect(crossRevoke.status).toBe(404)
|
|
523
|
+
const crossUpdate = await app.request(
|
|
524
|
+
`/v1/orgs/${org.id}/projects/${other.id}/api-keys/${minted.id}`,
|
|
525
|
+
json('PATCH', owner, { allowedIps: ['203.0.113.0/24'] }),
|
|
526
|
+
)
|
|
527
|
+
expect(crossUpdate.status).toBe(404)
|
|
430
528
|
|
|
431
529
|
// Foreign session: the whole surface 404s at the org-ownership link.
|
|
432
530
|
const stranger = await session(app)
|
|
@@ -3,6 +3,7 @@ import * as z from 'zod/mini'
|
|
|
3
3
|
|
|
4
4
|
import * as Analytics from '../../../analytics/Analytics.js'
|
|
5
5
|
import * as RequestEvents from '../../../analytics/tables/requestEvents.js'
|
|
6
|
+
import * as ApiKey from '../../../ApiKey.js'
|
|
6
7
|
import * as ApiKeys from '../../../ApiKeys.js'
|
|
7
8
|
import type * as App from '../../../App.js'
|
|
8
9
|
import * as Db from '../../../db/Db.js'
|
|
@@ -19,6 +20,12 @@ export namespace schema {
|
|
|
19
20
|
/** One API key: metadata only — the token appears once, at mint. */
|
|
20
21
|
export const Key = Schema.describe(
|
|
21
22
|
z.object({
|
|
23
|
+
allowedIps: ApiKey.schema.AllowedIps.check(
|
|
24
|
+
z.describe(
|
|
25
|
+
'Client IP addresses and CIDR ranges allowed to use this key. An empty list means unrestricted.',
|
|
26
|
+
),
|
|
27
|
+
z.meta({ examples: [['203.0.113.0/24', '2001:db8::1']] }),
|
|
28
|
+
),
|
|
22
29
|
createdAt: z
|
|
23
30
|
.string()
|
|
24
31
|
.check(
|
|
@@ -140,6 +147,14 @@ export namespace schema {
|
|
|
140
147
|
/** Request body minting a key. The wildcard scope is not issuable here. */
|
|
141
148
|
export const KeyBody = z
|
|
142
149
|
.object({
|
|
150
|
+
allowedIps: z
|
|
151
|
+
.optional(ApiKey.schema.AllowedIps)
|
|
152
|
+
.check(
|
|
153
|
+
z.describe(
|
|
154
|
+
'Client IP addresses and CIDR ranges allowed to use the key. Omit or use an empty list for unrestricted access.',
|
|
155
|
+
),
|
|
156
|
+
z.meta({ examples: [['203.0.113.0/24']] }),
|
|
157
|
+
),
|
|
143
158
|
environment: z
|
|
144
159
|
._default(z.enum(['production', 'sandbox']), 'production')
|
|
145
160
|
.check(z.describe('Key environment.'), z.meta({ examples: ['sandbox'] })),
|
|
@@ -224,10 +239,25 @@ export namespace schema {
|
|
|
224
239
|
})
|
|
225
240
|
.check(z.describe('Confirmation that the key was revoked.'))
|
|
226
241
|
}
|
|
242
|
+
|
|
243
|
+
/** Schemas for the updateApiKey operation. */
|
|
244
|
+
export namespace updateApiKey {
|
|
245
|
+
/** Request body replacing or clearing a key's IP allowlist. */
|
|
246
|
+
export const Body = z
|
|
247
|
+
.object({
|
|
248
|
+
allowedIps: ApiKey.schema.AllowedIps.check(
|
|
249
|
+
z.describe(
|
|
250
|
+
'Replacement client IP/CIDR allowlist. Use an empty list for unrestricted access.',
|
|
251
|
+
),
|
|
252
|
+
z.meta({ examples: [['203.0.113.0/24']] }),
|
|
253
|
+
),
|
|
254
|
+
})
|
|
255
|
+
.check(z.describe("Fields for updating an API key's network access."))
|
|
256
|
+
}
|
|
227
257
|
}
|
|
228
258
|
|
|
229
259
|
/**
|
|
230
|
-
* Mounts project-attributed API key mint, list, and revoke routes plus the org-wide list. Project routes enforce caller organization and project attribution.
|
|
260
|
+
* Mounts project-attributed API key mint, list, update, and revoke routes plus the org-wide list. Project routes enforce caller organization and project attribution.
|
|
231
261
|
*/
|
|
232
262
|
export function apiKeys(options: apiKeys.Options = {}) {
|
|
233
263
|
const hidden = !options.enabled
|
|
@@ -313,6 +343,7 @@ export function apiKeys(options: apiKeys.Options = {}) {
|
|
|
313
343
|
{
|
|
314
344
|
createdBy,
|
|
315
345
|
environment: body.environment,
|
|
346
|
+
...(body.allowedIps === undefined ? {} : { allowedIps: body.allowedIps }),
|
|
316
347
|
...(billingActive === undefined ? {} : { billingActive }),
|
|
317
348
|
...(body.name === undefined ? {} : { name: body.name }),
|
|
318
349
|
orgId,
|
|
@@ -453,6 +484,69 @@ export function apiKeys(options: apiKeys.Options = {}) {
|
|
|
453
484
|
}
|
|
454
485
|
},
|
|
455
486
|
)
|
|
487
|
+
.patch(
|
|
488
|
+
'/v1/orgs/:orgId{org_[A-Za-z0-9_-]+}/projects/:projectId{prj_[A-Za-z0-9_-]+}/api-keys/:keyId{key_[A-Za-z0-9_-]+}',
|
|
489
|
+
Auth.policy({ apiKey: { scopes: ['management:write'] }, session: true }),
|
|
490
|
+
Auth.ensureOrg({ role: 'admin' }),
|
|
491
|
+
Auth.ensureProject(),
|
|
492
|
+
OpenApi.validate('param', schema.KeyParams, {
|
|
493
|
+
code: 'param_invalid',
|
|
494
|
+
message: 'Check the path parameters and try again.',
|
|
495
|
+
}),
|
|
496
|
+
OpenApi.validate('json', schema.updateApiKey.Body, {
|
|
497
|
+
code: 'body_invalid',
|
|
498
|
+
message: 'Check the request body and try again.',
|
|
499
|
+
}),
|
|
500
|
+
OpenApi.describeRoute({
|
|
501
|
+
description:
|
|
502
|
+
"Replace an API key's client IP/CIDR allowlist. An empty list removes the restriction.",
|
|
503
|
+
hide: hidden,
|
|
504
|
+
operationId: 'updateApiKey',
|
|
505
|
+
responses: OpenApi.responses({
|
|
506
|
+
errors: {
|
|
507
|
+
404: 'No accessible API key was found for this id.',
|
|
508
|
+
},
|
|
509
|
+
success: {
|
|
510
|
+
description: 'The updated API key metadata.',
|
|
511
|
+
schema: schema.Key,
|
|
512
|
+
},
|
|
513
|
+
}),
|
|
514
|
+
summary: 'Update API key',
|
|
515
|
+
tags: ['API Keys'],
|
|
516
|
+
}),
|
|
517
|
+
async (c) => {
|
|
518
|
+
if (Auth.narrowAccess) return Auth.accessError(c)
|
|
519
|
+
if (Auth.narrowScope) return Auth.ensureProjectError(c)
|
|
520
|
+
if (OpenApi.narrowValidation)
|
|
521
|
+
return OpenApi.validationError(c, {
|
|
522
|
+
code: 'param_invalid',
|
|
523
|
+
message: 'Check the path parameters and try again.',
|
|
524
|
+
})
|
|
525
|
+
if (OpenApi.narrowValidation)
|
|
526
|
+
return OpenApi.validationError(c, {
|
|
527
|
+
code: 'body_invalid',
|
|
528
|
+
message: 'Check the request body and try again.',
|
|
529
|
+
})
|
|
530
|
+
|
|
531
|
+
const kv = c.get('kv')
|
|
532
|
+
if (!kv) return notEnabled(c)
|
|
533
|
+
const { keyId } = c.req.valid('param')
|
|
534
|
+
try {
|
|
535
|
+
const record = await ApiKeys.get(kv.store, keyId, {
|
|
536
|
+
scopeCatalog: c.get('scopeCatalog'),
|
|
537
|
+
})
|
|
538
|
+
if (!record || record.orgId !== Auth.org(c).id || record.projectId !== Auth.project(c).id)
|
|
539
|
+
return keyNotFound(c)
|
|
540
|
+
const updated = await ApiKeys.update(kv.store, keyId, c.req.valid('json'), {
|
|
541
|
+
scopeCatalog: c.get('scopeCatalog'),
|
|
542
|
+
})
|
|
543
|
+
if (!updated) return keyNotFound(c)
|
|
544
|
+
return c.json(Response.validated(schema.Key, serializeKey(updated)), 200)
|
|
545
|
+
} catch (cause) {
|
|
546
|
+
return Response.upstream(c, cause)
|
|
547
|
+
}
|
|
548
|
+
},
|
|
549
|
+
)
|
|
456
550
|
.delete(
|
|
457
551
|
'/v1/orgs/:orgId{org_[A-Za-z0-9_-]+}/projects/:projectId{prj_[A-Za-z0-9_-]+}/api-keys/:keyId{key_[A-Za-z0-9_-]+}',
|
|
458
552
|
Auth.policy({ apiKey: { scopes: ['management:write'] }, session: true }),
|
|
@@ -532,6 +626,7 @@ async function serializeKeys(c: Context<App.Environment>, records: readonly ApiK
|
|
|
532
626
|
/** Projects a persisted record onto the management key shape (no rate limits, no token). */
|
|
533
627
|
function serializeKey(record: ApiKeys.Record, lastUsedAt?: string) {
|
|
534
628
|
return {
|
|
629
|
+
allowedIps: [...record.allowedIps],
|
|
535
630
|
createdAt: record.createdAt,
|
|
536
631
|
...(record.createdBy === undefined ? {} : { createdBy: record.createdBy }),
|
|
537
632
|
environment: record.environment,
|
package/src/apps/mcp.test.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { Cli as incur_Cli, z } from 'incur'
|
|
2
|
+
import { App } from 'tapimo'
|
|
3
|
+
import { data } from 'tapimo/apps'
|
|
2
4
|
|
|
3
5
|
import * as TestApp from '../../test/App.js'
|
|
4
6
|
import * as Runtime from '../../test/runtime.js'
|
|
@@ -17,7 +19,7 @@ const reader = {
|
|
|
17
19
|
/** Composes a test app with the MCP group mounted, dispatching in-process. */
|
|
18
20
|
function createHost(
|
|
19
21
|
options: {
|
|
20
|
-
app?:
|
|
22
|
+
app?: Host | undefined
|
|
21
23
|
docs?: mcp.Options['docs'] | undefined
|
|
22
24
|
} = {},
|
|
23
25
|
) {
|
|
@@ -149,7 +151,7 @@ test('identifies as Tempo MCP', async () => {
|
|
|
149
151
|
})
|
|
150
152
|
})
|
|
151
153
|
|
|
152
|
-
test('scopes tools to the data
|
|
154
|
+
test('scopes tools to the data and inbound funding domains', async () => {
|
|
153
155
|
const { app, host } = createHost({ docs: docsStub() })
|
|
154
156
|
|
|
155
157
|
// The domain filter must drop paths that actually exist in the spec.
|
|
@@ -170,6 +172,8 @@ test('scopes tools to the data domain', async () => {
|
|
|
170
172
|
const tools = (await toolsSearch(host)).map((tool) => tool.name)
|
|
171
173
|
|
|
172
174
|
expect(tools).toContain('v1_tokens_get')
|
|
175
|
+
expect(tools).toContain('v1_funding_quotes')
|
|
176
|
+
expect(tools).toContain('v1_funding_transfers_post')
|
|
173
177
|
expect(tools).toContain('docs_search')
|
|
174
178
|
// RPC is part of the data domain tag group.
|
|
175
179
|
expect(tools.some((name) => name.startsWith('rpc'))).toBe(true)
|
|
@@ -180,6 +184,17 @@ test('scopes tools to the data domain', async () => {
|
|
|
180
184
|
expect(tools.some((name) => name.startsWith('v1_me'))).toBe(false)
|
|
181
185
|
})
|
|
182
186
|
|
|
187
|
+
test('keeps the data tools functional when funding is omitted', async () => {
|
|
188
|
+
const app = App.create({ auth: false, db: TestApp.database() }).route('/', data())
|
|
189
|
+
const { host } = createHost({ app })
|
|
190
|
+
|
|
191
|
+
await initialize(host)
|
|
192
|
+
const tools = (await toolsSearch(host)).map((tool) => tool.name)
|
|
193
|
+
|
|
194
|
+
expect(tools).toContain('v1_tokens_get')
|
|
195
|
+
expect(tools.some((name) => name.startsWith('v1_funding_'))).toBe(false)
|
|
196
|
+
})
|
|
197
|
+
|
|
183
198
|
test('keeps tool input schemas compact', async () => {
|
|
184
199
|
const { host } = createHost()
|
|
185
200
|
await initialize(host)
|
package/src/apps/mcp.ts
CHANGED
|
@@ -7,15 +7,18 @@ import * as Auth from '../internal/Auth.js'
|
|
|
7
7
|
import * as OpenApi from '../internal/OpenApi.js'
|
|
8
8
|
import * as Metadata from './metadata.js'
|
|
9
9
|
|
|
10
|
-
/**
|
|
11
|
-
const
|
|
10
|
+
/** Required document tag group whose operations surface as MCP tools. */
|
|
11
|
+
const dataTagGroup = 'Data API'
|
|
12
|
+
|
|
13
|
+
/** Optional document tag group added when the funding app is mounted. */
|
|
14
|
+
const fundingTagGroup = 'Funding & Bridge API'
|
|
12
15
|
|
|
13
16
|
/** Tags inside the group hidden from MCP: partner-shaped CoinGecko mirrors and the MCP endpoint itself. */
|
|
14
17
|
const excludedTags = ['CoinGecko', 'MCP']
|
|
15
18
|
|
|
16
19
|
/** Usage guidance surfaced to MCP clients on initialize. */
|
|
17
20
|
const instructions =
|
|
18
|
-
"Tempo MCP provides access to the Tempo API's data
|
|
21
|
+
"Tempo MCP provides access to the Tempo API's data and inbound funding domains, plus documentation. Authenticated tools accept a Tempo API key sent as an `Authorization: Bearer` header on the HTTP request. List endpoints paginate via `cursor`/`nextCursor`. `docs_*` tools search the Tempo documentation."
|
|
19
22
|
|
|
20
23
|
/** Zod schemas owned by the MCP endpoint's OpenAPI description. */
|
|
21
24
|
export namespace schema {
|
|
@@ -56,9 +59,8 @@ export namespace schema {
|
|
|
56
59
|
|
|
57
60
|
/**
|
|
58
61
|
* The MCP route group: a stateless streamable-HTTP MCP server at `/mcp` whose
|
|
59
|
-
* tools mirror the API's data domain
|
|
60
|
-
*
|
|
61
|
-
* the other route groups:
|
|
62
|
+
* tools mirror the API's data domain and its optional inbound funding domain,
|
|
63
|
+
* plus the remote Tempo docs tools. Mount alongside the other route groups:
|
|
62
64
|
*
|
|
63
65
|
* ```ts
|
|
64
66
|
* const app = App.create(options)
|
|
@@ -208,7 +210,7 @@ const openapi = {
|
|
|
208
210
|
'A hosted Model Context Protocol server: the data domain and Tempo docs exposed as tools.',
|
|
209
211
|
},
|
|
210
212
|
],
|
|
211
|
-
'x-tagGroups': [{ name:
|
|
213
|
+
'x-tagGroups': [{ name: dataTagGroup, tags: ['MCP'] }],
|
|
212
214
|
} satisfies App.create.Metadata
|
|
213
215
|
|
|
214
216
|
/** The slice of the OpenAPI document the domain filter reads. */
|
|
@@ -221,14 +223,17 @@ type Document = Openapi.OpenAPISpec & {
|
|
|
221
223
|
const methods: readonly string[] = ['delete', 'get', 'head', 'options', 'patch', 'post', 'put', 'trace'] // prettier-ignore
|
|
222
224
|
|
|
223
225
|
/**
|
|
224
|
-
* Narrows the document to the data
|
|
225
|
-
*
|
|
226
|
-
* operations never surface as tools.
|
|
226
|
+
* Narrows the document to the data and optional inbound funding domains.
|
|
227
|
+
* Management, relay, and session operations never surface as tools.
|
|
227
228
|
*/
|
|
228
229
|
function scope(document: Document): Openapi.OpenAPISpec {
|
|
229
|
-
const
|
|
230
|
-
|
|
231
|
-
|
|
230
|
+
const groups = document['x-tagGroups'] ?? []
|
|
231
|
+
const dataGroup = groups.find((entry) => entry.name === dataTagGroup)
|
|
232
|
+
if (!dataGroup) throw new Error(`openapi.json is missing the "${dataTagGroup}" tag group`)
|
|
233
|
+
const fundingGroup = groups.find((entry) => entry.name === fundingTagGroup)
|
|
234
|
+
const tags = new Set(
|
|
235
|
+
[...dataGroup.tags, ...(fundingGroup?.tags ?? [])].filter((tag) => !excludedTags.includes(tag)),
|
|
236
|
+
)
|
|
232
237
|
const paths: Document['paths'] = {}
|
|
233
238
|
for (const [path, item] of Object.entries(document.paths)) {
|
|
234
239
|
// Keep in-domain operations; non-method keys (parameters, summary) ride along.
|
package/src/apps/metadata.ts
CHANGED
|
@@ -7,6 +7,7 @@ import type * as App from '../App.js'
|
|
|
7
7
|
// `App.create` reads it back on mount. The `WeakMap` keeps the association off
|
|
8
8
|
// the app's type, so `testClient`/`hc` inference is untouched.
|
|
9
9
|
const registry = new WeakMap<Hono<any, any>, App.create.Metadata>()
|
|
10
|
+
const defaultChainRegistry = new WeakSet<Hono<any, any>>()
|
|
10
11
|
|
|
11
12
|
/** Attaches a group's OpenAPI metadata to its app instance for `create` to collect on mount. */
|
|
12
13
|
export function attach<const app extends Hono<any, any>>(
|
|
@@ -21,3 +22,14 @@ export function attach<const app extends Hono<any, any>>(
|
|
|
21
22
|
export function read(app: Hono<any, any>): App.create.Metadata | undefined {
|
|
22
23
|
return registry.get(app)
|
|
23
24
|
}
|
|
25
|
+
|
|
26
|
+
/** Marks a group whose routes use the root app's default chain access policy. */
|
|
27
|
+
export function defaultChain<const app extends Hono<any, any>>(app: app): app {
|
|
28
|
+
defaultChainRegistry.add(app)
|
|
29
|
+
return app
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Returns whether a group uses the root app's default chain access policy. */
|
|
33
|
+
export function usesDefaultChain(app: Hono<any, any>): boolean {
|
|
34
|
+
return defaultChainRegistry.has(app)
|
|
35
|
+
}
|
|
@@ -20,7 +20,13 @@ const input = {
|
|
|
20
20
|
} satisfies Mpp.RelayInput
|
|
21
21
|
|
|
22
22
|
const principal = {
|
|
23
|
-
apiKey: {
|
|
23
|
+
apiKey: {
|
|
24
|
+
allowedIps: [],
|
|
25
|
+
environment: 'production',
|
|
26
|
+
id: 'key_mpp',
|
|
27
|
+
orgId: 'org_mpp',
|
|
28
|
+
scopes: ['mpp:write'],
|
|
29
|
+
},
|
|
24
30
|
environment: 'production',
|
|
25
31
|
id: 'key_mpp',
|
|
26
32
|
orgId: 'org_mpp',
|