tapimo 0.4.4 → 0.5.1
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/dist/App.d.ts +4701 -3416
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js +24 -2
- package/dist/App.js.map +1 -1
- package/dist/Assets.js +3 -3
- package/dist/Assets.js.map +1 -1
- package/dist/Scope.d.ts +18 -1
- package/dist/Scope.d.ts.map +1 -1
- package/dist/Scope.js +23 -3
- package/dist/Scope.js.map +1 -1
- package/dist/Webhooks.d.ts +1 -1
- package/dist/Webhooks.d.ts.map +1 -1
- package/dist/Webhooks.js +5 -10
- package/dist/Webhooks.js.map +1 -1
- package/dist/admin/App.d.ts +6 -34
- package/dist/admin/App.d.ts.map +1 -1
- package/dist/admin/apps/api-keys.d.ts +3 -3
- package/dist/admin/apps/api-keys.d.ts.map +1 -1
- package/dist/admin/apps/api-keys.js +4 -1
- package/dist/admin/apps/api-keys.js.map +1 -1
- package/dist/admin/apps/scopes.d.ts +6 -34
- package/dist/admin/apps/scopes.d.ts.map +1 -1
- package/dist/admin/apps/scopes.js +2 -2
- package/dist/admin/apps/scopes.js.map +1 -1
- package/dist/admin/apps/verified-tokens.d.ts +57 -57
- package/dist/admin/ui.generated.d.ts +1 -1
- package/dist/admin/ui.generated.js +33 -33
- package/dist/admin/ui.generated.js.map +1 -1
- package/dist/apps/data/App.d.ts +4574 -4277
- package/dist/apps/data/App.d.ts.map +1 -1
- package/dist/apps/data/App.js +155 -77
- package/dist/apps/data/App.js.map +1 -1
- package/dist/apps/data/FundingQuoteProviders.d.ts +109 -0
- package/dist/apps/data/FundingQuoteProviders.d.ts.map +1 -0
- package/dist/apps/data/FundingQuoteProviders.js +464 -0
- package/dist/apps/data/FundingQuoteProviders.js.map +1 -0
- package/dist/apps/data/index.d.ts +1 -1
- package/dist/apps/data/index.js +1 -1
- package/dist/apps/data/routes/activities.d.ts +26 -26
- package/dist/apps/data/routes/addresses.d.ts +38 -38
- package/dist/apps/data/routes/balances.d.ts +45 -45
- package/dist/apps/data/routes/balances.d.ts.map +1 -1
- package/dist/apps/data/routes/balances.js +67 -149
- package/dist/apps/data/routes/balances.js.map +1 -1
- package/dist/apps/data/routes/blocks.d.ts +2 -3
- package/dist/apps/data/routes/blocks.d.ts.map +1 -1
- package/dist/apps/data/routes/blocks.js +3 -5
- package/dist/apps/data/routes/blocks.js.map +1 -1
- package/dist/apps/data/routes/coingecko.d.ts +177 -176
- package/dist/apps/data/routes/coingecko.d.ts.map +1 -1
- package/dist/apps/data/routes/coingecko.js +3 -8
- package/dist/apps/data/routes/coingecko.js.map +1 -1
- package/dist/apps/data/routes/funding.d.ts +131 -228
- package/dist/apps/data/routes/funding.d.ts.map +1 -1
- package/dist/apps/data/routes/funding.js +152 -219
- package/dist/apps/data/routes/funding.js.map +1 -1
- package/dist/apps/data/routes/indexer.d.ts +34 -34
- package/dist/apps/data/routes/indexer.d.ts.map +1 -1
- package/dist/apps/data/routes/indexer.js +9 -2
- package/dist/apps/data/routes/indexer.js.map +1 -1
- package/dist/apps/data/routes/receipts.d.ts +228 -228
- package/dist/apps/data/routes/rpc.d.ts.map +1 -1
- package/dist/apps/data/routes/rpc.js +86 -16
- package/dist/apps/data/routes/rpc.js.map +1 -1
- package/dist/apps/data/routes/tokenlist.d.ts +12 -12
- package/dist/apps/data/routes/verified-tokens.d.ts +5 -5
- package/dist/apps/data/routes/webhooks.d.ts +340 -0
- package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
- package/dist/apps/data/routes/webhooks.js +13 -1
- package/dist/apps/data/routes/webhooks.js.map +1 -1
- package/dist/apps/index.d.ts +1 -1
- package/dist/apps/index.js +1 -1
- package/dist/apps/management/App.d.ts +1833 -851
- package/dist/apps/management/App.d.ts.map +1 -1
- package/dist/apps/management/App.js +4 -0
- package/dist/apps/management/App.js.map +1 -1
- package/dist/apps/management/routes/api-keys.d.ts +41 -41
- package/dist/apps/management/routes/api-keys.js +2 -2
- package/dist/apps/management/routes/api-keys.js.map +1 -1
- package/dist/apps/management/routes/billing.d.ts +48 -48
- package/dist/apps/management/routes/invitations.d.ts +132 -132
- package/dist/apps/management/routes/invite-links.d.ts +1100 -0
- package/dist/apps/management/routes/invite-links.d.ts.map +1 -0
- package/dist/apps/management/routes/invite-links.js +411 -0
- package/dist/apps/management/routes/invite-links.js.map +1 -0
- package/dist/apps/management/routes/members.d.ts +41 -41
- package/dist/apps/management/routes/orgs.d.ts +92 -92
- package/dist/apps/management/routes/projects.d.ts +9 -9
- package/dist/apps/management/routes/scopes.d.ts.map +1 -1
- package/dist/apps/management/routes/scopes.js +2 -2
- package/dist/apps/management/routes/scopes.js.map +1 -1
- package/dist/apps/management/routes/usage.d.ts +24 -24
- package/dist/apps/mcp.js +2 -2
- package/dist/apps/mcp.js.map +1 -1
- package/dist/apps/relay/App.d.ts +2 -2
- package/dist/apps/relay/App.d.ts.map +1 -1
- package/dist/apps/relay/App.js +44 -6
- package/dist/apps/relay/App.js.map +1 -1
- package/dist/db/Db.d.ts +3 -0
- package/dist/db/Db.d.ts.map +1 -1
- package/dist/db/Db.js +57 -0
- package/dist/db/Db.js.map +1 -1
- package/dist/db/Schema.d.ts +31 -0
- package/dist/db/Schema.d.ts.map +1 -1
- package/dist/db/Schema.js +31 -0
- package/dist/db/Schema.js.map +1 -1
- package/dist/db/tables/inviteLinks.d.ts +42 -0
- package/dist/db/tables/inviteLinks.d.ts.map +1 -0
- package/dist/db/tables/inviteLinks.js +148 -0
- package/dist/db/tables/inviteLinks.js.map +1 -0
- package/dist/db/tables/organizations.d.ts.map +1 -1
- package/dist/db/tables/organizations.js +2 -0
- package/dist/db/tables/organizations.js.map +1 -1
- package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -1
- package/dist/db/tables/sponsoredTransactions.js +6 -2
- package/dist/db/tables/sponsoredTransactions.js.map +1 -1
- package/dist/handlers/relay.d.ts.map +1 -1
- package/dist/handlers/relay.js +13 -3
- package/dist/handlers/relay.js.map +1 -1
- package/dist/internal/Auth.d.ts +9 -1
- package/dist/internal/Auth.d.ts.map +1 -1
- package/dist/internal/Auth.js +138 -10
- package/dist/internal/Auth.js.map +1 -1
- package/dist/internal/EdgeCache.d.ts +3 -1
- package/dist/internal/EdgeCache.d.ts.map +1 -1
- package/dist/internal/EdgeCache.js +17 -2
- package/dist/internal/EdgeCache.js.map +1 -1
- package/dist/internal/{FundingRoutes.d.ts → FundingQuotes.d.ts} +240 -369
- package/dist/internal/FundingQuotes.d.ts.map +1 -0
- package/dist/internal/{FundingRoutes.js → FundingQuotes.js} +144 -183
- package/dist/internal/FundingQuotes.js.map +1 -0
- package/dist/internal/Log.d.ts +18 -12
- package/dist/internal/Log.d.ts.map +1 -1
- package/dist/internal/Log.js +2 -0
- package/dist/internal/Log.js.map +1 -1
- package/dist/internal/Tidx.d.ts +9 -3
- package/dist/internal/Tidx.d.ts.map +1 -1
- package/dist/internal/Tidx.js +13 -7
- package/dist/internal/Tidx.js.map +1 -1
- package/dist/internal/Viem.d.ts +17 -2
- package/dist/internal/Viem.d.ts.map +1 -1
- package/dist/internal/Viem.js +30 -3
- package/dist/internal/Viem.js.map +1 -1
- package/dist/internal/index.d.ts +1 -1
- package/dist/internal/index.js +1 -1
- package/package.json +2 -2
- package/src/ApiKeys.test.ts +25 -0
- package/src/App.test-d.ts +2 -0
- package/src/App.test.ts +13 -2
- package/src/App.ts +37 -5
- package/src/Assets.ts +3 -3
- package/src/Client.test-d.ts +21 -11
- package/src/Scope.test.ts +1 -0
- package/src/Scope.ts +32 -4
- package/src/Webhooks.ts +5 -10
- package/src/admin/apps/api-keys.test.ts +27 -0
- package/src/admin/apps/api-keys.ts +8 -1
- package/src/admin/apps/scopes.test.ts +7 -2
- package/src/admin/apps/scopes.ts +2 -2
- package/src/admin/ui/src/components/Dialog.tsx +8 -2
- package/src/admin/ui/src/lib/scopes.test.ts +19 -0
- package/src/admin/ui/src/lib/scopes.ts +12 -0
- package/src/admin/ui/src/routes/api-keys.tsx +128 -27
- package/src/admin/ui.generated.ts +33 -33
- package/src/apps/composition.test.ts +77 -0
- package/src/apps/data/App.test.ts +354 -0
- package/src/apps/data/App.ts +178 -91
- package/src/apps/data/FundingQuoteProviders.test.ts +577 -0
- package/src/apps/data/FundingQuoteProviders.ts +633 -0
- package/src/apps/data/index.ts +1 -1
- package/src/apps/data/routes/balances.test.ts +64 -18
- package/src/apps/data/routes/balances.ts +76 -177
- package/src/apps/data/routes/blocks.ts +3 -5
- package/src/apps/data/routes/coingecko.ts +3 -8
- package/src/apps/data/routes/funding.test.ts +304 -192
- package/src/apps/data/routes/funding.ts +166 -286
- package/src/apps/data/routes/indexer.test.ts +173 -15
- package/src/apps/data/routes/indexer.ts +8 -2
- package/src/apps/data/routes/rpc.test.ts +219 -0
- package/src/apps/data/routes/rpc.ts +92 -17
- package/src/apps/data/routes/webhooks.test.ts +21 -0
- package/src/apps/data/routes/webhooks.ts +13 -1
- package/src/apps/index.ts +1 -1
- package/src/apps/management/App.ts +4 -0
- package/src/apps/management/Billing.test.ts +2 -2
- package/src/apps/management/routes/api-keys.test.ts +47 -3
- package/src/apps/management/routes/api-keys.ts +2 -2
- package/src/apps/management/routes/invite-links.test.ts +385 -0
- package/src/apps/management/routes/invite-links.ts +570 -0
- package/src/apps/management/routes/scopes.test.ts +3 -2
- package/src/apps/management/routes/scopes.ts +4 -2
- package/src/apps/mcp.test.ts +18 -0
- package/src/apps/mcp.ts +2 -2
- package/src/apps/relay/App.test.ts +328 -7
- package/src/apps/relay/App.ts +42 -6
- package/src/apps/relay/Sponsorships.test.ts +2 -2
- package/src/db/Db.test.ts +5 -5
- package/src/db/Db.ts +65 -0
- package/src/db/Schema.ts +39 -0
- package/src/db/tables/billingSettings.test.ts +2 -2
- package/src/db/tables/inviteLinks.ts +187 -0
- package/src/db/tables/organizations.test.ts +2 -2
- package/src/db/tables/organizations.ts +2 -0
- package/src/db/tables/sponsoredTransactions.test.ts +19 -2
- package/src/db/tables/sponsoredTransactions.ts +21 -14
- package/src/db/tables/stripeCustomers.test.ts +2 -2
- package/src/handlers/relay.ts +16 -4
- package/src/internal/Auth.test.ts +132 -22
- package/src/internal/Auth.ts +181 -14
- package/src/internal/EdgeCache.test.ts +24 -3
- package/src/internal/EdgeCache.ts +18 -2
- package/src/internal/FundingQuotes.test.ts +268 -0
- package/src/internal/{FundingRoutes.ts → FundingQuotes.ts} +216 -277
- package/src/internal/Log.ts +21 -14
- package/src/internal/Tidx.test.ts +48 -0
- package/src/internal/Tidx.ts +21 -7
- package/src/internal/Viem.test.ts +46 -0
- package/src/internal/Viem.ts +48 -6
- package/src/internal/index.test.ts +1 -1
- package/src/internal/index.ts +1 -1
- package/dist/apps/data/FundingRouteProviders.d.ts +0 -36
- package/dist/apps/data/FundingRouteProviders.d.ts.map +0 -1
- package/dist/apps/data/FundingRouteProviders.js +0 -245
- package/dist/apps/data/FundingRouteProviders.js.map +0 -1
- package/dist/internal/FundingRoutes.d.ts.map +0 -1
- package/dist/internal/FundingRoutes.js.map +0 -1
- package/src/apps/data/FundingRouteProviders.test.ts +0 -225
- package/src/apps/data/FundingRouteProviders.ts +0 -312
- package/src/internal/FundingRoutes.test.ts +0 -213
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Hono, type Context, type TypedResponse } from 'hono'
|
|
2
|
+
import { ZoneRpcAuthentication } from 'ox/tempo'
|
|
2
3
|
import * as z from 'zod/mini'
|
|
3
4
|
|
|
4
5
|
import type * as App from '../../../App.js'
|
|
@@ -19,10 +20,52 @@ const strippedHeaders = new Set([
|
|
|
19
20
|
'keep-alive',
|
|
20
21
|
'proxy-authorization',
|
|
21
22
|
'te',
|
|
23
|
+
'tempo-api-key',
|
|
22
24
|
'trailer',
|
|
23
25
|
'transfer-encoding',
|
|
24
26
|
'upgrade',
|
|
25
|
-
|
|
27
|
+
ZoneRpcAuthentication.headerName.toLowerCase(),
|
|
28
|
+
])
|
|
29
|
+
|
|
30
|
+
// API-issued Zone read scopes never reach stateful or privileged RPC modules.
|
|
31
|
+
const zoneReadMethods = new Set([
|
|
32
|
+
'eth_blobBaseFee',
|
|
33
|
+
'eth_blockNumber',
|
|
34
|
+
'eth_call',
|
|
35
|
+
'eth_chainId',
|
|
36
|
+
'eth_createAccessList',
|
|
37
|
+
'eth_estimateGas',
|
|
38
|
+
'eth_feeHistory',
|
|
39
|
+
'eth_fillTransaction',
|
|
40
|
+
'eth_gasPrice',
|
|
41
|
+
'eth_getBalance',
|
|
42
|
+
'eth_getBlockByHash',
|
|
43
|
+
'eth_getBlockByNumber',
|
|
44
|
+
'eth_getBlockReceipts',
|
|
45
|
+
'eth_getBlockTransactionCountByHash',
|
|
46
|
+
'eth_getBlockTransactionCountByNumber',
|
|
47
|
+
'eth_getCode',
|
|
48
|
+
'eth_getLogs',
|
|
49
|
+
'eth_getProof',
|
|
50
|
+
'eth_getRawTransactionByHash',
|
|
51
|
+
'eth_getStorageAt',
|
|
52
|
+
'eth_getTransactionByBlockHashAndIndex',
|
|
53
|
+
'eth_getTransactionByBlockNumberAndIndex',
|
|
54
|
+
'eth_getTransactionByHash',
|
|
55
|
+
'eth_getTransactionCount',
|
|
56
|
+
'eth_getTransactionReceipt',
|
|
57
|
+
'eth_getUncleByBlockHashAndIndex',
|
|
58
|
+
'eth_getUncleByBlockNumberAndIndex',
|
|
59
|
+
'eth_getUncleCountByBlockHash',
|
|
60
|
+
'eth_getUncleCountByBlockNumber',
|
|
61
|
+
'eth_maxPriorityFeePerGas',
|
|
62
|
+
'eth_simulateV1',
|
|
63
|
+
'eth_syncing',
|
|
64
|
+
'net_listening',
|
|
65
|
+
'net_peerCount',
|
|
66
|
+
'net_version',
|
|
67
|
+
'web3_clientVersion',
|
|
68
|
+
'web3_sha3',
|
|
26
69
|
])
|
|
27
70
|
|
|
28
71
|
/** Never reached; carries typed passthrough response variants into Hono client inference. */
|
|
@@ -105,7 +148,7 @@ export function rpc() {
|
|
|
105
148
|
Auth.policy({ apiKey: { scopes: ['data:read'] }, public: true }),
|
|
106
149
|
OpenApi.describeRoute({
|
|
107
150
|
description:
|
|
108
|
-
'Sends a single or batch Ethereum JSON-RPC request directly to Tempo. Use `/rpc/testnet`, `/rpc/mainnet`, or `/rpc/:chain` to choose a chain. This passthrough is not part of the stable API contract and is supported on a best-effort basis only. It has no compatibility, latency, availability, or data-freshness guarantees, and breaking changes may happen with limited notice.',
|
|
151
|
+
'Sends a single or batch Ethereum JSON-RPC request directly to Tempo. Use `/rpc/testnet`, `/rpc/mainnet`, or `/rpc/:chain` to choose a chain. Zone-scoped API keys may call read-only methods. A Zone token in `X-Authorization-Token` is forwarded to the authenticated Zone RPC. This passthrough is not part of the stable API contract and is supported on a best-effort basis only. It has no compatibility, latency, availability, or data-freshness guarantees, and breaking changes may happen with limited notice.',
|
|
109
152
|
operationId: 'rpcRequest',
|
|
110
153
|
parameters: [
|
|
111
154
|
{
|
|
@@ -146,19 +189,6 @@ export function rpc() {
|
|
|
146
189
|
async (c) => {
|
|
147
190
|
if (Auth.narrowAccess) return Auth.accessError(c)
|
|
148
191
|
|
|
149
|
-
if (requiresApiKey(c)) {
|
|
150
|
-
const body = await c.req.raw
|
|
151
|
-
.clone()
|
|
152
|
-
.json()
|
|
153
|
-
.catch(() => undefined)
|
|
154
|
-
if (!isPublicRequest(body))
|
|
155
|
-
return Response.error(c, {
|
|
156
|
-
code: 'api_key_missing',
|
|
157
|
-
message: 'Missing API key',
|
|
158
|
-
status: 401,
|
|
159
|
-
})
|
|
160
|
-
}
|
|
161
|
-
|
|
162
192
|
const chainId = resolveChainId(c)
|
|
163
193
|
if (!chainId)
|
|
164
194
|
return Response.error(c, {
|
|
@@ -167,6 +197,31 @@ export function rpc() {
|
|
|
167
197
|
status: 400,
|
|
168
198
|
})
|
|
169
199
|
|
|
200
|
+
const zone = c.get('zones').get(chainId)
|
|
201
|
+
const zoneToken = getZoneToken(c)
|
|
202
|
+
const apiKeyRequired = requiresApiKey(c)
|
|
203
|
+
const inspectBody = apiKeyRequired || Boolean(zone && !zoneToken)
|
|
204
|
+
const body = inspectBody
|
|
205
|
+
? await c.req.raw
|
|
206
|
+
.clone()
|
|
207
|
+
.json()
|
|
208
|
+
.catch(() => undefined)
|
|
209
|
+
: undefined
|
|
210
|
+
|
|
211
|
+
if (apiKeyRequired && !isPublicRequest(body))
|
|
212
|
+
return Response.error(c, {
|
|
213
|
+
code: 'api_key_missing',
|
|
214
|
+
message: 'Missing API key',
|
|
215
|
+
status: 401,
|
|
216
|
+
})
|
|
217
|
+
if (zone && !zoneToken && !isZoneReadRequest(body))
|
|
218
|
+
return Response.error(c, {
|
|
219
|
+
code: 'api_key_forbidden',
|
|
220
|
+
message:
|
|
221
|
+
'Zone API keys may only call read-only RPC methods. Pass a Zone token via `X-Authorization-Token` for other methods.',
|
|
222
|
+
status: 403,
|
|
223
|
+
})
|
|
224
|
+
|
|
170
225
|
if (narrowPassthrough)
|
|
171
226
|
return c.json(Response.validated(schema.ResponseBody, { jsonrpc: '2.0' }), 200)
|
|
172
227
|
return passthrough(c, chainId)
|
|
@@ -180,6 +235,11 @@ function requiresApiKey(c: Context<App.Environment>) {
|
|
|
180
235
|
return principal?.type !== 'api_key' && principal?.type !== 'super_admin'
|
|
181
236
|
}
|
|
182
237
|
|
|
238
|
+
function getZoneToken(c: Context<App.Environment>) {
|
|
239
|
+
const value = c.req.header(ZoneRpcAuthentication.headerName)
|
|
240
|
+
return value?.trim() ? value : undefined
|
|
241
|
+
}
|
|
242
|
+
|
|
183
243
|
function isPublicRequest(body: unknown): boolean {
|
|
184
244
|
const requests = Array.isArray(body) ? body : [body]
|
|
185
245
|
return requests.length > 0 && requests.every((request) => isPublicMethod(request))
|
|
@@ -191,13 +251,27 @@ function isPublicMethod(request: unknown): boolean {
|
|
|
191
251
|
return typeof method === 'string' && method.startsWith('eth_')
|
|
192
252
|
}
|
|
193
253
|
|
|
254
|
+
function isZoneReadRequest(body: unknown): boolean {
|
|
255
|
+
const requests = Array.isArray(body) ? body : [body]
|
|
256
|
+
return requests.length > 0 && requests.every((request) => isZoneReadMethod(request))
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function isZoneReadMethod(request: unknown): boolean {
|
|
260
|
+
if (!request || typeof request !== 'object') return false
|
|
261
|
+
const method = (request as Record<string, unknown>)['method']
|
|
262
|
+
return typeof method === 'string' && zoneReadMethods.has(method)
|
|
263
|
+
}
|
|
264
|
+
|
|
194
265
|
async function passthrough(
|
|
195
266
|
c: Context<App.Environment>,
|
|
196
267
|
chainId: z.output<typeof Schema.ChainId>,
|
|
197
268
|
): Promise<globalThis.Response & TypedResponse<never, never, never>> {
|
|
198
|
-
const
|
|
269
|
+
const zone = c.get('zones').get(chainId)
|
|
270
|
+
const zoneToken = getZoneToken(c)
|
|
271
|
+
const rpc = zone && zoneToken ? { url: () => zone.forwardRpcUrl } : c.get('rpc')
|
|
199
272
|
const principal = Auth.getPrincipal(c)
|
|
200
|
-
const upstream_rpc =
|
|
273
|
+
const upstream_rpc =
|
|
274
|
+
!zone && principal?.type === 'public' && typeof rpc !== 'function' ? undefined : rpc
|
|
201
275
|
// Anonymous callers use the built-in public RPC unless a resolver chooses a
|
|
202
276
|
// different public upstream.
|
|
203
277
|
const { basicAuth, url } = Viem.resolve(upstream_rpc, { chainId, principal })
|
|
@@ -215,6 +289,7 @@ async function passthrough(
|
|
|
215
289
|
for (const [name, value] of c.req.raw.headers)
|
|
216
290
|
if (!strippedHeaders.has(name.toLowerCase())) headers.set(name, value)
|
|
217
291
|
if (basicAuth) headers.set('authorization', `Basic ${btoa(basicAuth)}`)
|
|
292
|
+
if (zone && zoneToken) headers.set(ZoneRpcAuthentication.headerName, zoneToken)
|
|
218
293
|
|
|
219
294
|
// `duplex` is required when streaming a request body, but is not yet in
|
|
220
295
|
// the DOM `RequestInit` type.
|
|
@@ -112,6 +112,27 @@ describe('access matrix', () => {
|
|
|
112
112
|
expect(body.error?.code).toMatchInlineSnapshot(`"api_key_forbidden"`)
|
|
113
113
|
})
|
|
114
114
|
|
|
115
|
+
test('rejects Zone webhook subscriptions', async () => {
|
|
116
|
+
const zone = 421_700_001
|
|
117
|
+
const app = TestApp.client({
|
|
118
|
+
auth: { keys: [writer] },
|
|
119
|
+
zones: [
|
|
120
|
+
{
|
|
121
|
+
chainId: zone,
|
|
122
|
+
forwardRpcUrl: 'https://zone.rpc.test',
|
|
123
|
+
},
|
|
124
|
+
],
|
|
125
|
+
})
|
|
126
|
+
const response = await app.v1.webhooks.$post(
|
|
127
|
+
{ json: { ...validBody, chainId: zone } },
|
|
128
|
+
as(writer),
|
|
129
|
+
)
|
|
130
|
+
const body = (await response.json()) as { error?: { code?: string } }
|
|
131
|
+
|
|
132
|
+
expect(response.status).toBe(400)
|
|
133
|
+
expect(body.error?.code).toBe('chain_id_unsupported')
|
|
134
|
+
})
|
|
135
|
+
|
|
115
136
|
test('lists event types without authentication (public catalog)', async () => {
|
|
116
137
|
// `GET /webhooks/event-types` is intentionally public: anonymous callers are
|
|
117
138
|
// served within the public quota, no key or scope required.
|
|
@@ -1294,7 +1294,13 @@ export function webhooks(options: webhooks.Options = {}) {
|
|
|
1294
1294
|
hide: hidden,
|
|
1295
1295
|
operationId: 'createWebhook',
|
|
1296
1296
|
responses: OpenApi.responses({
|
|
1297
|
-
errors: {
|
|
1297
|
+
errors: {
|
|
1298
|
+
400: {
|
|
1299
|
+
codes: ['chain_id_unsupported'],
|
|
1300
|
+
description: 'Webhook subscriptions do not support Zone chains.',
|
|
1301
|
+
},
|
|
1302
|
+
403: 'You have reached the webhook subscription limit.',
|
|
1303
|
+
},
|
|
1298
1304
|
success: {
|
|
1299
1305
|
description:
|
|
1300
1306
|
'The created webhook subscription, including the one-time signing secret.',
|
|
@@ -1320,6 +1326,12 @@ export function webhooks(options: webhooks.Options = {}) {
|
|
|
1320
1326
|
|
|
1321
1327
|
const body = c.req.valid('json')
|
|
1322
1328
|
const chainId = body.chainId ?? c.get('chainId')
|
|
1329
|
+
if (c.get('zones').has(chainId))
|
|
1330
|
+
return Response.error(c, {
|
|
1331
|
+
code: 'chain_id_unsupported',
|
|
1332
|
+
message: 'Webhook subscriptions do not support Zone chains.',
|
|
1333
|
+
status: 400,
|
|
1334
|
+
})
|
|
1323
1335
|
|
|
1324
1336
|
try {
|
|
1325
1337
|
const subscription = await Webhooks.createSubscription(
|
package/src/apps/index.ts
CHANGED
|
@@ -7,6 +7,7 @@ import * as Metadata from '../metadata.js'
|
|
|
7
7
|
import { apiKeys } from './routes/api-keys.js'
|
|
8
8
|
import { billing } from './routes/billing.js'
|
|
9
9
|
import { invitations } from './routes/invitations.js'
|
|
10
|
+
import { inviteLinks } from './routes/invite-links.js'
|
|
10
11
|
import { me } from './routes/me.js'
|
|
11
12
|
import { members } from './routes/members.js'
|
|
12
13
|
import { orgs } from './routes/orgs.js'
|
|
@@ -105,6 +106,7 @@ export function management(options: management.Options = {}) {
|
|
|
105
106
|
.route('/', apiKeys({ enabled: options.apiKeys !== false }))
|
|
106
107
|
.route('/', billing())
|
|
107
108
|
.route('/', invitations())
|
|
109
|
+
.route('/', inviteLinks())
|
|
108
110
|
.route('/', me())
|
|
109
111
|
.route('/', members())
|
|
110
112
|
.route('/', orgs())
|
|
@@ -144,6 +146,7 @@ const openapi = {
|
|
|
144
146
|
{ name: 'API Keys', description: 'Credentials for accessing the Tempo API.' },
|
|
145
147
|
{ name: 'Billing', description: 'Organization billing backed by Stripe.' },
|
|
146
148
|
{ name: 'Invitations', description: 'Pending organization invitations.' },
|
|
149
|
+
{ name: 'Invite Links', description: 'Reusable organization invite links.' },
|
|
147
150
|
{ name: 'Members', description: 'Organization team membership.' },
|
|
148
151
|
{ name: 'Organizations', description: 'Teams that own application workspaces and members.' },
|
|
149
152
|
{ name: 'Projects', description: 'Application workspaces within an organization.' },
|
|
@@ -158,6 +161,7 @@ const openapi = {
|
|
|
158
161
|
'Authentication',
|
|
159
162
|
'Billing',
|
|
160
163
|
'Invitations',
|
|
164
|
+
'Invite Links',
|
|
161
165
|
'Members',
|
|
162
166
|
'Organizations',
|
|
163
167
|
'Projects',
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { nanoid } from 'nanoid'
|
|
2
2
|
import type Stripe from 'stripe'
|
|
3
3
|
|
|
4
|
-
import
|
|
4
|
+
import * as Runtime from '../../../test/runtime.js'
|
|
5
5
|
import * as ApiKeys from '../../ApiKeys.js'
|
|
6
6
|
import * as Db from '../../db/Db.js'
|
|
7
7
|
import * as RequestUsage from '../../db/tables/requestUsage.js'
|
|
@@ -11,7 +11,7 @@ import * as Store from '../../internal/Store.js'
|
|
|
11
11
|
import * as Viem from '../../internal/Viem.js'
|
|
12
12
|
import * as Billing from './Billing.js'
|
|
13
13
|
|
|
14
|
-
const create = () => Db.postgres({ connectionString: postgresUrl, schema: `t_${nanoid()}` })
|
|
14
|
+
const create = () => Db.postgres({ connectionString: Runtime.postgresUrl, schema: `t_${nanoid()}` })
|
|
15
15
|
|
|
16
16
|
/** Baseline billable mainnet sponsorship input; tests override per case. */
|
|
17
17
|
const input = {
|
|
@@ -111,7 +111,20 @@ describe('keys', () => {
|
|
|
111
111
|
expect(write.status).toBe(200)
|
|
112
112
|
})
|
|
113
113
|
|
|
114
|
-
test('
|
|
114
|
+
test('zone scopes are never self-serve', async () => {
|
|
115
|
+
const app = createApp()
|
|
116
|
+
const owner = await session(app)
|
|
117
|
+
const { org, project } = await fixture(app, owner)
|
|
118
|
+
const base = `/v1/orgs/${org.id}/projects/${project.id}/api-keys`
|
|
119
|
+
|
|
120
|
+
// Even the org owner cannot self-mint zone access: membership is
|
|
121
|
+
// controlled zone-side, so zone scopes are super-admin-issued.
|
|
122
|
+
const denied = await app.request(base, json('POST', owner, { scopes: ['zone:421700001:read'] }))
|
|
123
|
+
expect(denied.status).toBe(403)
|
|
124
|
+
expect(await denied.json()).toMatchObject({ error: { code: 'scope_not_issuable' } })
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
test('restricts project management keys without delegating issuance', async () => {
|
|
115
128
|
const app = createApp()
|
|
116
129
|
const cookie = await session(app)
|
|
117
130
|
const { org, project } = await fixture(app, cookie)
|
|
@@ -135,10 +148,28 @@ describe('keys', () => {
|
|
|
135
148
|
Keys.schema.createApiKey.Response,
|
|
136
149
|
)
|
|
137
150
|
|
|
151
|
+
const own = await app.request(base, {
|
|
152
|
+
headers: { 'tempo-api-key': manager.token },
|
|
153
|
+
})
|
|
154
|
+
expect(own.status).toMatchInlineSnapshot(`200`)
|
|
155
|
+
|
|
138
156
|
const listed = await app.request(siblingBase, {
|
|
139
157
|
headers: { 'tempo-api-key': manager.token },
|
|
140
158
|
})
|
|
141
|
-
|
|
159
|
+
const { requestId: listedRequestId, ...listedBody } = (await listed.json()) as {
|
|
160
|
+
requestId?: string
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
expect(listed.status).toMatchInlineSnapshot(`404`)
|
|
164
|
+
expect(listedRequestId).toBeDefined()
|
|
165
|
+
expect(listedBody).toMatchInlineSnapshot(`
|
|
166
|
+
{
|
|
167
|
+
"error": {
|
|
168
|
+
"code": "project_not_found",
|
|
169
|
+
"message": "Project not found",
|
|
170
|
+
},
|
|
171
|
+
}
|
|
172
|
+
`)
|
|
142
173
|
|
|
143
174
|
const delegated = await app.request(base, {
|
|
144
175
|
body: JSON.stringify({ scopes: ['management:read'] }),
|
|
@@ -151,7 +182,20 @@ describe('keys', () => {
|
|
|
151
182
|
headers: { 'tempo-api-key': manager.token },
|
|
152
183
|
method: 'DELETE',
|
|
153
184
|
})
|
|
154
|
-
|
|
185
|
+
const { requestId: revokedRequestId, ...revokedBody } = (await revoked.json()) as {
|
|
186
|
+
requestId?: string
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
expect(revoked.status).toMatchInlineSnapshot(`404`)
|
|
190
|
+
expect(revokedRequestId).toBeDefined()
|
|
191
|
+
expect(revokedBody).toMatchInlineSnapshot(`
|
|
192
|
+
{
|
|
193
|
+
"error": {
|
|
194
|
+
"code": "project_not_found",
|
|
195
|
+
"message": "Project not found",
|
|
196
|
+
},
|
|
197
|
+
}
|
|
198
|
+
`)
|
|
155
199
|
})
|
|
156
200
|
|
|
157
201
|
test('mints, lists, authenticates, and revokes a project key', async () => {
|
|
@@ -60,7 +60,7 @@ export namespace schema {
|
|
|
60
60
|
.array(z.string().check(z.minLength(1)))
|
|
61
61
|
.check(
|
|
62
62
|
z.describe(
|
|
63
|
-
"Granted scopes. '*'
|
|
63
|
+
"Granted scopes. '*' and non-self-serve scopes appear only on keys minted by the super admin.",
|
|
64
64
|
),
|
|
65
65
|
z.meta({ examples: [['data:read']] }),
|
|
66
66
|
),
|
|
@@ -218,7 +218,7 @@ export namespace schema {
|
|
|
218
218
|
}
|
|
219
219
|
|
|
220
220
|
/**
|
|
221
|
-
* Mounts project-attributed API key mint, list, and revoke routes plus the org-wide list.
|
|
221
|
+
* Mounts project-attributed API key mint, list, and revoke routes plus the org-wide list. Project routes enforce caller organization and project attribution.
|
|
222
222
|
*/
|
|
223
223
|
export function apiKeys(options: apiKeys.Options = {}) {
|
|
224
224
|
const hidden = !options.enabled
|