tapimo 0.0.0 → 0.0.2
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/LICENSE +21 -0
- package/README.md +119 -0
- package/dist/ApiKey.d.ts +65 -0
- package/dist/ApiKey.d.ts.map +1 -0
- package/dist/ApiKey.js +105 -0
- package/dist/ApiKey.js.map +1 -0
- package/dist/ApiKeySource.d.ts +166 -0
- package/dist/ApiKeySource.d.ts.map +1 -0
- package/dist/ApiKeySource.js +234 -0
- package/dist/ApiKeySource.js.map +1 -0
- package/dist/App.d.ts +19317 -0
- package/dist/App.d.ts.map +1 -0
- package/dist/App.js +612 -0
- package/dist/App.js.map +1 -0
- package/dist/Assets.d.ts +44 -0
- package/dist/Assets.d.ts.map +1 -0
- package/dist/Assets.js +23 -0
- package/dist/Assets.js.map +1 -0
- package/dist/Auth.d.ts +2 -0
- package/dist/Auth.d.ts.map +1 -0
- package/dist/Auth.js +2 -0
- package/dist/Auth.js.map +1 -0
- package/dist/Cli.d.ts +21 -0
- package/dist/Cli.d.ts.map +1 -0
- package/dist/Cli.js +13 -0
- package/dist/Cli.js.map +1 -0
- package/dist/Client.d.ts +20 -0
- package/dist/Client.d.ts.map +1 -0
- package/dist/Client.js +24 -0
- package/dist/Client.js.map +1 -0
- package/dist/Metrics.d.ts +70 -0
- package/dist/Metrics.d.ts.map +1 -0
- package/dist/Metrics.js +91 -0
- package/dist/Metrics.js.map +1 -0
- package/dist/Scope.d.ts +35 -0
- package/dist/Scope.d.ts.map +1 -0
- package/dist/Scope.js +38 -0
- package/dist/Scope.js.map +1 -0
- package/dist/Webhooks.d.ts +79 -0
- package/dist/Webhooks.d.ts.map +1 -0
- package/dist/Webhooks.js +107 -0
- package/dist/Webhooks.js.map +1 -0
- package/dist/admin/Access.d.ts +41 -0
- package/dist/admin/Access.d.ts.map +1 -0
- package/dist/admin/Access.js +75 -0
- package/dist/admin/Access.js.map +1 -0
- package/dist/admin/App.d.ts +1221 -0
- package/dist/admin/App.d.ts.map +1 -0
- package/dist/admin/App.js +113 -0
- package/dist/admin/App.js.map +1 -0
- package/dist/admin/apps/api-keys.d.ts +209 -0
- package/dist/admin/apps/api-keys.d.ts.map +1 -0
- package/dist/admin/apps/api-keys.js +129 -0
- package/dist/admin/apps/api-keys.js.map +1 -0
- package/dist/admin/apps/scopes.d.ts +46 -0
- package/dist/admin/apps/scopes.d.ts.map +1 -0
- package/dist/admin/apps/scopes.js +41 -0
- package/dist/admin/apps/scopes.js.map +1 -0
- package/dist/admin/apps/verified-tokens.d.ts +1206 -0
- package/dist/admin/apps/verified-tokens.d.ts.map +1 -0
- package/dist/admin/apps/verified-tokens.js +485 -0
- package/dist/admin/apps/verified-tokens.js.map +1 -0
- package/dist/admin/index.d.ts +3 -0
- package/dist/admin/index.d.ts.map +1 -0
- package/dist/admin/index.js +3 -0
- package/dist/admin/index.js.map +1 -0
- package/dist/admin/ui.generated.d.ts +11 -0
- package/dist/admin/ui.generated.d.ts.map +1 -0
- package/dist/admin/ui.generated.js +43 -0
- package/dist/admin/ui.generated.js.map +1 -0
- package/dist/apps/activities.d.ts +18725 -0
- package/dist/apps/activities.d.ts.map +1 -0
- package/dist/apps/activities.js +3138 -0
- package/dist/apps/activities.js.map +1 -0
- package/dist/apps/addresses.d.ts +3662 -0
- package/dist/apps/addresses.d.ts.map +1 -0
- package/dist/apps/addresses.js +22 -0
- package/dist/apps/addresses.js.map +1 -0
- package/dist/apps/balances.d.ts +342 -0
- package/dist/apps/balances.d.ts.map +1 -0
- package/dist/apps/balances.js +393 -0
- package/dist/apps/balances.js.map +1 -0
- package/dist/apps/blocks.d.ts +931 -0
- package/dist/apps/blocks.d.ts.map +1 -0
- package/dist/apps/blocks.js +689 -0
- package/dist/apps/blocks.js.map +1 -0
- package/dist/apps/coingecko.d.ts +1053 -0
- package/dist/apps/coingecko.d.ts.map +1 -0
- package/dist/apps/coingecko.js +1015 -0
- package/dist/apps/coingecko.js.map +1 -0
- package/dist/apps/exchanges.d.ts +3000 -0
- package/dist/apps/exchanges.d.ts.map +1 -0
- package/dist/apps/exchanges.js +3570 -0
- package/dist/apps/exchanges.js.map +1 -0
- package/dist/apps/fee-amm.d.ts +640 -0
- package/dist/apps/fee-amm.d.ts.map +1 -0
- package/dist/apps/fee-amm.js +815 -0
- package/dist/apps/fee-amm.js.map +1 -0
- package/dist/apps/indexer.d.ts +116 -0
- package/dist/apps/indexer.d.ts.map +1 -0
- package/dist/apps/indexer.js +270 -0
- package/dist/apps/indexer.js.map +1 -0
- package/dist/apps/receipts.d.ts +1438 -0
- package/dist/apps/receipts.d.ts.map +1 -0
- package/dist/apps/receipts.js +874 -0
- package/dist/apps/receipts.js.map +1 -0
- package/dist/apps/rpc.d.ts +173 -0
- package/dist/apps/rpc.d.ts.map +1 -0
- package/dist/apps/rpc.js +191 -0
- package/dist/apps/rpc.js.map +1 -0
- package/dist/apps/tokenlist.d.ts +192 -0
- package/dist/apps/tokenlist.d.ts.map +1 -0
- package/dist/apps/tokenlist.js +153 -0
- package/dist/apps/tokenlist.js.map +1 -0
- package/dist/apps/tokens.d.ts +2246 -0
- package/dist/apps/tokens.d.ts.map +1 -0
- package/dist/apps/tokens.js +1789 -0
- package/dist/apps/tokens.js.map +1 -0
- package/dist/apps/transactions.d.ts +2327 -0
- package/dist/apps/transactions.d.ts.map +1 -0
- package/dist/apps/transactions.js +975 -0
- package/dist/apps/transactions.js.map +1 -0
- package/dist/apps/transfers.d.ts +589 -0
- package/dist/apps/transfers.d.ts.map +1 -0
- package/dist/apps/transfers.js +769 -0
- package/dist/apps/transfers.js.map +1 -0
- package/dist/apps/verified-tokens.d.ts +544 -0
- package/dist/apps/verified-tokens.d.ts.map +1 -0
- package/dist/apps/verified-tokens.js +234 -0
- package/dist/apps/verified-tokens.js.map +1 -0
- package/dist/apps/webhooks.d.ts +7684 -0
- package/dist/apps/webhooks.d.ts.map +1 -0
- package/dist/apps/webhooks.js +2112 -0
- package/dist/apps/webhooks.js.map +1 -0
- package/dist/cloudflare.d.ts +32 -0
- package/dist/cloudflare.d.ts.map +1 -0
- package/dist/cloudflare.js +45 -0
- package/dist/cloudflare.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/Auth.d.ts +298 -0
- package/dist/internal/Auth.d.ts.map +1 -0
- package/dist/internal/Auth.js +618 -0
- package/dist/internal/Auth.js.map +1 -0
- package/dist/internal/Cache.d.ts +74 -0
- package/dist/internal/Cache.d.ts.map +1 -0
- package/dist/internal/Cache.js +215 -0
- package/dist/internal/Cache.js.map +1 -0
- package/dist/internal/Cursor.d.ts +79 -0
- package/dist/internal/Cursor.d.ts.map +1 -0
- package/dist/internal/Cursor.js +116 -0
- package/dist/internal/Cursor.js.map +1 -0
- package/dist/internal/EdgeCache.d.ts +59 -0
- package/dist/internal/EdgeCache.d.ts.map +1 -0
- package/dist/internal/EdgeCache.js +174 -0
- package/dist/internal/EdgeCache.js.map +1 -0
- package/dist/internal/Log.d.ts +104 -0
- package/dist/internal/Log.d.ts.map +1 -0
- package/dist/internal/Log.js +118 -0
- package/dist/internal/Log.js.map +1 -0
- package/dist/internal/MetricSink.d.ts +65 -0
- package/dist/internal/MetricSink.d.ts.map +1 -0
- package/dist/internal/MetricSink.js +146 -0
- package/dist/internal/MetricSink.js.map +1 -0
- package/dist/internal/Mpp.d.ts +71 -0
- package/dist/internal/Mpp.d.ts.map +1 -0
- package/dist/internal/Mpp.js +105 -0
- package/dist/internal/Mpp.js.map +1 -0
- package/dist/internal/OpenApi.d.ts +313 -0
- package/dist/internal/OpenApi.d.ts.map +1 -0
- package/dist/internal/OpenApi.fonts.d.ts +2 -0
- package/dist/internal/OpenApi.fonts.d.ts.map +1 -0
- package/dist/internal/OpenApi.fonts.js +39 -0
- package/dist/internal/OpenApi.fonts.js.map +1 -0
- package/dist/internal/OpenApi.js +335 -0
- package/dist/internal/OpenApi.js.map +1 -0
- package/dist/internal/OpenApi.logo.d.ts +6 -0
- package/dist/internal/OpenApi.logo.d.ts.map +1 -0
- package/dist/internal/OpenApi.logo.js +19 -0
- package/dist/internal/OpenApi.logo.js.map +1 -0
- package/dist/internal/Path.d.ts +5 -0
- package/dist/internal/Path.d.ts.map +1 -0
- package/dist/internal/Path.js +16 -0
- package/dist/internal/Path.js.map +1 -0
- package/dist/internal/RateLimit.d.ts +60 -0
- package/dist/internal/RateLimit.d.ts.map +1 -0
- package/dist/internal/RateLimit.js +48 -0
- package/dist/internal/RateLimit.js.map +1 -0
- package/dist/internal/Response.d.ts +132 -0
- package/dist/internal/Response.d.ts.map +1 -0
- package/dist/internal/Response.js +72 -0
- package/dist/internal/Response.js.map +1 -0
- package/dist/internal/Schema.d.ts +257 -0
- package/dist/internal/Schema.d.ts.map +1 -0
- package/dist/internal/Schema.js +360 -0
- package/dist/internal/Schema.js.map +1 -0
- package/dist/internal/Store.d.ts +204 -0
- package/dist/internal/Store.d.ts.map +1 -0
- package/dist/internal/Store.js +291 -0
- package/dist/internal/Store.js.map +1 -0
- package/dist/internal/Tidx.d.ts +120 -0
- package/dist/internal/Tidx.d.ts.map +1 -0
- package/dist/internal/Tidx.js +156 -0
- package/dist/internal/Tidx.js.map +1 -0
- package/dist/internal/Timing.d.ts +22 -0
- package/dist/internal/Timing.d.ts.map +1 -0
- package/dist/internal/Timing.js +32 -0
- package/dist/internal/Timing.js.map +1 -0
- package/dist/internal/Ttl.d.ts +25 -0
- package/dist/internal/Ttl.d.ts.map +1 -0
- package/dist/internal/Ttl.js +49 -0
- package/dist/internal/Ttl.js.map +1 -0
- package/dist/internal/Value.d.ts +47 -0
- package/dist/internal/Value.d.ts.map +1 -0
- package/dist/internal/Value.js +122 -0
- package/dist/internal/Value.js.map +1 -0
- package/dist/internal/VerifiedTokens.d.ts +228 -0
- package/dist/internal/VerifiedTokens.d.ts.map +1 -0
- package/dist/internal/VerifiedTokens.js +450 -0
- package/dist/internal/VerifiedTokens.js.map +1 -0
- package/dist/internal/Viem.d.ts +2082 -0
- package/dist/internal/Viem.d.ts.map +1 -0
- package/dist/internal/Viem.js +63 -0
- package/dist/internal/Viem.js.map +1 -0
- package/dist/internal/WebhookDestination.d.ts +198 -0
- package/dist/internal/WebhookDestination.d.ts.map +1 -0
- package/dist/internal/WebhookDestination.js +170 -0
- package/dist/internal/WebhookDestination.js.map +1 -0
- package/dist/internal/Webhooks.d.ts +433 -0
- package/dist/internal/Webhooks.d.ts.map +1 -0
- package/dist/internal/Webhooks.js +489 -0
- package/dist/internal/Webhooks.js.map +1 -0
- package/dist/internal/webhookDestinations/betterStack.d.ts +26 -0
- package/dist/internal/webhookDestinations/betterStack.d.ts.map +1 -0
- package/dist/internal/webhookDestinations/betterStack.js +76 -0
- package/dist/internal/webhookDestinations/betterStack.js.map +1 -0
- package/dist/internal/webhookDestinations/slack.d.ts +59 -0
- package/dist/internal/webhookDestinations/slack.d.ts.map +1 -0
- package/dist/internal/webhookDestinations/slack.js +422 -0
- package/dist/internal/webhookDestinations/slack.js.map +1 -0
- package/dist/internal/webhookDestinations/url.d.ts +17 -0
- package/dist/internal/webhookDestinations/url.d.ts.map +1 -0
- package/dist/internal/webhookDestinations/url.js +130 -0
- package/dist/internal/webhookDestinations/url.js.map +1 -0
- package/dist/openrpc.json +5302 -0
- package/package.json +46 -4
- package/seeds/31318/icons/0x20c0000000000000000000000000000000000000.svg +1 -0
- package/seeds/31318/icons/0x20c0000000000000000000000000000000000001.svg +1 -0
- package/seeds/31318/icons/0x20c0000000000000000000000000000000000002.svg +1 -0
- package/seeds/31318/icons/0x20c0000000000000000000000000000000000003.svg +1 -0
- package/seeds/31318/verified-tokens.json +30 -0
- package/seeds/4217/icons/0x20c0000000000000000000000000000000000000.svg +25 -0
- package/seeds/4217/icons/0x20c000000000000000000000048c8f36df1c9a4a.svg +1 -0
- package/seeds/4217/icons/0x20c0000000000000000000000520792dcccccccc.svg +1 -0
- package/seeds/4217/icons/0x20c0000000000000000000000a6da882d075a4c3.svg +4 -0
- package/seeds/4217/icons/0x20c000000000000000000000111111111e910f0f.svg +30 -0
- package/seeds/4217/icons/0x20c00000000000000000000014f22ca97301eb73.svg +1 -0
- package/seeds/4217/icons/0x20c0000000000000000000001621e21f71cf12fb.svg +1 -0
- package/seeds/4217/icons/0x20c0000000000000000000002f52d5cc21a3207b.svg +1 -0
- package/seeds/4217/icons/0x20c0000000000000000000003158081efd85bfc2.svg +11 -0
- package/seeds/4217/icons/0x20c0000000000000000000003554d28269e0f3c2.svg +1 -0
- package/seeds/4217/icons/0x20c000000000000000000000383a23bacb546ab9.svg +1 -0
- package/seeds/4217/icons/0x20c00000000000000000000042109aef2f8b28e1.png +0 -0
- package/seeds/4217/icons/0x20c0000000000000000000005c0bac7cef389a11.svg +1 -0
- package/seeds/4217/icons/0x20c0000000000000000000006fd9a167923ba194.png +0 -0
- package/seeds/4217/icons/0x20c0000000000000000000007f7ba549dd0251b9.svg +1 -0
- package/seeds/4217/icons/0x20c0000000000000000000008191667423f70e67.svg +5 -0
- package/seeds/4217/icons/0x20c0000000000000000000008ee4fcff88888888.svg +1 -0
- package/seeds/4217/icons/0x20c0000000000000000000009a4a4b17e0dc6651.svg +1 -0
- package/seeds/4217/icons/0x20c000000000000000000000ab02d39df30bd17e.svg +1 -0
- package/seeds/4217/icons/0x20c000000000000000000000ae247a1130450f09.svg +1 -0
- package/seeds/4217/icons/0x20c000000000000000000000aeed2ec36a54d0e5.svg +1 -0
- package/seeds/4217/icons/0x20c000000000000000000000b9537d11c60e8b50.svg +1 -0
- package/seeds/4217/icons/0x20c000000000000000000000bd95bfb69fbe6ce3.svg +1 -0
- package/seeds/4217/icons/0x20c000000000000000000000c412ec89d0c08be5.svg +1 -0
- package/seeds/4217/icons/0x20c000000000000000000000f047dd7018e50367.png +0 -0
- package/seeds/4217/verified-tokens.json +177 -0
- package/seeds/42431/icons/0x20c0000000000000000000000000000000000000.svg +25 -0
- package/seeds/42431/icons/0x20c0000000000000000000000000000000000001.svg +1 -0
- package/seeds/42431/icons/0x20c0000000000000000000000000000000000002.svg +1 -0
- package/seeds/42431/icons/0x20c0000000000000000000000000000000000003.svg +1 -0
- package/seeds/42431/icons/0x20c0000000000000000000009e8d7eb59b783726.svg +1 -0
- package/seeds/42431/icons/0x20c000000000000000000000d72572838bbee59c.svg +1 -0
- package/seeds/42431/verified-tokens.json +44 -0
- package/seeds/openrpc/README.md +27 -0
- package/seeds/openrpc/execution-apis.json +8989 -0
- package/src/ApiKey.test.ts +91 -0
- package/src/ApiKey.ts +122 -0
- package/src/ApiKeySource.test-d.ts +11 -0
- package/src/ApiKeySource.test.ts +295 -0
- package/src/ApiKeySource.ts +303 -0
- package/src/App.test-d.ts +38 -0
- package/src/App.test.ts +65927 -0
- package/src/App.ts +855 -0
- package/src/Assets.ts +58 -0
- package/src/Auth.ts +1 -0
- package/src/Cli.test.ts +81 -0
- package/src/Cli.ts +38 -0
- package/src/Client.test-d.ts +1438 -0
- package/src/Client.test.ts +134 -0
- package/src/Client.ts +45 -0
- package/src/Metrics.ts +154 -0
- package/src/Scope.ts +50 -0
- package/src/Webhooks.test.ts +49 -0
- package/src/Webhooks.ts +172 -0
- package/src/admin/Access.test.ts +130 -0
- package/src/admin/Access.ts +87 -0
- package/src/admin/App.test.ts +225 -0
- package/src/admin/App.ts +216 -0
- package/src/admin/apps/api-keys.test.ts +156 -0
- package/src/admin/apps/api-keys.ts +157 -0
- package/src/admin/apps/scopes.test.ts +52 -0
- package/src/admin/apps/scopes.ts +47 -0
- package/src/admin/apps/verified-tokens.test.ts +415 -0
- package/src/admin/apps/verified-tokens.ts +566 -0
- package/src/admin/index.ts +2 -0
- package/src/admin/ui/plugin.ts +135 -0
- package/src/admin/ui/src/components/Checkbox.tsx +22 -0
- package/src/admin/ui/src/components/Dialog.tsx +32 -0
- package/src/admin/ui/src/components/MobileProfile.tsx +31 -0
- package/src/admin/ui/src/components/Page.tsx +53 -0
- package/src/admin/ui/src/components/TempoLogo.tsx +37 -0
- package/src/admin/ui/src/fonts/Pilat-Demi.woff2 +0 -0
- package/src/admin/ui/src/fonts/Pilat-Regular.woff2 +0 -0
- package/src/admin/ui/src/lib/api-keys.ts +63 -0
- package/src/admin/ui/src/lib/client.ts +15 -0
- package/src/admin/ui/src/lib/config.ts +57 -0
- package/src/admin/ui/src/lib/query-client.ts +26 -0
- package/src/admin/ui/src/lib/scopes.ts +25 -0
- package/src/admin/ui/src/lib/verified-tokens.ts +89 -0
- package/src/admin/ui/src/lib/wagmi.ts +18 -0
- package/src/admin/ui/src/routeTree.gen.ts +104 -0
- package/src/admin/ui/src/router.tsx +25 -0
- package/src/admin/ui/src/routes/__root.tsx +219 -0
- package/src/admin/ui/src/routes/api-keys.tsx +329 -0
- package/src/admin/ui/src/routes/index.tsx +7 -0
- package/src/admin/ui/src/routes/verified-tokens.tsx +331 -0
- package/src/admin/ui/src/styles.css +20 -0
- package/src/admin/ui/tsconfig.json +16 -0
- package/src/admin/ui/vite.config.ts +29 -0
- package/src/admin/ui.generated.ts +60 -0
- package/src/apps/activities.test.ts +1020 -0
- package/src/apps/activities.ts +3934 -0
- package/src/apps/addresses.ts +24 -0
- package/src/apps/balances.test.ts +274 -0
- package/src/apps/balances.ts +533 -0
- package/src/apps/blocks.test.ts +385 -0
- package/src/apps/blocks.ts +896 -0
- package/src/apps/coingecko.test.ts +218 -0
- package/src/apps/coingecko.ts +1383 -0
- package/src/apps/exchanges.test.ts +958 -0
- package/src/apps/exchanges.ts +4773 -0
- package/src/apps/fee-amm.test.ts +321 -0
- package/src/apps/fee-amm.ts +1121 -0
- package/src/apps/indexer.test.ts +153 -0
- package/src/apps/indexer.ts +330 -0
- package/src/apps/receipts.test.ts +285 -0
- package/src/apps/receipts.ts +1193 -0
- package/src/apps/rpc.test.ts +302 -0
- package/src/apps/rpc.ts +222 -0
- package/src/apps/tokenlist.test.ts +80 -0
- package/src/apps/tokenlist.ts +207 -0
- package/src/apps/tokens.test.ts +1062 -0
- package/src/apps/tokens.ts +2292 -0
- package/src/apps/transactions.test.ts +408 -0
- package/src/apps/transactions.ts +1363 -0
- package/src/apps/transfers.test.ts +371 -0
- package/src/apps/transfers.ts +1116 -0
- package/src/apps/verified-tokens.test.ts +114 -0
- package/src/apps/verified-tokens.ts +295 -0
- package/src/apps/webhooks.test.ts +1937 -0
- package/src/apps/webhooks.ts +2926 -0
- package/src/cloudflare.ts +55 -0
- package/src/index.ts +15 -0
- package/src/internal/Auth.test.ts +980 -0
- package/src/internal/Auth.ts +1012 -0
- package/src/internal/Cache.test.ts +240 -0
- package/src/internal/Cache.ts +262 -0
- package/src/internal/Cursor.test.ts +100 -0
- package/src/internal/Cursor.ts +149 -0
- package/src/internal/EdgeCache.test.ts +234 -0
- package/src/internal/EdgeCache.ts +224 -0
- package/src/internal/Errors.ts +14 -0
- package/src/internal/Log.test.ts +244 -0
- package/src/internal/Log.ts +216 -0
- package/src/internal/MetricSink.ts +220 -0
- package/src/internal/Mpp.test.ts +64 -0
- package/src/internal/Mpp.ts +141 -0
- package/src/internal/OpenApi.fonts.ts +38 -0
- package/src/internal/OpenApi.logo.ts +18 -0
- package/src/internal/OpenApi.ts +488 -0
- package/src/internal/Path.test.ts +14 -0
- package/src/internal/Path.ts +15 -0
- package/src/internal/RateLimit.test.ts +93 -0
- package/src/internal/RateLimit.ts +93 -0
- package/src/internal/Response.ts +131 -0
- package/src/internal/Schema.test.ts +34 -0
- package/src/internal/Schema.ts +523 -0
- package/src/internal/Store.test.ts +545 -0
- package/src/internal/Store.ts +526 -0
- package/src/internal/Tidx.local.test.ts +33 -0
- package/src/internal/Tidx.test.ts +294 -0
- package/src/internal/Tidx.ts +238 -0
- package/src/internal/Timing.test.ts +22 -0
- package/src/internal/Timing.ts +62 -0
- package/src/internal/Ttl.test.ts +18 -0
- package/src/internal/Ttl.ts +59 -0
- package/src/internal/Value.ts +114 -0
- package/src/internal/VerifiedTokens.test.ts +333 -0
- package/src/internal/VerifiedTokens.ts +645 -0
- package/src/internal/Viem.test.ts +115 -0
- package/src/internal/Viem.ts +165 -0
- package/src/internal/WebhookDestination.test.ts +648 -0
- package/src/internal/WebhookDestination.ts +331 -0
- package/src/internal/Webhooks.test.ts +668 -0
- package/src/internal/Webhooks.ts +940 -0
- package/src/internal/webhookDestinations/betterStack.ts +91 -0
- package/src/internal/webhookDestinations/slack.ts +496 -0
- package/src/internal/webhookDestinations/url.ts +128 -0
- package/src/openrpc.json +5302 -0
|
@@ -0,0 +1,1012 @@
|
|
|
1
|
+
import type { Context as hono_Context, Hono, MiddlewareHandler } from 'hono'
|
|
2
|
+
import type { HonoBase } from 'hono/hono-base'
|
|
3
|
+
import { createMiddleware } from 'hono/factory'
|
|
4
|
+
import { matchedRoutes } from 'hono/route'
|
|
5
|
+
import type { MergePath, RouterRoute, Schema as core_Schema } from 'hono/types'
|
|
6
|
+
import { findTargetHandler } from 'hono/utils/handler'
|
|
7
|
+
import { Credential, type Method } from 'mppx'
|
|
8
|
+
import { Mppx, tempo } from 'mppx/hono'
|
|
9
|
+
|
|
10
|
+
import * as ApiKeySource from '../ApiKeySource.js'
|
|
11
|
+
import * as ApiKey from '../ApiKey.js'
|
|
12
|
+
import * as OpenApi from './OpenApi.js'
|
|
13
|
+
import * as RateLimit from './RateLimit.js'
|
|
14
|
+
import * as Response from './Response.js'
|
|
15
|
+
import * as Schema from './Schema.js'
|
|
16
|
+
import * as Scope from '../Scope.js'
|
|
17
|
+
import * as Viem from './Viem.js'
|
|
18
|
+
|
|
19
|
+
const policySymbol = Symbol('tempo-api.auth.policy')
|
|
20
|
+
|
|
21
|
+
/** Auth context consumed by Tempo API handlers. */
|
|
22
|
+
export type Context = {
|
|
23
|
+
/** API key authentication configuration. */
|
|
24
|
+
apiKey?:
|
|
25
|
+
| {
|
|
26
|
+
/**
|
|
27
|
+
* API-key quotas keyed by quota scope. The reserved `'*'` entry is the
|
|
28
|
+
* config default; any other key sets that scope's quota. Mirrors the
|
|
29
|
+
* per-key {@link ApiKey.ApiKey.rateLimits} shape.
|
|
30
|
+
*/
|
|
31
|
+
rateLimits?: Record<string, RateLimit.Limit> | undefined
|
|
32
|
+
/** Backing API-key source. */
|
|
33
|
+
source: ApiKeySource.Source
|
|
34
|
+
}
|
|
35
|
+
| undefined
|
|
36
|
+
/** MPP payment configuration. */
|
|
37
|
+
mpp?: Mpp | undefined
|
|
38
|
+
/** Rate-limit store used by API handlers. */
|
|
39
|
+
rateLimit?: RateLimit.Store | undefined
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Hono environment variables set by auth middleware. */
|
|
43
|
+
export type Environment = {
|
|
44
|
+
Variables: Variables
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* HTTP endpoint key inferred from a Hono app schema. The schema is matched
|
|
49
|
+
* against `HonoBase` (which carries it as its 2nd generic) rather than `Hono`
|
|
50
|
+
* (which re-declares only 3 generics and loses the inferred schema when used
|
|
51
|
+
* as a value type from `ReturnType<typeof create>`).
|
|
52
|
+
*/
|
|
53
|
+
export type Endpoint<app extends Hono<any, any>, mount extends string = '/'> =
|
|
54
|
+
app extends HonoBase<any, infer schema extends core_Schema, any>
|
|
55
|
+
? endpoint.FromSchema<schema, mount>
|
|
56
|
+
: never
|
|
57
|
+
|
|
58
|
+
/** Metadata for an accepted payment. */
|
|
59
|
+
export type Payment = {
|
|
60
|
+
/** Verified MPP payer identifier. */
|
|
61
|
+
payer?: string | undefined
|
|
62
|
+
/** Why payment was required for the request. */
|
|
63
|
+
reason: 'api_key_over_quota' | 'public_over_quota'
|
|
64
|
+
/** Payment protocol kind. */
|
|
65
|
+
type: 'mpp'
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** MPP charge options used by route access policies. */
|
|
69
|
+
export type Charge = Method.RequestDefaults<ReturnType<typeof tempo.charge>> & {
|
|
70
|
+
/** Optional human-readable payment description. */
|
|
71
|
+
description?: string | undefined
|
|
72
|
+
/** Optional challenge expiration timestamp or date. */
|
|
73
|
+
expires?: Date | string | undefined
|
|
74
|
+
/** Optional server-defined correlation data. */
|
|
75
|
+
meta?: Record<string, string> | undefined
|
|
76
|
+
/** Optional route/resource scope bound to the challenge. */
|
|
77
|
+
scope?: string | undefined
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** MPP payment handler consumed by auth policies. */
|
|
81
|
+
type Mpp = {
|
|
82
|
+
/** Creates a Hono middleware for a Tempo charge. */
|
|
83
|
+
charge(options: Charge): MiddlewareHandler
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Route-level auth policy override. Each lane is independent: `true` enables it
|
|
88
|
+
* with the middleware defaults, `false` disables it, an object enables it with
|
|
89
|
+
* the given overrides, and omitting it inherits the resolved policy. Lanes are
|
|
90
|
+
* default-closed except `apiKey` (see {@link middleware}), so a route that
|
|
91
|
+
* declares nothing — or declares only `apiKey` — is API-key-only.
|
|
92
|
+
*/
|
|
93
|
+
export type PolicyOverride = {
|
|
94
|
+
/** API-key lane: pass quota/scopes overrides, or a boolean to enable/disable. */
|
|
95
|
+
apiKey?:
|
|
96
|
+
| {
|
|
97
|
+
/** API-key quota override for this route (a protective per-route cap). */
|
|
98
|
+
rateLimit?: RateLimit.Limit | undefined
|
|
99
|
+
/** Required API-key scopes override for this route. */
|
|
100
|
+
scopes?: readonly Scope.Id[] | undefined
|
|
101
|
+
}
|
|
102
|
+
| boolean
|
|
103
|
+
| undefined
|
|
104
|
+
/** MPP paid lane: object to override the charge, or boolean to enable/disable. */
|
|
105
|
+
mpp?:
|
|
106
|
+
| {
|
|
107
|
+
/** Tempo charge override for this route. */
|
|
108
|
+
charge?: Charge | undefined
|
|
109
|
+
}
|
|
110
|
+
| boolean
|
|
111
|
+
| undefined
|
|
112
|
+
/** Public lane: object to override the quota, or boolean to enable/disable. */
|
|
113
|
+
public?:
|
|
114
|
+
| {
|
|
115
|
+
/** Anonymous public quota override for this route. */
|
|
116
|
+
rateLimit?: RateLimit.Limit | undefined
|
|
117
|
+
}
|
|
118
|
+
| boolean
|
|
119
|
+
| undefined
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Authenticated or anonymous caller. */
|
|
123
|
+
export type Principal =
|
|
124
|
+
| {
|
|
125
|
+
/** Resolved API key. */
|
|
126
|
+
apiKey: ApiKey.ApiKey
|
|
127
|
+
/** Key environment (`production` accesses any chain; `sandbox` is non-mainnet only). */
|
|
128
|
+
environment: ApiKey.ApiKey['environment']
|
|
129
|
+
/** Principal id. */
|
|
130
|
+
id: string
|
|
131
|
+
/** Owning organization id. */
|
|
132
|
+
orgId: string
|
|
133
|
+
/** Payment metadata for paid overflow requests. */
|
|
134
|
+
payment?: Payment | undefined
|
|
135
|
+
/** Principal kind. */
|
|
136
|
+
type: 'api_key'
|
|
137
|
+
}
|
|
138
|
+
| {
|
|
139
|
+
/** Public quota identity. */
|
|
140
|
+
id: string
|
|
141
|
+
/** Payment metadata for paid overflow requests. */
|
|
142
|
+
payment?: Payment | undefined
|
|
143
|
+
/** Principal kind. */
|
|
144
|
+
type: 'public'
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** Hono variables set by auth middleware. */
|
|
148
|
+
export type Variables = {
|
|
149
|
+
/** Auth context consumed by protected handlers. */
|
|
150
|
+
auth: Context
|
|
151
|
+
/** Authenticated or anonymous caller. */
|
|
152
|
+
principal?: Principal | undefined
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** Reads the authenticated principal from a Hono context. */
|
|
156
|
+
export function getPrincipal<environment extends Environment>(c: hono_Context<environment>) {
|
|
157
|
+
return c.get('principal') ?? null
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Always false at runtime, but typed as `boolean` so handlers can include a
|
|
162
|
+
* never-reached access-error branch in Hono's inferred response union.
|
|
163
|
+
*/
|
|
164
|
+
export const narrowAccess = false as boolean
|
|
165
|
+
|
|
166
|
+
/** Typed auth/payment error response for Hono client inference. Never reached at runtime. */
|
|
167
|
+
export function accessError(c: hono_Context) {
|
|
168
|
+
const status = Number(c.req.header('tempo-narrow-access-status'))
|
|
169
|
+
if (status === 400)
|
|
170
|
+
return Response.error(c, {
|
|
171
|
+
code: 'api_key_malformed',
|
|
172
|
+
message: 'Malformed API key',
|
|
173
|
+
status: 400,
|
|
174
|
+
})
|
|
175
|
+
if (status === 401) {
|
|
176
|
+
const code =
|
|
177
|
+
c.req.header('tempo-narrow-access-code') === 'api_key_missing'
|
|
178
|
+
? 'api_key_missing'
|
|
179
|
+
: 'api_key_invalid'
|
|
180
|
+
return Response.error(c, {
|
|
181
|
+
code,
|
|
182
|
+
message: 'Missing or invalid API key',
|
|
183
|
+
status: 401,
|
|
184
|
+
})
|
|
185
|
+
}
|
|
186
|
+
if (status === 403)
|
|
187
|
+
return Response.error(c, {
|
|
188
|
+
code: 'api_key_forbidden',
|
|
189
|
+
message: 'API key missing required scope',
|
|
190
|
+
status: 403,
|
|
191
|
+
})
|
|
192
|
+
const code =
|
|
193
|
+
c.req.header('tempo-narrow-access-code') === 'rate_limit_exceeded'
|
|
194
|
+
? 'rate_limit_exceeded'
|
|
195
|
+
: 'payment_required'
|
|
196
|
+
return Response.error(c, {
|
|
197
|
+
code,
|
|
198
|
+
message: 'Payment required or rate limit exceeded',
|
|
199
|
+
status: 429,
|
|
200
|
+
})
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/** Typed auth/payment error response for Hono client inference. Never reached at runtime. */
|
|
204
|
+
export function paidAccessError(c: hono_Context) {
|
|
205
|
+
const status = Number(c.req.header('tempo-narrow-access-status'))
|
|
206
|
+
if (status !== 402) return accessError(c)
|
|
207
|
+
// MPP owns the 402 challenge body and headers; it is intentionally not the
|
|
208
|
+
// API's JSON error envelope.
|
|
209
|
+
return c.body(null, 402)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/** Creates auth middleware for protected OpenAPI routes. */
|
|
213
|
+
export function middleware<
|
|
214
|
+
endpoint extends string = string,
|
|
215
|
+
environment extends Environment = Environment,
|
|
216
|
+
>(options: middleware.Options<endpoint>): MiddlewareHandler<environment> {
|
|
217
|
+
const { charge = {}, ...mpp } = options.mpp === false ? {} : (options.mpp ?? {})
|
|
218
|
+
const defaults = laneDefaults(options)
|
|
219
|
+
const base = basePolicy(options, defaults)
|
|
220
|
+
// Resolution is cached per isolate by default (positive 60s / negative 10s,
|
|
221
|
+
// keyed by token sha256 — see `ApiKeySource.cached`) so hot keys skip the
|
|
222
|
+
// backend read on the auth path of every request. Pass `apiKey.cache: false`
|
|
223
|
+
// when instant revocation matters more than the per-request read.
|
|
224
|
+
const source = (() => {
|
|
225
|
+
if (options.apiKey === false || !options.apiKey?.source) return ApiKeySource.noop
|
|
226
|
+
if (options.apiKey.cache === false) return options.apiKey.source
|
|
227
|
+
return ApiKeySource.cached(options.apiKey.source, options.apiKey.cache)
|
|
228
|
+
})()
|
|
229
|
+
const auth: Context = {
|
|
230
|
+
...(options.apiKey === false
|
|
231
|
+
? {}
|
|
232
|
+
: {
|
|
233
|
+
apiKey: {
|
|
234
|
+
...(options.apiKey?.rateLimits ? { rateLimits: options.apiKey.rateLimits } : {}),
|
|
235
|
+
source,
|
|
236
|
+
},
|
|
237
|
+
}),
|
|
238
|
+
...(options.mpp === false
|
|
239
|
+
? {}
|
|
240
|
+
: {
|
|
241
|
+
mpp: Mppx.create({
|
|
242
|
+
...mpp,
|
|
243
|
+
methods: [tempo.charge({ amount: '0.001', ...charge })],
|
|
244
|
+
secretKey: mpp.secretKey ?? crypto.randomUUID(),
|
|
245
|
+
}),
|
|
246
|
+
}),
|
|
247
|
+
rateLimit: RateLimit.memory(options.rateLimit),
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return createMiddleware<environment>((c, next) => {
|
|
251
|
+
const route = openApiRoute(c)
|
|
252
|
+
if (!route) return next()
|
|
253
|
+
|
|
254
|
+
const { key, description } = route
|
|
255
|
+
const overrides = options.overrides as
|
|
256
|
+
| Record<string, PolicyOverride | false | undefined>
|
|
257
|
+
| undefined
|
|
258
|
+
const endpointPolicy = resolvePolicy(
|
|
259
|
+
base,
|
|
260
|
+
defaults,
|
|
261
|
+
[...policies(c), overrides?.[key]],
|
|
262
|
+
description,
|
|
263
|
+
)
|
|
264
|
+
if (!endpointPolicy) return next()
|
|
265
|
+
|
|
266
|
+
c.set('auth', auth)
|
|
267
|
+
return require<environment>(endpointPolicy)(c, next)
|
|
268
|
+
})
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export declare namespace middleware {
|
|
272
|
+
/** Per-route policy overrides keyed by HTTP endpoint. */
|
|
273
|
+
type Overrides<endpoint extends string = string> = Partial<
|
|
274
|
+
Record<NoInfer<endpoint>, PolicyOverride | false | undefined>
|
|
275
|
+
>
|
|
276
|
+
|
|
277
|
+
/** Options for creating auth middleware. */
|
|
278
|
+
type Options<endpoint extends string = string> = {
|
|
279
|
+
/** Default API-key access policy. Pass false to disable API-key access. */
|
|
280
|
+
apiKey?:
|
|
281
|
+
| {
|
|
282
|
+
/**
|
|
283
|
+
* Resolve-cache tuning for the source (per-isolate, positive 60s /
|
|
284
|
+
* negative 10s by default), or `false` to resolve uncached on every
|
|
285
|
+
* request — e.g. when instant revocation matters more than the
|
|
286
|
+
* per-request backend read.
|
|
287
|
+
*/
|
|
288
|
+
cache?: ApiKeySource.cached.Options | false | undefined
|
|
289
|
+
/** API-key quotas keyed by quota scope (reserved `'*'` is the config default). */
|
|
290
|
+
rateLimits?: Record<string, RateLimit.Limit> | undefined
|
|
291
|
+
/** Backing API-key source. Defaults to an empty source (rejects all). */
|
|
292
|
+
source?: ApiKeySource.Source | undefined
|
|
293
|
+
/** Required API-key scopes. */
|
|
294
|
+
scopes?: readonly Scope.Id[] | undefined
|
|
295
|
+
}
|
|
296
|
+
| false
|
|
297
|
+
| undefined
|
|
298
|
+
/** MPP payment options. Pass false to disable paid overflow. */
|
|
299
|
+
mpp?: MppOptions | false | undefined
|
|
300
|
+
/** Per-route policy overrides keyed by HTTP endpoint, e.g. `GET /tokens/:token`. */
|
|
301
|
+
overrides?: Overrides<endpoint> | undefined
|
|
302
|
+
/** Default public access policy. Pass false to disable anonymous access. */
|
|
303
|
+
public?:
|
|
304
|
+
| {
|
|
305
|
+
/** Anonymous public quota for protected routes. */
|
|
306
|
+
rateLimit?: RateLimit.Limit | undefined
|
|
307
|
+
}
|
|
308
|
+
| false
|
|
309
|
+
| undefined
|
|
310
|
+
/** Rate-limit store options. */
|
|
311
|
+
rateLimit?: RateLimit.memory.Options | undefined
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/** MPP payment options used by auth middleware. */
|
|
315
|
+
type MppOptions = Omit<NonNullable<Parameters<typeof Mppx.create>[0]>, 'methods'> & {
|
|
316
|
+
/** Global Tempo charge options passed to the MPP method instantiator. */
|
|
317
|
+
charge?: NonNullable<Parameters<typeof tempo.charge>[0]> | undefined
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/** Creates route middleware that contributes an auth policy override. */
|
|
322
|
+
export function policy(options: PolicyOverride): MiddlewareHandler {
|
|
323
|
+
const middleware = createMiddleware((_, next) => next()) as MiddlewareHandler & PolicyHandler
|
|
324
|
+
middleware[policySymbol] = options
|
|
325
|
+
return middleware
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
export declare namespace endpoint {
|
|
329
|
+
/**
|
|
330
|
+
* HTTP endpoint key inferred from a Hono app schema. Distributes over the
|
|
331
|
+
* schema union so the `BlankSchema | MergeSchemaPath<...>` shape Hono
|
|
332
|
+
* produces from `.basePath(...).route(...)` chains contributes its typed
|
|
333
|
+
* routes (a non-distributing mapped type would collapse `BlankSchema` to
|
|
334
|
+
* `never` and infect the whole union).
|
|
335
|
+
*/
|
|
336
|
+
type FromSchema<schema extends core_Schema, mount extends string = '/'> = schema extends infer s
|
|
337
|
+
? {
|
|
338
|
+
[path in keyof s & string]: {
|
|
339
|
+
[method in keyof s[path] &
|
|
340
|
+
`$${string}`]: `${Method<method>} ${Path<MergePath<mount, path>>}`
|
|
341
|
+
}[keyof s[path] & `$${string}`]
|
|
342
|
+
}[keyof s & string]
|
|
343
|
+
: never
|
|
344
|
+
|
|
345
|
+
/** HTTP method string extracted from Hono's schema method key. */
|
|
346
|
+
type Method<method extends string> = method extends `$${infer value}` ? Uppercase<value> : never
|
|
347
|
+
|
|
348
|
+
/** Public endpoint path with Hono route regex constraints removed. */
|
|
349
|
+
type Path<path extends string> = path extends `${infer head}/${infer tail}`
|
|
350
|
+
? `${Segment<head>}/${Path<tail>}`
|
|
351
|
+
: Segment<path>
|
|
352
|
+
|
|
353
|
+
/** Public endpoint path segment with any regex constraint removed. */
|
|
354
|
+
type Segment<segment extends string> = segment extends `:${infer parameter}{${string}`
|
|
355
|
+
? `:${parameter}`
|
|
356
|
+
: segment
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/** Per-lane default config a route inherits when it enables a lane. */
|
|
360
|
+
type LaneDefaults = {
|
|
361
|
+
apiKey: NonNullable<require.Policy['apiKey']>
|
|
362
|
+
mpp: NonNullable<require.Policy['mpp']>
|
|
363
|
+
public: NonNullable<require.Policy['public']>
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
type OpenApiHandler = {
|
|
367
|
+
[OpenApi.uniqueSymbol]?: OpenApi.HandlerUniqueProperty | undefined
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
type OpenApiRoute = {
|
|
371
|
+
key: string
|
|
372
|
+
description: string | undefined
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
type PolicyHandler = {
|
|
376
|
+
[policySymbol]?: PolicyOverride | undefined
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function laneDefaults(options: middleware.Options): LaneDefaults {
|
|
380
|
+
const apiKey = options.apiKey === false ? undefined : options.apiKey
|
|
381
|
+
const public_ = options.public === false ? undefined : options.public
|
|
382
|
+
return {
|
|
383
|
+
apiKey: {
|
|
384
|
+
// `rateLimit` is intentionally omitted: `policy.apiKey.rateLimit` means an
|
|
385
|
+
// explicit per-route cap (precedence tier 2), not the config default. The
|
|
386
|
+
// config default (tier 5) lives on `auth.apiKey.rateLimits['*']` and is
|
|
387
|
+
// applied by `resolveApiKeyLimit` at consume time.
|
|
388
|
+
//
|
|
389
|
+
// Default-closed to a full-access key: a route that declares no scopes
|
|
390
|
+
// requires the `'*'` wildcard, so a narrowly-scoped key only reaches
|
|
391
|
+
// routes that explicitly grant its scope. Routes opt into specific
|
|
392
|
+
// catalog scopes via `Auth.policy`.
|
|
393
|
+
scopes: apiKey?.scopes ?? [Scope.wildcard],
|
|
394
|
+
},
|
|
395
|
+
mpp: { charge: {} },
|
|
396
|
+
public: { rateLimit: public_?.rateLimit ?? { perMinute: 60 } },
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Base policy before route overrides. Default-closed except the API-key lane:
|
|
402
|
+
* a route that declares nothing is API-key-only, and must opt into the public
|
|
403
|
+
* and paid lanes explicitly. This makes a partial override fail safe — omitting
|
|
404
|
+
* a lane leaves it closed rather than silently inheriting open access.
|
|
405
|
+
*/
|
|
406
|
+
function basePolicy(options: middleware.Options, defaults: LaneDefaults): require.Policy {
|
|
407
|
+
return options.apiKey === false ? {} : { apiKey: defaults.apiKey }
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function resolvePolicy(
|
|
411
|
+
base: require.Policy,
|
|
412
|
+
defaults: LaneDefaults,
|
|
413
|
+
overrides: readonly (PolicyOverride | false | undefined)[],
|
|
414
|
+
description: string | undefined,
|
|
415
|
+
): require.Policy | undefined {
|
|
416
|
+
let apiKey = base.apiKey
|
|
417
|
+
let mpp = base.mpp
|
|
418
|
+
let public_ = base.public
|
|
419
|
+
|
|
420
|
+
for (const override of overrides) {
|
|
421
|
+
if (override === false) return undefined
|
|
422
|
+
if (!override) continue
|
|
423
|
+
apiKey = resolveApiKeyLane(apiKey, override.apiKey, defaults.apiKey)
|
|
424
|
+
mpp = resolveMppLane(mpp, override.mpp, defaults.mpp)
|
|
425
|
+
public_ = resolvePublicLane(public_, override.public, defaults.public)
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// The OpenAPI summary/description rides along as the payment-challenge
|
|
429
|
+
// description. It is metadata, not a policy override, so it only applies when
|
|
430
|
+
// the paid lane is actually enabled and has no explicit description already.
|
|
431
|
+
if (mpp && description !== undefined && mpp.charge.description === undefined)
|
|
432
|
+
mpp = { charge: { ...mpp.charge, description } }
|
|
433
|
+
|
|
434
|
+
if (!apiKey && !mpp && !public_) return undefined
|
|
435
|
+
return {
|
|
436
|
+
...(apiKey ? { apiKey } : {}),
|
|
437
|
+
...(mpp ? { mpp } : {}),
|
|
438
|
+
...(public_ ? { public: public_ } : {}),
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function resolveApiKeyLane(
|
|
443
|
+
current: require.Policy['apiKey'],
|
|
444
|
+
override: PolicyOverride['apiKey'],
|
|
445
|
+
default_: LaneDefaults['apiKey'],
|
|
446
|
+
): require.Policy['apiKey'] {
|
|
447
|
+
if (override === undefined) return current
|
|
448
|
+
if (override === false) return undefined
|
|
449
|
+
if (override === true) return current ?? default_
|
|
450
|
+
const base = current ?? default_
|
|
451
|
+
return {
|
|
452
|
+
rateLimit: override.rateLimit ?? base.rateLimit,
|
|
453
|
+
scopes: override.scopes ?? base.scopes,
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function resolveMppLane(
|
|
458
|
+
current: require.Policy['mpp'],
|
|
459
|
+
override: PolicyOverride['mpp'],
|
|
460
|
+
default_: LaneDefaults['mpp'],
|
|
461
|
+
): require.Policy['mpp'] {
|
|
462
|
+
if (override === undefined) return current
|
|
463
|
+
if (override === false) return undefined
|
|
464
|
+
if (override === true) return current ?? default_
|
|
465
|
+
const base = current ?? default_
|
|
466
|
+
return { charge: { ...base.charge, ...override.charge } }
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
function resolvePublicLane(
|
|
470
|
+
current: require.Policy['public'],
|
|
471
|
+
override: PolicyOverride['public'],
|
|
472
|
+
default_: LaneDefaults['public'],
|
|
473
|
+
): require.Policy['public'] {
|
|
474
|
+
if (override === undefined) return current
|
|
475
|
+
if (override === false) return undefined
|
|
476
|
+
if (override === true) return current ?? default_
|
|
477
|
+
const base = current ?? default_
|
|
478
|
+
return { rateLimit: override.rateLimit ?? base.rateLimit }
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
function openApiRoute(c: hono_Context): OpenApiRoute | undefined {
|
|
482
|
+
const route = matchedRoutes(c)
|
|
483
|
+
.slice(c.req.routeIndex + 1)
|
|
484
|
+
.find((route) => !!openApiSpec(route))
|
|
485
|
+
if (!route) return undefined
|
|
486
|
+
|
|
487
|
+
const spec = openApiSpec(route)
|
|
488
|
+
if (!spec) return undefined
|
|
489
|
+
|
|
490
|
+
const description = typeof spec.summary === 'string' ? spec.summary : spec.description
|
|
491
|
+
return {
|
|
492
|
+
key: endpointKey(c, route),
|
|
493
|
+
description: typeof description === 'string' ? description : undefined,
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
function policies(c: hono_Context) {
|
|
498
|
+
return matchedRoutes(c)
|
|
499
|
+
.slice(c.req.routeIndex + 1)
|
|
500
|
+
.map(policyFromRoute)
|
|
501
|
+
.filter((policy): policy is PolicyOverride => policy !== undefined)
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function openApiSpec(route: RouterRoute) {
|
|
505
|
+
const handler = findTargetHandler(route.handler) as OpenApiHandler
|
|
506
|
+
const metadata = handler[OpenApi.uniqueSymbol]
|
|
507
|
+
if (!metadata || !('spec' in metadata)) return undefined
|
|
508
|
+
return metadata.spec
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
function policyFromRoute(route: RouterRoute) {
|
|
512
|
+
const handler = findTargetHandler(route.handler) as PolicyHandler
|
|
513
|
+
return handler[policySymbol]
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/** Resolved access lanes for one documented operation. */
|
|
517
|
+
export type OperationAccess = {
|
|
518
|
+
/** Whether MPP payment is accepted (enables the `402` payment-challenge response). */
|
|
519
|
+
mpp: boolean
|
|
520
|
+
/** Whether anonymous public-quota access is allowed (keeps the anonymous security option). */
|
|
521
|
+
public: boolean
|
|
522
|
+
/** Required API-key scopes (`[]` accepts any valid key); empty when the API-key lane is disabled. */
|
|
523
|
+
scopes: readonly Scope.Id[]
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
/**
|
|
527
|
+
* Maps each documented operation id to its resolved access lanes by reading the
|
|
528
|
+
* `policy` overrides co-located on the app's routes. This is the single source
|
|
529
|
+
* of truth for per-operation `security` and the `402` response in the generated
|
|
530
|
+
* OpenAPI document (see `App.create`): the API-key lane is default-on, and the
|
|
531
|
+
* public/MPP lanes apply only where a route opts in via {@link policy}.
|
|
532
|
+
*/
|
|
533
|
+
export function describeAccess(app: Hono<any, any>): Record<string, OperationAccess> {
|
|
534
|
+
const groups = new Map<string, { operationId?: string; overrides: PolicyOverride[] }>()
|
|
535
|
+
for (const route of app.routes) {
|
|
536
|
+
const key = `${route.method} ${route.path}`
|
|
537
|
+
const group = groups.get(key) ?? { overrides: [] }
|
|
538
|
+
const spec = openApiSpec(route) as { operationId?: unknown } | undefined
|
|
539
|
+
if (typeof spec?.operationId === 'string') group.operationId = spec.operationId
|
|
540
|
+
const override = policyFromRoute(route)
|
|
541
|
+
if (override) group.overrides.push(override)
|
|
542
|
+
groups.set(key, group)
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
const access: Record<string, OperationAccess> = {}
|
|
546
|
+
for (const { operationId, overrides } of groups.values()) {
|
|
547
|
+
if (!operationId) continue
|
|
548
|
+
let apiKey = true
|
|
549
|
+
let scopes: readonly Scope.Id[] = []
|
|
550
|
+
let public_ = false
|
|
551
|
+
let mpp = false
|
|
552
|
+
for (const override of overrides) {
|
|
553
|
+
if (override.apiKey !== undefined) apiKey = override.apiKey !== false
|
|
554
|
+
if (override.apiKey && override.apiKey !== true && override.apiKey.scopes)
|
|
555
|
+
scopes = override.apiKey.scopes
|
|
556
|
+
if (override.public !== undefined) public_ = override.public !== false
|
|
557
|
+
if (override.mpp !== undefined) mpp = override.mpp !== false
|
|
558
|
+
}
|
|
559
|
+
access[operationId] = { mpp, public: public_, scopes: apiKey ? scopes : [] }
|
|
560
|
+
}
|
|
561
|
+
return access
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
function endpointKey(c: hono_Context, route: RouterRoute) {
|
|
565
|
+
return `${route.method.toUpperCase()} ${stripBasePath(normalizeRoutePath(route.path), basePath(c))}`
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
function normalizeRoutePath(path: string) {
|
|
569
|
+
// Hono stores regex-constrained params as `:name{...}`. Auth overrides use
|
|
570
|
+
// the stable public route shape, e.g. `GET /tokens/:symbol`.
|
|
571
|
+
return path
|
|
572
|
+
.split('/')
|
|
573
|
+
.map((segment) => {
|
|
574
|
+
if (!segment.startsWith(':')) return segment
|
|
575
|
+
const pattern = segment.indexOf('{')
|
|
576
|
+
return pattern === -1 ? segment : segment.slice(0, pattern)
|
|
577
|
+
})
|
|
578
|
+
.join('/')
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
function stripBasePath(path: string, basePath: string | undefined) {
|
|
582
|
+
if (!basePath || basePath === '/') return path
|
|
583
|
+
if (path === basePath) return '/'
|
|
584
|
+
if (path.startsWith(`${basePath}/`)) return path.slice(basePath.length)
|
|
585
|
+
return path
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
function basePath(c: hono_Context) {
|
|
589
|
+
const variables = c.var as Record<string, unknown>
|
|
590
|
+
return typeof variables['basePath'] === 'string' ? variables['basePath'] : undefined
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
/** Requires route access through API key, public quota, or MPP payment. */
|
|
594
|
+
export function require<environment extends Environment = Environment>(
|
|
595
|
+
policy: require.Policy,
|
|
596
|
+
): MiddlewareHandler<environment> {
|
|
597
|
+
return createMiddleware<environment>(async (c, next) => {
|
|
598
|
+
const auth = c.get('auth')
|
|
599
|
+
const credential = getApiKeyCredential(c)
|
|
600
|
+
// The deprecated `key` query param has now been consumed for auth. Strip it
|
|
601
|
+
// from the request URL so it never reaches a route's query validator —
|
|
602
|
+
// endpoint query schemas are `z.strictObject`, so an unexpected `key`
|
|
603
|
+
// param would otherwise fail validation with `query_invalid`, breaking the
|
|
604
|
+
// legacy `?key=...` lane on every strict-query endpoint.
|
|
605
|
+
stripLegacyAuthQuery(c)
|
|
606
|
+
if (credential === false)
|
|
607
|
+
return jsonError(c, {
|
|
608
|
+
code: 'api_key_malformed',
|
|
609
|
+
message: 'Malformed API key',
|
|
610
|
+
status: 400,
|
|
611
|
+
})
|
|
612
|
+
|
|
613
|
+
const hasPayment = hasPaymentCredential(c) && isPaymentEnabled(auth, policy)
|
|
614
|
+
if (credential) {
|
|
615
|
+
const apiKey = await auth.apiKey?.source.resolve(credential.token)
|
|
616
|
+
if (!apiKey)
|
|
617
|
+
return jsonError(c, {
|
|
618
|
+
code: 'api_key_invalid',
|
|
619
|
+
message: 'Invalid API key',
|
|
620
|
+
status: 401,
|
|
621
|
+
})
|
|
622
|
+
|
|
623
|
+
// Sandbox keys are restricted to non-mainnet chains. An explicit
|
|
624
|
+
// `chainId` of mainnet makes the key unusable for this request, so it is
|
|
625
|
+
// ignored and the request falls through to the public policy below (as if
|
|
626
|
+
// no key were presented). A request that omits `chainId` is steered to
|
|
627
|
+
// testnet so a sandbox key still "just works" without passing `?chainId`.
|
|
628
|
+
const explicitChain = explicitChainId(c)
|
|
629
|
+
const sandboxOnMainnet =
|
|
630
|
+
apiKey.environment === 'sandbox' &&
|
|
631
|
+
explicitChain !== undefined &&
|
|
632
|
+
Viem.isMainnet(explicitChain)
|
|
633
|
+
|
|
634
|
+
if (!sandboxOnMainnet) {
|
|
635
|
+
if (
|
|
636
|
+
apiKey.environment === 'sandbox' &&
|
|
637
|
+
explicitChain === undefined &&
|
|
638
|
+
Viem.isMainnet((c.get('chainId' as never) as number | undefined) ?? Viem.defaultChainId)
|
|
639
|
+
)
|
|
640
|
+
// `chainId` lives on the app's `Variables`, not the narrower auth
|
|
641
|
+
// contract, so it is read/written through an untyped cast.
|
|
642
|
+
c.set('chainId' as never, Viem.chainId.testnet as never)
|
|
643
|
+
|
|
644
|
+
const principal = principalFromApiKey(apiKey)
|
|
645
|
+
c.set('principal', principal)
|
|
646
|
+
const missing = apiKey.scopes.includes(Scope.wildcard)
|
|
647
|
+
? []
|
|
648
|
+
: (policy.apiKey?.scopes.filter((scope) => !apiKey.scopes.includes(scope)) ?? [])
|
|
649
|
+
if (missing.length > 0)
|
|
650
|
+
return jsonError(c, {
|
|
651
|
+
code: 'api_key_forbidden',
|
|
652
|
+
message: 'API key missing required scope',
|
|
653
|
+
status: 403,
|
|
654
|
+
})
|
|
655
|
+
|
|
656
|
+
if (hasPayment)
|
|
657
|
+
return requirePayment(c, next, {
|
|
658
|
+
auth,
|
|
659
|
+
payment: { reason: 'api_key_over_quota', type: 'mpp' },
|
|
660
|
+
policy,
|
|
661
|
+
principal,
|
|
662
|
+
})
|
|
663
|
+
|
|
664
|
+
if (policy.apiKey) {
|
|
665
|
+
// A route consumes exactly one quota bucket: its single required scope,
|
|
666
|
+
// else `'*'` (zero scopes). Routes today require at most one scope; a
|
|
667
|
+
// future multi-scope route buckets against its first scope.
|
|
668
|
+
const scope = policy.apiKey.scopes[0] ?? Scope.wildcard
|
|
669
|
+
const limit = resolveApiKeyLimit({ apiKey, auth, policy: policy.apiKey, scope })
|
|
670
|
+
const result = await consumeRateLimit(auth, {
|
|
671
|
+
key: `api_key:${apiKey.id}:scope:${scope}`,
|
|
672
|
+
limit,
|
|
673
|
+
})
|
|
674
|
+
if (result) {
|
|
675
|
+
setRateLimitHeaders(c, result)
|
|
676
|
+
// Surface the bucket so callers can correlate a 429 with the exhausted
|
|
677
|
+
// quota. Set before the over-quota branch so it rides the 429 (an MPP
|
|
678
|
+
// 402 challenge is owned by mppx and drops these headers — by design).
|
|
679
|
+
c.header('RateLimit-Scope', scope)
|
|
680
|
+
}
|
|
681
|
+
if (result && !result.allowed)
|
|
682
|
+
return requirePaymentOrRateLimit(c, next, {
|
|
683
|
+
auth,
|
|
684
|
+
payment: { reason: 'api_key_over_quota', type: 'mpp' },
|
|
685
|
+
policy,
|
|
686
|
+
principal,
|
|
687
|
+
rateLimit: result,
|
|
688
|
+
})
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
return next()
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
const publicPrincipal = principalFromPublic(c)
|
|
696
|
+
if (hasPayment) {
|
|
697
|
+
c.set('principal', publicPrincipal)
|
|
698
|
+
return requirePayment(c, next, {
|
|
699
|
+
auth,
|
|
700
|
+
payment: { reason: 'public_over_quota', type: 'mpp' },
|
|
701
|
+
policy,
|
|
702
|
+
principal: publicPrincipal,
|
|
703
|
+
})
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
const limit = policy.public?.rateLimit
|
|
707
|
+
if (limit) {
|
|
708
|
+
const result = await consumeRateLimit(auth, {
|
|
709
|
+
key: `public:${publicPrincipal.id}`,
|
|
710
|
+
limit,
|
|
711
|
+
})
|
|
712
|
+
if (result) setRateLimitHeaders(c, result)
|
|
713
|
+
if (!result || result.allowed) {
|
|
714
|
+
c.set('principal', publicPrincipal)
|
|
715
|
+
return next()
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
c.set('principal', publicPrincipal)
|
|
719
|
+
return requirePaymentOrRateLimit(c, next, {
|
|
720
|
+
auth,
|
|
721
|
+
payment: { reason: 'public_over_quota', type: 'mpp' },
|
|
722
|
+
policy,
|
|
723
|
+
principal: publicPrincipal,
|
|
724
|
+
rateLimit: result,
|
|
725
|
+
})
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
if (isPaymentEnabled(auth, policy)) {
|
|
729
|
+
c.set('principal', publicPrincipal)
|
|
730
|
+
return requirePayment(c, next, {
|
|
731
|
+
auth,
|
|
732
|
+
payment: { reason: 'public_over_quota', type: 'mpp' },
|
|
733
|
+
policy,
|
|
734
|
+
principal: publicPrincipal,
|
|
735
|
+
})
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
return jsonError(c, {
|
|
739
|
+
code: 'api_key_missing',
|
|
740
|
+
message: 'Missing API key',
|
|
741
|
+
status: 401,
|
|
742
|
+
})
|
|
743
|
+
})
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
export declare namespace require {
|
|
747
|
+
/** Route access policy. */
|
|
748
|
+
type Policy = {
|
|
749
|
+
/** API-key access policy. */
|
|
750
|
+
apiKey?:
|
|
751
|
+
| {
|
|
752
|
+
/** API-key quota override for this route (a protective per-route cap). */
|
|
753
|
+
rateLimit?: RateLimit.Limit | undefined
|
|
754
|
+
/**
|
|
755
|
+
* Required API-key scopes. Defaults to `['*']`, so a route that does
|
|
756
|
+
* not declare scopes requires a full-access key; narrow it by listing
|
|
757
|
+
* catalog scopes via {@link policy}.
|
|
758
|
+
*/
|
|
759
|
+
scopes: readonly (Scope.Id | typeof Scope.wildcard)[]
|
|
760
|
+
}
|
|
761
|
+
| undefined
|
|
762
|
+
/** MPP payment access policy. */
|
|
763
|
+
mpp?:
|
|
764
|
+
| {
|
|
765
|
+
/** Tempo charge policy. */
|
|
766
|
+
charge: Charge
|
|
767
|
+
}
|
|
768
|
+
| undefined
|
|
769
|
+
/** Public access policy. */
|
|
770
|
+
public?:
|
|
771
|
+
| {
|
|
772
|
+
/** Anonymous public quota for this route. */
|
|
773
|
+
rateLimit: RateLimit.Limit
|
|
774
|
+
}
|
|
775
|
+
| undefined
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
type ApiKeyCredential = {
|
|
780
|
+
token: string
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
type PaymentAccess = {
|
|
784
|
+
auth: Context
|
|
785
|
+
payment: Omit<Payment, 'payer'>
|
|
786
|
+
policy: require.Policy
|
|
787
|
+
principal: Principal
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
type RateLimitAccess = PaymentAccess & {
|
|
791
|
+
rateLimit: RateLimit.Result
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
function consumeRateLimit(
|
|
795
|
+
auth: Context,
|
|
796
|
+
options: RateLimit.Store.ConsumeOptions,
|
|
797
|
+
): Promise<RateLimit.Result | null> {
|
|
798
|
+
if (!auth.rateLimit) return Promise.resolve(null)
|
|
799
|
+
// Fail open: a rate-limit store error (e.g. a lost Durable Object
|
|
800
|
+
// connection) degrades to unenforced quota instead of failing the request.
|
|
801
|
+
return auth.rateLimit.consume(options).catch((error) => {
|
|
802
|
+
console.error('[auth] rate-limit consume failed; failing open', error)
|
|
803
|
+
return null
|
|
804
|
+
})
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
/**
|
|
808
|
+
* Resolves the rate limit for an API-key request against a quota bucket.
|
|
809
|
+
* Most-specific wins: a per-key per-scope grant is the only thing that pierces a
|
|
810
|
+
* route's protective cap; the per-key `'*'` default does not (so a premium key
|
|
811
|
+
* can't silently blow past an expensive endpoint's ceiling). Per-key values live
|
|
812
|
+
* in the optional {@link ApiKey.ApiKey.rateLimits} map and stay `undefined` until a
|
|
813
|
+
* record source populates them, so until then only tiers 2/4/5/6 apply.
|
|
814
|
+
*/
|
|
815
|
+
function resolveApiKeyLimit(options: resolveApiKeyLimit.Options): RateLimit.Limit {
|
|
816
|
+
const { apiKey, auth, policy, scope } = options
|
|
817
|
+
return (
|
|
818
|
+
apiKey.rateLimits?.[scope] ?? // 1. per-key, per-scope
|
|
819
|
+
policy?.rateLimit ?? // 2. route override (protective cap)
|
|
820
|
+
apiKey.rateLimits?.[Scope.wildcard] ?? // 3. per-key default
|
|
821
|
+
auth.apiKey?.rateLimits?.[scope] ?? // 4. config per-scope
|
|
822
|
+
auth.apiKey?.rateLimits?.[Scope.wildcard] ?? { perMinute: 100 } // 5. config default // 6. hardcoded fallback
|
|
823
|
+
)
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
declare namespace resolveApiKeyLimit {
|
|
827
|
+
type Options = {
|
|
828
|
+
/** Resolved API key (carries per-key overrides). */
|
|
829
|
+
apiKey: ApiKey.ApiKey
|
|
830
|
+
/** Auth context (carries config defaults). */
|
|
831
|
+
auth: Context
|
|
832
|
+
/** Route API-key policy (carries the per-route cap). */
|
|
833
|
+
policy: require.Policy['apiKey']
|
|
834
|
+
/** Quota bucket the request consumes. */
|
|
835
|
+
scope: string
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
function getApiKeyCredential(c: hono_Context): ApiKeyCredential | false | null {
|
|
840
|
+
const header = c.req.header('tempo-api-key')
|
|
841
|
+
if (header) return { token: header }
|
|
842
|
+
|
|
843
|
+
// `Authorization: Bearer` compatibility fallback. Must not consume or reject
|
|
844
|
+
// `Authorization: Payment` (MPP), which is a distinct credential — a request
|
|
845
|
+
// carrying only a payment scheme falls through to the deprecated lanes below
|
|
846
|
+
// (which find nothing) and resolves to no API-key credential.
|
|
847
|
+
const authorization = c.req.header('authorization')
|
|
848
|
+
if (authorization && !Credential.extractPaymentScheme(authorization)) {
|
|
849
|
+
const match = /^Bearer\s+(.+)$/i.exec(authorization)
|
|
850
|
+
if (!match?.[1]) return false
|
|
851
|
+
return { token: match[1] }
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
// Deprecated fallbacks carried over from the v1 API so existing integrations
|
|
855
|
+
// keep authenticating: the `x-api-key` header and the `key` query parameter.
|
|
856
|
+
const legacyHeader = c.req.header('x-api-key')
|
|
857
|
+
if (legacyHeader) return { token: legacyHeader }
|
|
858
|
+
|
|
859
|
+
const legacyQuery = c.req.query('key')
|
|
860
|
+
if (legacyQuery) return { token: legacyQuery }
|
|
861
|
+
|
|
862
|
+
return null
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
/**
|
|
866
|
+
* Remove the deprecated `key` auth query param from the request URL once it has
|
|
867
|
+
* been read for authentication. Route query schemas are strict
|
|
868
|
+
* (`z.strictObject`), so leaving `key` in the query string would make the
|
|
869
|
+
* downstream validator reject the request as `query_invalid`. Stripping it
|
|
870
|
+
* keeps the legacy `?key=...` auth lane working on strict-query endpoints
|
|
871
|
+
* (e.g. `GET /v1/tokens`).
|
|
872
|
+
*/
|
|
873
|
+
function stripLegacyAuthQuery(c: hono_Context) {
|
|
874
|
+
const url = new URL(c.req.url)
|
|
875
|
+
if (!url.searchParams.has('key')) return
|
|
876
|
+
url.searchParams.delete('key')
|
|
877
|
+
c.req.raw = new Request(url, c.req.raw)
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
/**
|
|
881
|
+
* Reads an explicit `chainId`/`chain_id` query parameter, normalized to a
|
|
882
|
+
* numeric chain id. Returns `undefined` when absent or malformed (a malformed
|
|
883
|
+
* value falls through to the route's own validation). Mirrors the resolution in
|
|
884
|
+
* `App.create`'s unsupported-chain guard.
|
|
885
|
+
*/
|
|
886
|
+
function explicitChainId(c: hono_Context): number | undefined {
|
|
887
|
+
const raw = c.req.query('chainId') ?? c.req.query('chain_id')
|
|
888
|
+
if (raw === undefined) return undefined
|
|
889
|
+
const parsed = Schema.ChainId.safeParse(raw)
|
|
890
|
+
return parsed.success ? parsed.data : undefined
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
function getRequestId(c: hono_Context) {
|
|
894
|
+
const variables = c.var as Record<string, unknown>
|
|
895
|
+
return typeof variables['requestId'] === 'string' ? variables['requestId'] : undefined
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
function hasPaymentCredential(c: hono_Context) {
|
|
899
|
+
const authorization = c.req.header('authorization')
|
|
900
|
+
if (!authorization) return false
|
|
901
|
+
return !!Credential.extractPaymentScheme(authorization)
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
function isPaymentEnabled(auth: Context, policy: require.Policy) {
|
|
905
|
+
return !!auth.mpp && !!policy.mpp
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
function jsonError(c: hono_Context, options: jsonError.Options) {
|
|
909
|
+
const requestId = getRequestId(c)
|
|
910
|
+
// Auth error envelopes embed per-request state and must not be cached.
|
|
911
|
+
c.header('Cache-Control', 'no-store')
|
|
912
|
+
c.set('errorCode', options.code)
|
|
913
|
+
return c.json(
|
|
914
|
+
{
|
|
915
|
+
error: {
|
|
916
|
+
code: options.code,
|
|
917
|
+
message: options.message,
|
|
918
|
+
},
|
|
919
|
+
...(requestId === undefined ? {} : { requestId }),
|
|
920
|
+
},
|
|
921
|
+
options.status,
|
|
922
|
+
)
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
declare namespace jsonError {
|
|
926
|
+
type Options = {
|
|
927
|
+
code: string
|
|
928
|
+
message: string
|
|
929
|
+
status: 400 | 401 | 403 | 429
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
function payer(c: hono_Context) {
|
|
934
|
+
try {
|
|
935
|
+
return Credential.fromRequest(c.req.raw).source
|
|
936
|
+
} catch {
|
|
937
|
+
return undefined
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
function principalFromApiKey(apiKey: ApiKey.ApiKey): Principal {
|
|
942
|
+
return {
|
|
943
|
+
apiKey,
|
|
944
|
+
environment: apiKey.environment,
|
|
945
|
+
id: apiKey.id,
|
|
946
|
+
orgId: apiKey.orgId,
|
|
947
|
+
type: 'api_key',
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
function principalFromPublic(c: hono_Context): Principal {
|
|
952
|
+
const forwarded = c.req.header('x-forwarded-for')?.split(',')[0]?.trim()
|
|
953
|
+
return {
|
|
954
|
+
id:
|
|
955
|
+
c.req.header('cf-connecting-ip') ??
|
|
956
|
+
c.req.header('x-real-ip') ??
|
|
957
|
+
forwarded ??
|
|
958
|
+
c.req.header('x-test-client-ip') ??
|
|
959
|
+
'anonymous',
|
|
960
|
+
type: 'public',
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
async function requirePayment<environment extends Environment>(
|
|
965
|
+
c: hono_Context<environment>,
|
|
966
|
+
next: () => Promise<void>,
|
|
967
|
+
options: PaymentAccess,
|
|
968
|
+
) {
|
|
969
|
+
const middleware = options.auth.mpp?.charge(options.policy.mpp?.charge ?? {})
|
|
970
|
+
if (!middleware)
|
|
971
|
+
return jsonError(c, {
|
|
972
|
+
code: 'payment_required',
|
|
973
|
+
message: 'Payment required',
|
|
974
|
+
status: 429,
|
|
975
|
+
})
|
|
976
|
+
|
|
977
|
+
return middleware(c, async () => {
|
|
978
|
+
const payer_ = payer(c)
|
|
979
|
+
c.set('principal', {
|
|
980
|
+
...options.principal,
|
|
981
|
+
payment: {
|
|
982
|
+
...options.payment,
|
|
983
|
+
...(payer_ === undefined ? {} : { payer: payer_ }),
|
|
984
|
+
},
|
|
985
|
+
})
|
|
986
|
+
await next()
|
|
987
|
+
})
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
function requirePaymentOrRateLimit<environment extends Environment>(
|
|
991
|
+
c: hono_Context<environment>,
|
|
992
|
+
next: () => Promise<void>,
|
|
993
|
+
options: RateLimitAccess,
|
|
994
|
+
) {
|
|
995
|
+
if (isPaymentEnabled(options.auth, options.policy)) return requirePayment(c, next, options)
|
|
996
|
+
|
|
997
|
+
c.header(
|
|
998
|
+
'Retry-After',
|
|
999
|
+
String(Math.max(options.rateLimit.reset - Math.ceil(Date.now() / 1_000), 1)),
|
|
1000
|
+
)
|
|
1001
|
+
return jsonError(c, {
|
|
1002
|
+
code: 'rate_limit_exceeded',
|
|
1003
|
+
message: 'Rate limit exceeded',
|
|
1004
|
+
status: 429,
|
|
1005
|
+
})
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
function setRateLimitHeaders(c: hono_Context, result: RateLimit.Result) {
|
|
1009
|
+
c.header('RateLimit-Limit', String(result.limit))
|
|
1010
|
+
c.header('RateLimit-Remaining', String(result.remaining))
|
|
1011
|
+
c.header('RateLimit-Reset', String(result.reset))
|
|
1012
|
+
}
|