tapimo 0.0.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -16
- package/dist/ApiKey.d.ts +14 -11
- package/dist/ApiKey.d.ts.map +1 -1
- package/dist/ApiKey.js +11 -12
- package/dist/ApiKey.js.map +1 -1
- package/dist/ApiKeys.d.ts +207 -0
- package/dist/ApiKeys.d.ts.map +1 -0
- package/dist/ApiKeys.js +282 -0
- package/dist/ApiKeys.js.map +1 -0
- package/dist/App.d.ts +6771 -3819
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js +145 -317
- package/dist/App.js.map +1 -1
- package/dist/Handler.d.ts +86 -0
- package/dist/Handler.d.ts.map +1 -0
- package/dist/Handler.js +83 -0
- package/dist/Handler.js.map +1 -0
- package/dist/Scope.d.ts +14 -4
- package/dist/Scope.d.ts.map +1 -1
- package/dist/Scope.js +16 -4
- package/dist/Scope.js.map +1 -1
- package/dist/Webhooks.d.ts +5 -7
- package/dist/Webhooks.d.ts.map +1 -1
- package/dist/Webhooks.js +11 -4
- package/dist/Webhooks.js.map +1 -1
- package/dist/admin/Access.d.ts +1 -3
- package/dist/admin/Access.d.ts.map +1 -1
- package/dist/admin/Access.js +1 -3
- package/dist/admin/Access.js.map +1 -1
- package/dist/admin/App.d.ts +167 -205
- package/dist/admin/App.d.ts.map +1 -1
- package/dist/admin/App.js +2 -4
- package/dist/admin/App.js.map +1 -1
- package/dist/admin/apps/api-keys.d.ts +105 -5
- package/dist/admin/apps/api-keys.d.ts.map +1 -1
- package/dist/admin/apps/api-keys.js +75 -9
- package/dist/admin/apps/api-keys.js.map +1 -1
- package/dist/admin/apps/scopes.d.ts +6 -0
- package/dist/admin/apps/scopes.d.ts.map +1 -1
- package/dist/admin/apps/verified-tokens.d.ts +35 -149
- package/dist/admin/apps/verified-tokens.d.ts.map +1 -1
- package/dist/admin/apps/verified-tokens.js +18 -39
- package/dist/admin/apps/verified-tokens.js.map +1 -1
- package/dist/admin/ui.generated.d.ts +1 -1
- package/dist/admin/ui.generated.js +1 -1
- package/dist/apps/data/App.d.ts +19176 -0
- package/dist/apps/data/App.d.ts.map +1 -0
- package/dist/apps/data/App.js +236 -0
- package/dist/apps/data/App.js.map +1 -0
- package/dist/apps/data/index.d.ts +3 -0
- package/dist/apps/data/index.d.ts.map +1 -0
- package/dist/apps/data/index.js +3 -0
- package/dist/apps/data/index.js.map +1 -0
- package/dist/apps/{activities.d.ts → data/routes/activities.d.ts} +32 -32
- package/dist/apps/{activities.d.ts.map → data/routes/activities.d.ts.map} +1 -1
- package/dist/apps/{activities.js → data/routes/activities.js} +13 -13
- package/dist/apps/data/routes/activities.js.map +1 -0
- package/dist/apps/{addresses.d.ts → data/routes/addresses.d.ts} +22 -22
- package/dist/apps/{addresses.d.ts.map → data/routes/addresses.d.ts.map} +1 -1
- package/dist/apps/{addresses.js → data/routes/addresses.js} +5 -5
- package/dist/apps/data/routes/addresses.js.map +1 -0
- package/dist/apps/{balances.d.ts → data/routes/balances.d.ts} +49 -49
- package/dist/apps/data/routes/balances.d.ts.map +1 -0
- package/dist/apps/{balances.js → data/routes/balances.js} +42 -11
- package/dist/apps/data/routes/balances.js.map +1 -0
- package/dist/apps/{blocks.d.ts → data/routes/blocks.d.ts} +47 -47
- package/dist/apps/data/routes/blocks.d.ts.map +1 -0
- package/dist/apps/{blocks.js → data/routes/blocks.js} +13 -13
- package/dist/apps/data/routes/blocks.js.map +1 -0
- package/dist/apps/{coingecko.d.ts → data/routes/coingecko.d.ts} +183 -183
- package/dist/apps/data/routes/coingecko.d.ts.map +1 -0
- package/dist/apps/{coingecko.js → data/routes/coingecko.js} +13 -13
- package/dist/apps/data/routes/coingecko.js.map +1 -0
- package/dist/apps/{exchanges.d.ts → data/routes/exchanges.d.ts} +460 -460
- package/dist/apps/data/routes/exchanges.d.ts.map +1 -0
- package/dist/apps/{exchanges.js → data/routes/exchanges.js} +19 -19
- package/dist/apps/data/routes/exchanges.js.map +1 -0
- package/dist/apps/{fee-amm.d.ts → data/routes/fee-amm.d.ts} +94 -94
- package/dist/apps/data/routes/fee-amm.d.ts.map +1 -0
- package/dist/apps/{fee-amm.js → data/routes/fee-amm.js} +17 -17
- package/dist/apps/data/routes/fee-amm.js.map +1 -0
- package/dist/apps/{indexer.d.ts → data/routes/indexer.d.ts} +4 -4
- package/dist/apps/data/routes/indexer.d.ts.map +1 -0
- package/dist/apps/{indexer.js → data/routes/indexer.js} +13 -9
- package/dist/apps/data/routes/indexer.js.map +1 -0
- package/dist/apps/{receipts.d.ts → data/routes/receipts.d.ts} +6 -6
- package/dist/apps/data/routes/receipts.d.ts.map +1 -0
- package/dist/apps/{receipts.js → data/routes/receipts.js} +12 -12
- package/dist/apps/data/routes/receipts.js.map +1 -0
- package/dist/apps/{rpc.d.ts → data/routes/rpc.d.ts} +4 -4
- package/dist/apps/data/routes/rpc.d.ts.map +1 -0
- package/dist/apps/{rpc.js → data/routes/rpc.js} +6 -6
- package/dist/apps/data/routes/rpc.js.map +1 -0
- package/dist/apps/{tokenlist.d.ts → data/routes/tokenlist.d.ts} +4 -4
- package/dist/apps/data/routes/tokenlist.d.ts.map +1 -0
- package/dist/apps/{tokenlist.js → data/routes/tokenlist.js} +8 -8
- package/dist/apps/data/routes/tokenlist.js.map +1 -0
- package/dist/apps/{tokens.d.ts → data/routes/tokens.d.ts} +11 -11
- package/dist/apps/data/routes/tokens.d.ts.map +1 -0
- package/dist/apps/{tokens.js → data/routes/tokens.js} +19 -19
- package/dist/apps/data/routes/tokens.js.map +1 -0
- package/dist/apps/{transactions.d.ts → data/routes/transactions.d.ts} +288 -288
- package/dist/apps/data/routes/transactions.d.ts.map +1 -0
- package/dist/apps/{transactions.js → data/routes/transactions.js} +13 -13
- package/dist/apps/data/routes/transactions.js.map +1 -0
- package/dist/apps/{transfers.d.ts → data/routes/transfers.d.ts} +35 -35
- package/dist/apps/data/routes/transfers.d.ts.map +1 -0
- package/dist/apps/{transfers.js → data/routes/transfers.js} +13 -13
- package/dist/apps/data/routes/transfers.js.map +1 -0
- package/dist/apps/{verified-tokens.d.ts → data/routes/verified-tokens.d.ts} +42 -42
- package/dist/apps/data/routes/verified-tokens.d.ts.map +1 -0
- package/dist/apps/{verified-tokens.js → data/routes/verified-tokens.js} +9 -9
- package/dist/apps/data/routes/verified-tokens.js.map +1 -0
- package/dist/apps/{webhooks.d.ts → data/routes/webhooks.d.ts} +66 -63
- package/dist/apps/{webhooks.d.ts.map → data/routes/webhooks.d.ts.map} +1 -1
- package/dist/apps/{webhooks.js → data/routes/webhooks.js} +67 -59
- package/dist/apps/data/routes/webhooks.js.map +1 -0
- package/dist/apps/docs/App.d.ts +27 -0
- package/dist/apps/docs/App.d.ts.map +1 -0
- package/dist/apps/docs/App.js +122 -0
- package/dist/apps/docs/App.js.map +1 -0
- package/dist/{internal/OpenApi.fonts.d.ts → apps/docs/fonts.d.ts} +1 -1
- package/dist/apps/docs/fonts.d.ts.map +1 -0
- package/dist/{internal/OpenApi.fonts.js → apps/docs/fonts.js} +1 -1
- package/dist/apps/docs/fonts.js.map +1 -0
- package/dist/apps/docs/index.d.ts +2 -0
- package/dist/apps/docs/index.d.ts.map +1 -0
- package/dist/apps/docs/index.js +2 -0
- package/dist/apps/docs/index.js.map +1 -0
- package/dist/{internal/OpenApi.logo.d.ts → apps/docs/logo.d.ts} +1 -1
- package/dist/apps/docs/logo.d.ts.map +1 -0
- package/dist/{internal/OpenApi.logo.js → apps/docs/logo.js} +1 -1
- package/dist/apps/docs/logo.js.map +1 -0
- package/dist/apps/index.d.ts +4 -0
- package/dist/apps/index.d.ts.map +1 -0
- package/dist/apps/index.js +4 -0
- package/dist/apps/index.js.map +1 -0
- package/dist/apps/metadata.d.ts +7 -0
- package/dist/apps/metadata.d.ts.map +1 -0
- package/dist/apps/metadata.js +15 -0
- package/dist/apps/metadata.js.map +1 -0
- package/dist/apps/platform/App.d.ts +2985 -0
- package/dist/apps/platform/App.d.ts.map +1 -0
- package/dist/apps/platform/App.js +74 -0
- package/dist/apps/platform/App.js.map +1 -0
- package/dist/apps/platform/index.d.ts +2 -0
- package/dist/apps/platform/index.d.ts.map +1 -0
- package/dist/apps/platform/index.js +2 -0
- package/dist/apps/platform/index.js.map +1 -0
- package/dist/apps/platform/routes/api-keys.d.ts +681 -0
- package/dist/apps/platform/routes/api-keys.d.ts.map +1 -0
- package/dist/apps/platform/routes/api-keys.js +328 -0
- package/dist/apps/platform/routes/api-keys.js.map +1 -0
- package/dist/apps/platform/routes/invitations.d.ts +733 -0
- package/dist/apps/platform/routes/invitations.d.ts.map +1 -0
- package/dist/apps/platform/routes/invitations.js +413 -0
- package/dist/apps/platform/routes/invitations.js.map +1 -0
- package/dist/apps/platform/routes/me.d.ts +95 -0
- package/dist/apps/platform/routes/me.d.ts.map +1 -0
- package/dist/apps/platform/routes/me.js +81 -0
- package/dist/apps/platform/routes/me.js.map +1 -0
- package/dist/apps/platform/routes/members.d.ts +554 -0
- package/dist/apps/platform/routes/members.d.ts.map +1 -0
- package/dist/apps/platform/routes/members.js +251 -0
- package/dist/apps/platform/routes/members.js.map +1 -0
- package/dist/apps/platform/routes/orgs.d.ts +582 -0
- package/dist/apps/platform/routes/orgs.d.ts.map +1 -0
- package/dist/apps/platform/routes/orgs.js +265 -0
- package/dist/apps/platform/routes/orgs.js.map +1 -0
- package/dist/apps/platform/routes/projects.d.ts +740 -0
- package/dist/apps/platform/routes/projects.d.ts.map +1 -0
- package/dist/apps/platform/routes/projects.js +286 -0
- package/dist/apps/platform/routes/projects.js.map +1 -0
- package/dist/apps/relay/App.d.ts +41 -0
- package/dist/apps/relay/App.d.ts.map +1 -0
- package/dist/apps/relay/App.js +103 -0
- package/dist/apps/relay/App.js.map +1 -0
- package/dist/apps/relay/Sponsorships.d.ts +107 -0
- package/dist/apps/relay/Sponsorships.d.ts.map +1 -0
- package/dist/apps/relay/Sponsorships.js +193 -0
- package/dist/apps/relay/Sponsorships.js.map +1 -0
- package/dist/apps/relay/index.d.ts +3 -0
- package/dist/apps/relay/index.d.ts.map +1 -0
- package/dist/apps/relay/index.js +3 -0
- package/dist/apps/relay/index.js.map +1 -0
- package/dist/{Cli.d.ts → cli/Cli.d.ts} +13 -2
- package/dist/cli/Cli.d.ts.map +1 -0
- package/dist/cli/Cli.js +19 -0
- package/dist/cli/Cli.js.map +1 -0
- package/dist/cli/bin.d.ts +3 -0
- package/dist/cli/bin.d.ts.map +1 -0
- package/dist/cli/bin.js +4 -0
- package/dist/cli/bin.js.map +1 -0
- package/dist/cli/commands/admin.d.ts +15 -0
- package/dist/cli/commands/admin.d.ts.map +1 -0
- package/dist/cli/commands/admin.js +60 -0
- package/dist/cli/commands/admin.js.map +1 -0
- package/dist/cloudflare.d.ts +7 -0
- package/dist/cloudflare.d.ts.map +1 -1
- package/dist/cloudflare.js +9 -0
- package/dist/cloudflare.js.map +1 -1
- package/dist/db/Db.d.ts +113 -0
- package/dist/db/Db.d.ts.map +1 -0
- package/dist/db/Db.js +402 -0
- package/dist/db/Db.js.map +1 -0
- package/dist/db/Schema.d.ts +168 -0
- package/dist/db/Schema.d.ts.map +1 -0
- package/dist/db/Schema.js +229 -0
- package/dist/db/Schema.js.map +1 -0
- package/dist/db/tables/invitations.d.ts +84 -0
- package/dist/db/tables/invitations.d.ts.map +1 -0
- package/dist/db/tables/invitations.js +165 -0
- package/dist/db/tables/invitations.js.map +1 -0
- package/dist/db/tables/memberships.d.ts +108 -0
- package/dist/db/tables/memberships.d.ts.map +1 -0
- package/dist/db/tables/memberships.js +153 -0
- package/dist/db/tables/memberships.js.map +1 -0
- package/dist/db/tables/organizations.d.ts +123 -0
- package/dist/db/tables/organizations.d.ts.map +1 -0
- package/dist/db/tables/organizations.js +170 -0
- package/dist/db/tables/organizations.js.map +1 -0
- package/dist/db/tables/projects.d.ts +65 -0
- package/dist/db/tables/projects.d.ts.map +1 -0
- package/dist/db/tables/projects.js +75 -0
- package/dist/db/tables/projects.js.map +1 -0
- package/dist/db/tables/sponsoredTransactions.d.ts +120 -0
- package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -0
- package/dist/db/tables/sponsoredTransactions.js +161 -0
- package/dist/db/tables/sponsoredTransactions.js.map +1 -0
- package/dist/db/tables/users.d.ts +48 -0
- package/dist/db/tables/users.d.ts.map +1 -0
- package/dist/db/tables/users.js +65 -0
- package/dist/db/tables/users.js.map +1 -0
- package/dist/db/tables/verifiedTokens.d.ts +53 -0
- package/dist/db/tables/verifiedTokens.d.ts.map +1 -0
- package/dist/db/tables/verifiedTokens.js +74 -0
- package/dist/db/tables/verifiedTokens.js.map +1 -0
- package/dist/db/tables/webhookDeliveries.d.ts +71 -0
- package/dist/db/tables/webhookDeliveries.d.ts.map +1 -0
- package/dist/db/tables/webhookDeliveries.js +119 -0
- package/dist/db/tables/webhookDeliveries.js.map +1 -0
- package/dist/db/tables/webhookSubscriptions.d.ts +158 -0
- package/dist/db/tables/webhookSubscriptions.d.ts.map +1 -0
- package/dist/db/tables/webhookSubscriptions.js +272 -0
- package/dist/db/tables/webhookSubscriptions.js.map +1 -0
- package/dist/handlers/internal/executionError.d.ts +25 -0
- package/dist/handlers/internal/executionError.d.ts.map +1 -0
- package/dist/handlers/internal/executionError.js +186 -0
- package/dist/handlers/internal/executionError.js.map +1 -0
- package/dist/handlers/internal/multisig.d.ts +233 -0
- package/dist/handlers/internal/multisig.d.ts.map +1 -0
- package/dist/handlers/internal/multisig.js +542 -0
- package/dist/handlers/internal/multisig.js.map +1 -0
- package/dist/handlers/internal/requestListener.d.ts +124 -0
- package/dist/handlers/internal/requestListener.d.ts.map +1 -0
- package/dist/handlers/internal/requestListener.js +173 -0
- package/dist/handlers/internal/requestListener.js.map +1 -0
- package/dist/handlers/internal/sponsorship.d.ts +113 -0
- package/dist/handlers/internal/sponsorship.d.ts.map +1 -0
- package/dist/handlers/internal/sponsorship.js +128 -0
- package/dist/handlers/internal/sponsorship.js.map +1 -0
- package/dist/handlers/internal/types.d.ts +284 -0
- package/dist/handlers/internal/types.d.ts.map +1 -0
- package/dist/handlers/internal/types.js +2 -0
- package/dist/handlers/internal/types.js.map +1 -0
- package/dist/handlers/internal/utils.d.ts +34 -0
- package/dist/handlers/internal/utils.d.ts.map +1 -0
- package/dist/handlers/internal/utils.js +131 -0
- package/dist/handlers/internal/utils.js.map +1 -0
- package/dist/handlers/relay.d.ts +123 -0
- package/dist/handlers/relay.d.ts.map +1 -0
- package/dist/handlers/relay.js +1278 -0
- package/dist/handlers/relay.js.map +1 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/internal/Auth.d.ts +224 -16
- package/dist/internal/Auth.d.ts.map +1 -1
- package/dist/internal/Auth.js +572 -33
- package/dist/internal/Auth.js.map +1 -1
- package/dist/internal/Log.d.ts +7 -3
- package/dist/internal/Log.d.ts.map +1 -1
- package/dist/internal/Log.js +4 -0
- package/dist/internal/Log.js.map +1 -1
- package/dist/internal/OpenApi.d.ts +0 -19
- package/dist/internal/OpenApi.d.ts.map +1 -1
- package/dist/internal/OpenApi.js +0 -57
- package/dist/internal/OpenApi.js.map +1 -1
- package/dist/internal/RpcSchema.d.ts +10 -0
- package/dist/internal/RpcSchema.d.ts.map +1 -0
- package/dist/internal/RpcSchema.js +2 -0
- package/dist/internal/RpcSchema.js.map +1 -0
- package/dist/internal/Store.d.ts +49 -4
- package/dist/internal/Store.d.ts.map +1 -1
- package/dist/internal/Store.js +81 -5
- package/dist/internal/Store.js.map +1 -1
- package/dist/internal/VerifiedTokens.d.ts +15 -34
- package/dist/internal/VerifiedTokens.d.ts.map +1 -1
- package/dist/internal/VerifiedTokens.js +115 -159
- package/dist/internal/VerifiedTokens.js.map +1 -1
- package/dist/internal/Viem.d.ts +84 -0
- package/dist/internal/Viem.d.ts.map +1 -1
- package/dist/internal/Webhooks.d.ts +50 -36
- package/dist/internal/Webhooks.d.ts.map +1 -1
- package/dist/internal/Webhooks.js +92 -205
- package/dist/internal/Webhooks.js.map +1 -1
- package/package.json +37 -6
- package/src/ApiKey.test.ts +3 -3
- package/src/ApiKey.ts +11 -12
- package/src/ApiKeys.test.ts +278 -0
- package/src/ApiKeys.ts +358 -0
- package/src/App.test-d.ts +1 -1
- package/src/App.test.ts +40508 -19083
- package/src/App.ts +239 -416
- package/src/Client.test-d.ts +307 -16
- package/src/Handler.ts +154 -0
- package/src/Scope.ts +17 -4
- package/src/Webhooks.test.ts +8 -7
- package/src/Webhooks.ts +13 -7
- package/src/admin/Access.ts +1 -3
- package/src/admin/App.ts +19 -24
- package/src/admin/apps/api-keys.test.ts +70 -18
- package/src/admin/apps/api-keys.ts +92 -9
- package/src/admin/apps/scopes.test.ts +8 -0
- package/src/admin/apps/verified-tokens.test.ts +3 -30
- package/src/admin/apps/verified-tokens.ts +18 -37
- package/src/admin/ui.generated.ts +1 -1
- package/src/apps/composition.test.ts +66 -0
- package/src/apps/data/App.ts +295 -0
- package/src/apps/data/index.ts +2 -0
- package/src/apps/{activities.test.ts → data/routes/activities.test.ts} +3 -3
- package/src/apps/{activities.ts → data/routes/activities.ts} +15 -15
- package/src/apps/{addresses.ts → data/routes/addresses.ts} +6 -6
- package/src/apps/{balances.test.ts → data/routes/balances.test.ts} +6 -5
- package/src/apps/{balances.ts → data/routes/balances.ts} +46 -12
- package/src/apps/{blocks.test.ts → data/routes/blocks.test.ts} +4 -4
- package/src/apps/{blocks.ts → data/routes/blocks.ts} +14 -14
- package/src/apps/{coingecko.test.ts → data/routes/coingecko.test.ts} +2 -2
- package/src/apps/{coingecko.ts → data/routes/coingecko.ts} +14 -14
- package/src/apps/{exchanges.test.ts → data/routes/exchanges.test.ts} +2 -2
- package/src/apps/{exchanges.ts → data/routes/exchanges.ts} +20 -20
- package/src/apps/{fee-amm.test.ts → data/routes/fee-amm.test.ts} +3 -3
- package/src/apps/{fee-amm.ts → data/routes/fee-amm.ts} +18 -18
- package/src/apps/{indexer.test.ts → data/routes/indexer.test.ts} +27 -6
- package/src/apps/{indexer.ts → data/routes/indexer.ts} +13 -10
- package/src/apps/{receipts.test.ts → data/routes/receipts.test.ts} +4 -4
- package/src/apps/{receipts.ts → data/routes/receipts.ts} +13 -13
- package/src/apps/{rpc.test.ts → data/routes/rpc.test.ts} +142 -13
- package/src/apps/{rpc.ts → data/routes/rpc.ts} +7 -7
- package/src/apps/{tokenlist.test.ts → data/routes/tokenlist.test.ts} +8 -10
- package/src/apps/{tokenlist.ts → data/routes/tokenlist.ts} +9 -9
- package/src/apps/{tokens.test.ts → data/routes/tokens.test.ts} +6 -5
- package/src/apps/{tokens.ts → data/routes/tokens.ts} +20 -20
- package/src/apps/{transactions.test.ts → data/routes/transactions.test.ts} +4 -4
- package/src/apps/{transactions.ts → data/routes/transactions.ts} +15 -15
- package/src/apps/{transfers.test.ts → data/routes/transfers.test.ts} +3 -3
- package/src/apps/{transfers.ts → data/routes/transfers.ts} +15 -15
- package/src/apps/{verified-tokens.test.ts → data/routes/verified-tokens.test.ts} +27 -15
- package/src/apps/{verified-tokens.ts → data/routes/verified-tokens.ts} +10 -10
- package/src/apps/{webhooks.test.ts → data/routes/webhooks.test.ts} +137 -150
- package/src/apps/{webhooks.ts → data/routes/webhooks.ts} +83 -81
- package/src/apps/docs/App.ts +136 -0
- package/src/apps/docs/index.ts +1 -0
- package/src/apps/index.ts +3 -0
- package/src/apps/metadata.ts +23 -0
- package/src/apps/platform/App.ts +130 -0
- package/src/apps/platform/index.ts +1 -0
- package/src/apps/platform/routes/api-keys.test.ts +330 -0
- package/src/apps/platform/routes/api-keys.ts +427 -0
- package/src/apps/platform/routes/invitations.test.ts +361 -0
- package/src/apps/platform/routes/invitations.ts +519 -0
- package/src/apps/platform/routes/me.test.ts +121 -0
- package/src/apps/platform/routes/me.ts +106 -0
- package/src/apps/platform/routes/members.test.ts +195 -0
- package/src/apps/platform/routes/members.ts +313 -0
- package/src/apps/platform/routes/orgs.test.ts +313 -0
- package/src/apps/platform/routes/orgs.ts +343 -0
- package/src/apps/platform/routes/projects.test.ts +181 -0
- package/src/apps/platform/routes/projects.ts +367 -0
- package/src/apps/relay/App.test.ts +484 -0
- package/src/apps/relay/App.ts +128 -0
- package/src/apps/relay/Sponsorships.test.ts +375 -0
- package/src/apps/relay/Sponsorships.ts +305 -0
- package/src/apps/relay/index.ts +2 -0
- package/src/{Cli.test.ts → cli/Cli.test.ts} +10 -9
- package/src/{Cli.ts → cli/Cli.ts} +16 -9
- package/src/cli/bin.ts +4 -0
- package/src/cli/commands/admin.test.ts +71 -0
- package/src/cli/commands/admin.ts +60 -0
- package/src/cloudflare.ts +10 -0
- package/src/db/Db.test.ts +84 -0
- package/src/db/Db.ts +542 -0
- package/src/db/Schema.ts +301 -0
- package/src/db/tables/invitations.ts +198 -0
- package/src/db/tables/memberships.ts +208 -0
- package/src/db/tables/organizations.test.ts +160 -0
- package/src/db/tables/organizations.ts +232 -0
- package/src/db/tables/projects.ts +107 -0
- package/src/db/tables/sponsoredTransactions.test.ts +218 -0
- package/src/db/tables/sponsoredTransactions.ts +236 -0
- package/src/db/tables/users.ts +85 -0
- package/src/db/tables/verifiedTokens.ts +111 -0
- package/src/db/tables/webhookDeliveries.ts +159 -0
- package/src/db/tables/webhookSubscriptions.ts +358 -0
- package/src/handlers/internal/executionError.ts +207 -0
- package/src/handlers/internal/multisig.test.ts +818 -0
- package/src/handlers/internal/multisig.ts +823 -0
- package/src/handlers/internal/requestListener.ts +273 -0
- package/src/handlers/internal/sponsorship.ts +245 -0
- package/src/handlers/internal/types.ts +378 -0
- package/src/handlers/internal/utils.ts +153 -0
- package/src/handlers/relay.test.ts +2035 -0
- package/src/handlers/relay.ts +1623 -0
- package/src/index.ts +4 -2
- package/src/internal/Auth.test.ts +337 -21
- package/src/internal/Auth.ts +783 -44
- package/src/internal/Cursor.test.ts +3 -2
- package/src/internal/Log.test.ts +1 -1
- package/src/internal/Log.ts +11 -3
- package/src/internal/MetricSink.ts +1 -1
- package/src/internal/OpenApi.ts +0 -59
- package/src/internal/RateLimit.test.ts +1 -1
- package/src/internal/RpcSchema.ts +10 -0
- package/src/internal/Store.test.ts +150 -10
- package/src/internal/Store.ts +136 -11
- package/src/internal/Tidx.local.test.ts +1 -1
- package/src/internal/VerifiedTokens.test.ts +99 -115
- package/src/internal/VerifiedTokens.ts +123 -175
- package/src/internal/WebhookDestination.test.ts +1 -1
- package/src/internal/Webhooks.test.ts +111 -111
- package/src/internal/Webhooks.ts +103 -221
- package/dist/ApiKeySource.d.ts +0 -166
- package/dist/ApiKeySource.d.ts.map +0 -1
- package/dist/ApiKeySource.js +0 -234
- package/dist/ApiKeySource.js.map +0 -1
- package/dist/Cli.d.ts.map +0 -1
- package/dist/Cli.js +0 -13
- package/dist/Cli.js.map +0 -1
- package/dist/apps/activities.js.map +0 -1
- package/dist/apps/addresses.js.map +0 -1
- package/dist/apps/balances.d.ts.map +0 -1
- package/dist/apps/balances.js.map +0 -1
- package/dist/apps/blocks.d.ts.map +0 -1
- package/dist/apps/blocks.js.map +0 -1
- package/dist/apps/coingecko.d.ts.map +0 -1
- package/dist/apps/coingecko.js.map +0 -1
- package/dist/apps/exchanges.d.ts.map +0 -1
- package/dist/apps/exchanges.js.map +0 -1
- package/dist/apps/fee-amm.d.ts.map +0 -1
- package/dist/apps/fee-amm.js.map +0 -1
- package/dist/apps/indexer.d.ts.map +0 -1
- package/dist/apps/indexer.js.map +0 -1
- package/dist/apps/receipts.d.ts.map +0 -1
- package/dist/apps/receipts.js.map +0 -1
- package/dist/apps/rpc.d.ts.map +0 -1
- package/dist/apps/rpc.js.map +0 -1
- package/dist/apps/tokenlist.d.ts.map +0 -1
- package/dist/apps/tokenlist.js.map +0 -1
- package/dist/apps/tokens.d.ts.map +0 -1
- package/dist/apps/tokens.js.map +0 -1
- package/dist/apps/transactions.d.ts.map +0 -1
- package/dist/apps/transactions.js.map +0 -1
- package/dist/apps/transfers.d.ts.map +0 -1
- package/dist/apps/transfers.js.map +0 -1
- package/dist/apps/verified-tokens.d.ts.map +0 -1
- package/dist/apps/verified-tokens.js.map +0 -1
- package/dist/apps/webhooks.js.map +0 -1
- package/dist/internal/OpenApi.fonts.d.ts.map +0 -1
- package/dist/internal/OpenApi.fonts.js.map +0 -1
- package/dist/internal/OpenApi.logo.d.ts.map +0 -1
- package/dist/internal/OpenApi.logo.js.map +0 -1
- package/seeds/31318/icons/0x20c0000000000000000000000000000000000000.svg +0 -1
- package/seeds/31318/icons/0x20c0000000000000000000000000000000000001.svg +0 -1
- package/seeds/31318/icons/0x20c0000000000000000000000000000000000002.svg +0 -1
- package/seeds/31318/icons/0x20c0000000000000000000000000000000000003.svg +0 -1
- package/seeds/31318/verified-tokens.json +0 -30
- package/seeds/4217/icons/0x20c0000000000000000000000000000000000000.svg +0 -25
- package/seeds/4217/icons/0x20c000000000000000000000048c8f36df1c9a4a.svg +0 -1
- package/seeds/4217/icons/0x20c0000000000000000000000520792dcccccccc.svg +0 -1
- package/seeds/4217/icons/0x20c0000000000000000000000a6da882d075a4c3.svg +0 -4
- package/seeds/4217/icons/0x20c000000000000000000000111111111e910f0f.svg +0 -30
- package/seeds/4217/icons/0x20c00000000000000000000014f22ca97301eb73.svg +0 -1
- package/seeds/4217/icons/0x20c0000000000000000000001621e21f71cf12fb.svg +0 -1
- package/seeds/4217/icons/0x20c0000000000000000000002f52d5cc21a3207b.svg +0 -1
- package/seeds/4217/icons/0x20c0000000000000000000003158081efd85bfc2.svg +0 -11
- package/seeds/4217/icons/0x20c0000000000000000000003554d28269e0f3c2.svg +0 -1
- package/seeds/4217/icons/0x20c000000000000000000000383a23bacb546ab9.svg +0 -1
- package/seeds/4217/icons/0x20c00000000000000000000042109aef2f8b28e1.png +0 -0
- package/seeds/4217/icons/0x20c0000000000000000000005c0bac7cef389a11.svg +0 -1
- package/seeds/4217/icons/0x20c0000000000000000000006fd9a167923ba194.png +0 -0
- package/seeds/4217/icons/0x20c0000000000000000000007f7ba549dd0251b9.svg +0 -1
- package/seeds/4217/icons/0x20c0000000000000000000008191667423f70e67.svg +0 -5
- package/seeds/4217/icons/0x20c0000000000000000000008ee4fcff88888888.svg +0 -1
- package/seeds/4217/icons/0x20c0000000000000000000009a4a4b17e0dc6651.svg +0 -1
- package/seeds/4217/icons/0x20c000000000000000000000ab02d39df30bd17e.svg +0 -1
- package/seeds/4217/icons/0x20c000000000000000000000ae247a1130450f09.svg +0 -1
- package/seeds/4217/icons/0x20c000000000000000000000aeed2ec36a54d0e5.svg +0 -1
- package/seeds/4217/icons/0x20c000000000000000000000b9537d11c60e8b50.svg +0 -1
- package/seeds/4217/icons/0x20c000000000000000000000bd95bfb69fbe6ce3.svg +0 -1
- package/seeds/4217/icons/0x20c000000000000000000000c412ec89d0c08be5.svg +0 -1
- package/seeds/4217/icons/0x20c000000000000000000000f047dd7018e50367.png +0 -0
- package/seeds/4217/verified-tokens.json +0 -177
- package/seeds/42431/icons/0x20c0000000000000000000000000000000000000.svg +0 -25
- package/seeds/42431/icons/0x20c0000000000000000000000000000000000001.svg +0 -1
- package/seeds/42431/icons/0x20c0000000000000000000000000000000000002.svg +0 -1
- package/seeds/42431/icons/0x20c0000000000000000000000000000000000003.svg +0 -1
- package/seeds/42431/icons/0x20c0000000000000000000009e8d7eb59b783726.svg +0 -1
- package/seeds/42431/icons/0x20c000000000000000000000d72572838bbee59c.svg +0 -1
- package/seeds/42431/verified-tokens.json +0 -44
- package/seeds/openrpc/README.md +0 -27
- package/seeds/openrpc/execution-apis.json +0 -8989
- package/src/ApiKeySource.test-d.ts +0 -11
- package/src/ApiKeySource.test.ts +0 -295
- package/src/ApiKeySource.ts +0 -303
- /package/src/{internal/OpenApi.fonts.ts → apps/docs/fonts.ts} +0 -0
- /package/src/{internal/OpenApi.logo.ts → apps/docs/logo.ts} +0 -0
package/src/internal/Auth.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as Accounts from 'accounts/server'
|
|
2
|
+
import { Hono } from 'hono'
|
|
3
|
+
import type { Context as hono_Context, MiddlewareHandler } from 'hono'
|
|
2
4
|
import type { HonoBase } from 'hono/hono-base'
|
|
3
5
|
import { createMiddleware } from 'hono/factory'
|
|
4
6
|
import { matchedRoutes } from 'hono/route'
|
|
@@ -6,14 +8,23 @@ import type { MergePath, RouterRoute, Schema as core_Schema } from 'hono/types'
|
|
|
6
8
|
import { findTargetHandler } from 'hono/utils/handler'
|
|
7
9
|
import { Credential, type Method } from 'mppx'
|
|
8
10
|
import { Mppx, tempo } from 'mppx/hono'
|
|
11
|
+
import * as z from 'zod/mini'
|
|
9
12
|
|
|
10
|
-
import * as
|
|
13
|
+
import type * as App from '../App.js'
|
|
11
14
|
import * as ApiKey from '../ApiKey.js'
|
|
15
|
+
import * as ApiKeys from '../ApiKeys.js'
|
|
16
|
+
import * as Db from '../db/Db.js'
|
|
17
|
+
import type * as Store from './Store.js'
|
|
18
|
+
import * as Memberships from '../db/tables/memberships.js'
|
|
19
|
+
import * as Metadata from '../apps/metadata.js'
|
|
12
20
|
import * as OpenApi from './OpenApi.js'
|
|
21
|
+
import * as Organizations from '../db/tables/organizations.js'
|
|
22
|
+
import * as Projects from '../db/tables/projects.js'
|
|
13
23
|
import * as RateLimit from './RateLimit.js'
|
|
14
24
|
import * as Response from './Response.js'
|
|
15
25
|
import * as Schema from './Schema.js'
|
|
16
26
|
import * as Scope from '../Scope.js'
|
|
27
|
+
import * as Users from '../db/tables/users.js'
|
|
17
28
|
import * as Viem from './Viem.js'
|
|
18
29
|
|
|
19
30
|
const policySymbol = Symbol('tempo-api.auth.policy')
|
|
@@ -29,14 +40,18 @@ export type Context = {
|
|
|
29
40
|
* per-key {@link ApiKey.ApiKey.rateLimits} shape.
|
|
30
41
|
*/
|
|
31
42
|
rateLimits?: Record<string, RateLimit.Limit> | undefined
|
|
32
|
-
/**
|
|
33
|
-
|
|
43
|
+
/** Resolves a presented token against the app's KV store on `c`. */
|
|
44
|
+
resolve: (c: hono_Context, token: string) => Promise<ApiKey.ApiKey | null>
|
|
34
45
|
}
|
|
35
46
|
| undefined
|
|
36
47
|
/** MPP payment configuration. */
|
|
37
48
|
mpp?: Mpp | undefined
|
|
38
49
|
/** Rate-limit store used by API handlers. */
|
|
39
50
|
rateLimit?: RateLimit.Store | undefined
|
|
51
|
+
/** Session lane resolution (the session surface's capability), or undefined when sessions are unmounted. */
|
|
52
|
+
session?: Session | undefined
|
|
53
|
+
/** Super-admin machine credential (sha256 of the configured secret), or undefined when disabled. */
|
|
54
|
+
superAdmin?: { tokenHash: string } | undefined
|
|
40
55
|
}
|
|
41
56
|
|
|
42
57
|
/** Hono environment variables set by auth middleware. */
|
|
@@ -86,9 +101,11 @@ type Mpp = {
|
|
|
86
101
|
/**
|
|
87
102
|
* Route-level auth policy override. Each lane is independent: `true` enables it
|
|
88
103
|
* with the middleware defaults, `false` disables it, an object enables it with
|
|
89
|
-
* the given overrides, and omitting it inherits the resolved policy.
|
|
90
|
-
* default-closed
|
|
91
|
-
*
|
|
104
|
+
* the given overrides, and omitting it inherits the resolved policy. All lanes
|
|
105
|
+
* are default-closed (see {@link middleware}).
|
|
106
|
+
*
|
|
107
|
+
* Routes grant API-key, public, or paid access explicitly via {@link policy};
|
|
108
|
+
* otherwise only `super_admin` reaches them.
|
|
92
109
|
*/
|
|
93
110
|
export type PolicyOverride = {
|
|
94
111
|
/** API-key lane: pass quota/scopes overrides, or a boolean to enable/disable. */
|
|
@@ -117,6 +134,8 @@ export type PolicyOverride = {
|
|
|
117
134
|
}
|
|
118
135
|
| boolean
|
|
119
136
|
| undefined
|
|
137
|
+
/** Session lane: `true` allows signed-in wallet sessions, `false` disables. */
|
|
138
|
+
session?: boolean | undefined
|
|
120
139
|
}
|
|
121
140
|
|
|
122
141
|
/** Authenticated or anonymous caller. */
|
|
@@ -132,6 +151,8 @@ export type Principal =
|
|
|
132
151
|
orgId: string
|
|
133
152
|
/** Payment metadata for paid overflow requests. */
|
|
134
153
|
payment?: Payment | undefined
|
|
154
|
+
/** Owning project id, when the key carries project attribution. */
|
|
155
|
+
projectId?: string | undefined
|
|
135
156
|
/** Principal kind. */
|
|
136
157
|
type: 'api_key'
|
|
137
158
|
}
|
|
@@ -143,13 +164,46 @@ export type Principal =
|
|
|
143
164
|
/** Principal kind. */
|
|
144
165
|
type: 'public'
|
|
145
166
|
}
|
|
167
|
+
| {
|
|
168
|
+
/** Verified email bound to the session, when present. */
|
|
169
|
+
email?: string | undefined
|
|
170
|
+
/** Principal id: the user id (`usr_…`). */
|
|
171
|
+
id: string
|
|
172
|
+
/** Identity that established the session, as a provider/subject pair (OIDC `iss`/`sub` semantics). */
|
|
173
|
+
identity: {
|
|
174
|
+
/** Identity provider: `wallet` for SIWE sign-in, an OIDC issuer otherwise. */
|
|
175
|
+
provider: string
|
|
176
|
+
/** Provider-scoped subject — the wallet address for `wallet`. */
|
|
177
|
+
subject: string
|
|
178
|
+
}
|
|
179
|
+
/** Payment metadata for paid overflow requests. */
|
|
180
|
+
payment?: Payment | undefined
|
|
181
|
+
/** Principal kind. */
|
|
182
|
+
type: 'session'
|
|
183
|
+
}
|
|
184
|
+
| {
|
|
185
|
+
/** Attributed super admin identity (`'super_admin'` for the configured machine secret). */
|
|
186
|
+
actor: string
|
|
187
|
+
/** Principal id. */
|
|
188
|
+
id: string
|
|
189
|
+
/** Payment metadata for paid overflow requests. */
|
|
190
|
+
payment?: Payment | undefined
|
|
191
|
+
/** Principal kind. */
|
|
192
|
+
type: 'super_admin'
|
|
193
|
+
}
|
|
146
194
|
|
|
147
195
|
/** Hono variables set by auth middleware. */
|
|
148
196
|
export type Variables = {
|
|
149
197
|
/** Auth context consumed by protected handlers. */
|
|
150
198
|
auth: Context
|
|
199
|
+
/** Caller's membership resolved by {@link ensureOrg}; absent for `super_admin`. */
|
|
200
|
+
membership?: Memberships.Record | undefined
|
|
201
|
+
/** Organization resolved by {@link ensureOrg} for the current request. */
|
|
202
|
+
org?: Organizations.Record | undefined
|
|
151
203
|
/** Authenticated or anonymous caller. */
|
|
152
204
|
principal?: Principal | undefined
|
|
205
|
+
/** Project resolved by {@link ensureProject} for the current request. */
|
|
206
|
+
project?: Projects.Record | undefined
|
|
153
207
|
}
|
|
154
208
|
|
|
155
209
|
/** Reads the authenticated principal from a Hono context. */
|
|
@@ -157,6 +211,147 @@ export function getPrincipal<environment extends Environment>(c: hono_Context<en
|
|
|
157
211
|
return c.get('principal') ?? null
|
|
158
212
|
}
|
|
159
213
|
|
|
214
|
+
/**
|
|
215
|
+
* Guards org-scoped routes (`:orgId`). Sessions reach only organizations they
|
|
216
|
+
* are members of; the super admin reaches any. Missing or foreign
|
|
217
|
+
* organizations return `404`, so foreign org ids are indistinguishable from
|
|
218
|
+
* absent ones; a member below the required role gets `403` (the resource is
|
|
219
|
+
* visible to them, the action is not). The resolved organization and
|
|
220
|
+
* membership land on context — read them with {@link org} and
|
|
221
|
+
* {@link membership}.
|
|
222
|
+
*/
|
|
223
|
+
export function ensureOrg(options: ensureOrg.Options = {}): MiddlewareHandler {
|
|
224
|
+
const { role: minimum = 'member' } = options
|
|
225
|
+
// Typed loosely (like {@link policy}) so route chains keep their inferred
|
|
226
|
+
// schema; a concrete environment parameter collapses Hono's inference.
|
|
227
|
+
return createMiddleware<Environment>(async (c, next) => {
|
|
228
|
+
const notFound = () =>
|
|
229
|
+
Response.error(c, {
|
|
230
|
+
code: 'organization_not_found',
|
|
231
|
+
message: 'Organization not found',
|
|
232
|
+
status: 404,
|
|
233
|
+
})
|
|
234
|
+
const orgId = c.req.param('orgId' as never) as string | undefined
|
|
235
|
+
if (!orgId) return notFound()
|
|
236
|
+
const principal = getPrincipal(c)
|
|
237
|
+
if (principal?.type !== 'session' && principal?.type !== 'super_admin') return notFound()
|
|
238
|
+
const db = Db.get(contextDb(c))
|
|
239
|
+
const record = await Organizations.get(db, orgId)
|
|
240
|
+
if (!record) return notFound()
|
|
241
|
+
if (principal.type !== 'super_admin') {
|
|
242
|
+
const member = await Memberships.get(db, orgId, principal.id)
|
|
243
|
+
if (!member) return notFound()
|
|
244
|
+
if (Memberships.rank[member.role] < Memberships.rank[minimum])
|
|
245
|
+
return Response.error(c, {
|
|
246
|
+
code: 'forbidden',
|
|
247
|
+
message: `Requires the ${minimum} role`,
|
|
248
|
+
status: 403,
|
|
249
|
+
})
|
|
250
|
+
c.set('membership', member)
|
|
251
|
+
}
|
|
252
|
+
c.set('org', record)
|
|
253
|
+
await next()
|
|
254
|
+
return undefined
|
|
255
|
+
})
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export declare namespace ensureOrg {
|
|
259
|
+
/** Options for the org guard. */
|
|
260
|
+
type Options = {
|
|
261
|
+
/** Minimum role required; defaults to `member` (any membership). */
|
|
262
|
+
role?: Memberships.Role | undefined
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Guards project-scoped routes (`:projectId`) nested under {@link ensureOrg},
|
|
268
|
+
* which must run first. Projects resolve only within the scoped organization;
|
|
269
|
+
* missing or cross-org projects return `404`. The resolved project lands on
|
|
270
|
+
* context — read it with {@link project}.
|
|
271
|
+
*/
|
|
272
|
+
export function ensureProject(): MiddlewareHandler {
|
|
273
|
+
// Typed loosely (like {@link policy}) so route chains keep their inferred
|
|
274
|
+
// schema; a concrete environment parameter collapses Hono's inference.
|
|
275
|
+
return createMiddleware<Environment>(async (c, next) => {
|
|
276
|
+
const notFound = () =>
|
|
277
|
+
Response.error(c, {
|
|
278
|
+
code: 'project_not_found',
|
|
279
|
+
message: 'Project not found',
|
|
280
|
+
status: 404,
|
|
281
|
+
})
|
|
282
|
+
const projectId = c.req.param('projectId' as never) as string | undefined
|
|
283
|
+
if (!projectId) return notFound()
|
|
284
|
+
const scoped = c.get('org')
|
|
285
|
+
if (!scoped) return notFound()
|
|
286
|
+
const record = await Projects.get(Db.get(contextDb(c)), projectId)
|
|
287
|
+
if (!record || record.orgId !== scoped.id) return notFound()
|
|
288
|
+
c.set('project', record)
|
|
289
|
+
await next()
|
|
290
|
+
return undefined
|
|
291
|
+
})
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Always false at runtime, but typed as `boolean` so handlers can include a
|
|
296
|
+
* never-reached scope-error branch in Hono's inferred response union (the
|
|
297
|
+
* ensure middlewares produce these `404`s before the handler runs).
|
|
298
|
+
*/
|
|
299
|
+
export const narrowScope = false as boolean
|
|
300
|
+
|
|
301
|
+
/** Typed org-scope error response for Hono client inference. Never reached at runtime. */
|
|
302
|
+
export function ensureOrgError(c: hono_Context) {
|
|
303
|
+
return Response.error(c, {
|
|
304
|
+
code: 'organization_not_found',
|
|
305
|
+
message: 'Organization not found',
|
|
306
|
+
status: 404,
|
|
307
|
+
})
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/** Typed project-scope error response for Hono client inference. Never reached at runtime. */
|
|
311
|
+
export function ensureProjectError(c: hono_Context) {
|
|
312
|
+
// Project routes run the org scope first, so either not-found can surface.
|
|
313
|
+
const code =
|
|
314
|
+
c.req.header('tempo-narrow-scope-code') === 'organization_not_found'
|
|
315
|
+
? ('organization_not_found' as const)
|
|
316
|
+
: ('project_not_found' as const)
|
|
317
|
+
return Response.error(c, {
|
|
318
|
+
code,
|
|
319
|
+
message: 'Project not found',
|
|
320
|
+
status: 404,
|
|
321
|
+
})
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/** Reads the caller's membership resolved by {@link ensureOrg}; `undefined` for `super_admin`. */
|
|
325
|
+
export function membership<environment extends Environment>(
|
|
326
|
+
c: hono_Context<environment>,
|
|
327
|
+
): Memberships.Record | undefined {
|
|
328
|
+
return c.get('membership')
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/** Reads the organization resolved by {@link ensureOrg}; throws when the middleware did not run. */
|
|
332
|
+
export function org<environment extends Environment>(
|
|
333
|
+
c: hono_Context<environment>,
|
|
334
|
+
): Organizations.Record {
|
|
335
|
+
const record = c.get('org')
|
|
336
|
+
if (!record) throw new Error('`orgScope` middleware must run before reading the organization')
|
|
337
|
+
return record
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/** Reads the project resolved by {@link ensureProject}; throws when the middleware did not run. */
|
|
341
|
+
export function project<environment extends Environment>(
|
|
342
|
+
c: hono_Context<environment>,
|
|
343
|
+
): Projects.Record {
|
|
344
|
+
const record = c.get('project')
|
|
345
|
+
if (!record) throw new Error('`projectScope` middleware must run before reading the project')
|
|
346
|
+
return record
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Reads the app's database source from request context (set by `App.create`
|
|
350
|
+
// from its `db` option).
|
|
351
|
+
function contextDb(c: hono_Context): Db.Source {
|
|
352
|
+
return (c.get as (key: string) => unknown)('db') as Db.Source
|
|
353
|
+
}
|
|
354
|
+
|
|
160
355
|
/**
|
|
161
356
|
* Always false at runtime, but typed as `boolean` so handlers can include a
|
|
162
357
|
* never-reached access-error branch in Hono's inferred response union.
|
|
@@ -200,6 +395,33 @@ export function accessError(c: hono_Context) {
|
|
|
200
395
|
})
|
|
201
396
|
}
|
|
202
397
|
|
|
398
|
+
/** Typed lane-less policy error response for Hono client inference. Never reached at runtime. */
|
|
399
|
+
export function superAdminAccessError(c: hono_Context) {
|
|
400
|
+
const status = Number(c.req.header('tempo-narrow-access-status'))
|
|
401
|
+
if (status === 400)
|
|
402
|
+
return Response.error(c, {
|
|
403
|
+
code: 'api_key_malformed',
|
|
404
|
+
message: 'Malformed API key',
|
|
405
|
+
status: 400,
|
|
406
|
+
})
|
|
407
|
+
if (status === 401) {
|
|
408
|
+
const code =
|
|
409
|
+
c.req.header('tempo-narrow-access-code') === 'api_key_missing'
|
|
410
|
+
? 'api_key_missing'
|
|
411
|
+
: 'api_key_invalid'
|
|
412
|
+
return Response.error(c, {
|
|
413
|
+
code,
|
|
414
|
+
message: 'Missing or invalid API key',
|
|
415
|
+
status: 401,
|
|
416
|
+
})
|
|
417
|
+
}
|
|
418
|
+
return Response.error(c, {
|
|
419
|
+
code: 'forbidden',
|
|
420
|
+
message: 'API key not permitted for this route',
|
|
421
|
+
status: 403,
|
|
422
|
+
})
|
|
423
|
+
}
|
|
424
|
+
|
|
203
425
|
/** Typed auth/payment error response for Hono client inference. Never reached at runtime. */
|
|
204
426
|
export function paidAccessError(c: hono_Context) {
|
|
205
427
|
const status = Number(c.req.header('tempo-narrow-access-status'))
|
|
@@ -216,15 +438,23 @@ export function middleware<
|
|
|
216
438
|
>(options: middleware.Options<endpoint>): MiddlewareHandler<environment> {
|
|
217
439
|
const { charge = {}, ...mpp } = options.mpp === false ? {} : (options.mpp ?? {})
|
|
218
440
|
const defaults = laneDefaults(options)
|
|
219
|
-
|
|
441
|
+
// Default-deny: no lanes open until a route grants them via `policy`.
|
|
442
|
+
// Otherwise the `super_admin` principal bypasses lane checks entirely.
|
|
443
|
+
const base: require.Policy = {}
|
|
444
|
+
// Keys resolve against the app's KV state store on request context
|
|
445
|
+
// (`App.create({ kv })`), so the middleware needs no storage wiring; when no
|
|
446
|
+
// store is configured, every token resolves to `null` (closed).
|
|
220
447
|
// Resolution is cached per isolate by default (positive 60s / negative 10s,
|
|
221
|
-
// keyed by token sha256
|
|
222
|
-
//
|
|
223
|
-
//
|
|
224
|
-
const
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
448
|
+
// keyed by token sha256) so hot keys skip the backend read on the auth path
|
|
449
|
+
// of every request. Pass `apiKey.cache: false` when instant revocation
|
|
450
|
+
// matters more than the per-request read.
|
|
451
|
+
const resolve = (() => {
|
|
452
|
+
const uncached = (c: hono_Context, token: string) => {
|
|
453
|
+
const kv = contextKv(c)
|
|
454
|
+
return kv ? ApiKeys.resolve(kv.store, token) : Promise.resolve(null)
|
|
455
|
+
}
|
|
456
|
+
if (options.apiKey === false || options.apiKey?.cache === false) return uncached
|
|
457
|
+
return cachedResolve(uncached, options.apiKey?.cache ?? {})
|
|
228
458
|
})()
|
|
229
459
|
const auth: Context = {
|
|
230
460
|
...(options.apiKey === false
|
|
@@ -232,7 +462,7 @@ export function middleware<
|
|
|
232
462
|
: {
|
|
233
463
|
apiKey: {
|
|
234
464
|
...(options.apiKey?.rateLimits ? { rateLimits: options.apiKey.rateLimits } : {}),
|
|
235
|
-
|
|
465
|
+
resolve,
|
|
236
466
|
},
|
|
237
467
|
}),
|
|
238
468
|
...(options.mpp === false
|
|
@@ -245,6 +475,10 @@ export function middleware<
|
|
|
245
475
|
}),
|
|
246
476
|
}),
|
|
247
477
|
rateLimit: RateLimit.memory(options.rateLimit),
|
|
478
|
+
...(options.session ? { session: options.session } : {}),
|
|
479
|
+
...(options.superAdmin?.secret
|
|
480
|
+
? { superAdmin: { tokenHash: ApiKey.hash(options.superAdmin.secret) } }
|
|
481
|
+
: {}),
|
|
248
482
|
}
|
|
249
483
|
|
|
250
484
|
return createMiddleware<environment>((c, next) => {
|
|
@@ -280,16 +514,14 @@ export declare namespace middleware {
|
|
|
280
514
|
apiKey?:
|
|
281
515
|
| {
|
|
282
516
|
/**
|
|
283
|
-
* Resolve-cache tuning
|
|
284
|
-
*
|
|
285
|
-
*
|
|
286
|
-
*
|
|
517
|
+
* Resolve-cache tuning (per-isolate, positive 60s / negative 10s by
|
|
518
|
+
* default), or `false` to resolve uncached on every request — e.g.
|
|
519
|
+
* when instant revocation matters more than the per-request backend
|
|
520
|
+
* read.
|
|
287
521
|
*/
|
|
288
|
-
cache?:
|
|
522
|
+
cache?: Cache | false | undefined
|
|
289
523
|
/** API-key quotas keyed by quota scope (reserved `'*'` is the config default). */
|
|
290
524
|
rateLimits?: Record<string, RateLimit.Limit> | undefined
|
|
291
|
-
/** Backing API-key source. Defaults to an empty source (rejects all). */
|
|
292
|
-
source?: ApiKeySource.Source | undefined
|
|
293
525
|
/** Required API-key scopes. */
|
|
294
526
|
scopes?: readonly Scope.Id[] | undefined
|
|
295
527
|
}
|
|
@@ -309,6 +541,24 @@ export declare namespace middleware {
|
|
|
309
541
|
| undefined
|
|
310
542
|
/** Rate-limit store options. */
|
|
311
543
|
rateLimit?: RateLimit.memory.Options | undefined
|
|
544
|
+
/** Session lane resolution — the session surface's capability; omit to leave the session lane closed. */
|
|
545
|
+
session?: Session | undefined
|
|
546
|
+
/**
|
|
547
|
+
* Super-admin machine credential. A presented API-key token matching this
|
|
548
|
+
* secret resolves to a `super_admin` principal — bypassing scope and quota
|
|
549
|
+
* checks — before the key lookup runs. Omit to disable the entry entirely.
|
|
550
|
+
*/
|
|
551
|
+
superAdmin?: { secret: string } | undefined
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/** Resolve-cache tuning for API-key resolution. */
|
|
555
|
+
type Cache = {
|
|
556
|
+
/** Time-to-live for negative (`null`) results in milliseconds. */
|
|
557
|
+
negativeTtl?: number | undefined
|
|
558
|
+
/** Clock used for expiry. */
|
|
559
|
+
now?: (() => number) | undefined
|
|
560
|
+
/** Time-to-live for positive results in milliseconds. */
|
|
561
|
+
ttl?: number | undefined
|
|
312
562
|
}
|
|
313
563
|
|
|
314
564
|
/** MPP payment options used by auth middleware. */
|
|
@@ -318,6 +568,305 @@ export declare namespace middleware {
|
|
|
318
568
|
}
|
|
319
569
|
}
|
|
320
570
|
|
|
571
|
+
/** Zod schemas documenting the session sign-in surface (`/v1/auth/siwe`). */
|
|
572
|
+
namespace schema {
|
|
573
|
+
const siweMessage =
|
|
574
|
+
'api.tempo.xyz wants you to sign in with your Ethereum account:\n0x0000000000000000000000000000000000000000\n\nURI: https://api.tempo.xyz\nVersion: 1\nChain ID: 0\nNonce: 3D0sZfnHqTBmc9tKR\nIssued At: 2026-01-01T00:00:00.000Z\nExpiration Time: 2026-01-01T00:10:00.000Z'
|
|
575
|
+
|
|
576
|
+
/** Error body returned by the sign-in handler (SDK shape, not the API error envelope). */
|
|
577
|
+
export const HandlerError = z.object({
|
|
578
|
+
error: z
|
|
579
|
+
.string()
|
|
580
|
+
.check(
|
|
581
|
+
z.describe('Human-readable failure reason.'),
|
|
582
|
+
z.meta({ examples: ['domain mismatch'] }),
|
|
583
|
+
),
|
|
584
|
+
issues: z
|
|
585
|
+
.optional(z.array(z.unknown()))
|
|
586
|
+
.check(z.describe('Field-level validation issues, present on request-schema failures.')),
|
|
587
|
+
})
|
|
588
|
+
|
|
589
|
+
/** Schemas for `POST /v1/auth/siwe/challenge`. */
|
|
590
|
+
export namespace challenge {
|
|
591
|
+
/** Request body. Accepted fields beyond this document pass through to the SDK handler. */
|
|
592
|
+
export const Body = z.object({})
|
|
593
|
+
|
|
594
|
+
/** Response body. */
|
|
595
|
+
export const Response = z.object({
|
|
596
|
+
message: z
|
|
597
|
+
.string()
|
|
598
|
+
.check(
|
|
599
|
+
z.describe('Single-use EIP-4361 (SIWE) message to sign and submit for verification.'),
|
|
600
|
+
z.meta({ examples: [siweMessage] }),
|
|
601
|
+
),
|
|
602
|
+
})
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
/** Schemas for `POST /v1/auth/siwe` (verify). */
|
|
606
|
+
export namespace verify {
|
|
607
|
+
/** Request body. */
|
|
608
|
+
export const Body = z.object({
|
|
609
|
+
address: z
|
|
610
|
+
.string()
|
|
611
|
+
.check(
|
|
612
|
+
z.describe('Wallet address that signed the message; becomes the session subject.'),
|
|
613
|
+
z.meta({ examples: ['0x0000000000000000000000000000000000000001'] }),
|
|
614
|
+
),
|
|
615
|
+
idToken: z.optional(z.string()).check(
|
|
616
|
+
z.describe('Wallet-minted OIDC identity token (JWT) asserting a verified email, folded onto the session.'), // prettier-ignore
|
|
617
|
+
z.meta({ examples: ['eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIweDAwMDAifQ.c2lnbmF0dXJl'] }),
|
|
618
|
+
),
|
|
619
|
+
keyAuthorization: z.optional(z.string()).check(
|
|
620
|
+
z.describe('RLP-serialized signed key authorization (TIP-1053) whose witness binds this message; verification recovers over its digest.'), // prettier-ignore
|
|
621
|
+
z.meta({ examples: [`0x${'aa'.repeat(60)}`] }),
|
|
622
|
+
),
|
|
623
|
+
message: z
|
|
624
|
+
.string()
|
|
625
|
+
.check(
|
|
626
|
+
z.describe('The exact challenge message issued by `POST /v1/auth/siwe/challenge`.'),
|
|
627
|
+
z.meta({ examples: [siweMessage] }),
|
|
628
|
+
),
|
|
629
|
+
returnToken: z.optional(z.boolean()).check(
|
|
630
|
+
z.describe('Return the session token in the body instead of setting the session cookie.'), // prettier-ignore
|
|
631
|
+
z.meta({ examples: [true] }),
|
|
632
|
+
),
|
|
633
|
+
signature: z
|
|
634
|
+
.string()
|
|
635
|
+
.check(
|
|
636
|
+
z.describe('Signature over the challenge message (or the key-authorization digest).'),
|
|
637
|
+
z.meta({ examples: [`0x${'aa'.repeat(65)}`] }),
|
|
638
|
+
),
|
|
639
|
+
})
|
|
640
|
+
|
|
641
|
+
/** Response body. */
|
|
642
|
+
export const Response = z.object({
|
|
643
|
+
token: z
|
|
644
|
+
.optional(z.string())
|
|
645
|
+
.check(
|
|
646
|
+
z.describe('Bearer session token; present only when `returnToken` is true.'),
|
|
647
|
+
z.meta({ examples: ['aa'.repeat(32)] }),
|
|
648
|
+
),
|
|
649
|
+
})
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
/** `{ error }` response for a sign-in handler failure status. */
|
|
654
|
+
function handlerError(description: string) {
|
|
655
|
+
return {
|
|
656
|
+
content: { 'application/json': { schema: OpenApi.resolver(schema.HandlerError) } },
|
|
657
|
+
description,
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
/**
|
|
662
|
+
* Builds the built-in auth pieces from `App.create`'s `auth` option: the
|
|
663
|
+
* session-surface app to mount (when `session` is configured) and the
|
|
664
|
+
* enforcement middleware, wired with the surface's resolver. `auth: false`
|
|
665
|
+
* disables both.
|
|
666
|
+
*/
|
|
667
|
+
export function install(options: install.Options) {
|
|
668
|
+
const { db, kv } = options
|
|
669
|
+
const { session: options_session, ...options_middleware } =
|
|
670
|
+
options.auth === false ? {} : (options.auth ?? {})
|
|
671
|
+
if (options_session && !kv)
|
|
672
|
+
throw new Error('`auth.session` requires the `kv` state store: sessions and sign-in challenges persist there.') // prettier-ignore
|
|
673
|
+
|
|
674
|
+
// Session sign-in surface (challenge, verify, logout — SIWE wallet sign-in
|
|
675
|
+
// today) plus the capability the session lane resolves through. Public but
|
|
676
|
+
// policy-gated: the hidden describeRoute engages the middleware for the
|
|
677
|
+
// mounted sub-app, and the public lane rate-limits challenge-store writes.
|
|
678
|
+
const app = (() => {
|
|
679
|
+
if (!options_session || !kv) return undefined
|
|
680
|
+
const { issuer, origin, ttl } =
|
|
681
|
+
(typeof options_session === 'object' ? options_session.wallet : undefined) ?? {}
|
|
682
|
+
|
|
683
|
+
const handler = Accounts.Handler.auth({
|
|
684
|
+
...(issuer ? { identity: { issuer } } : {}),
|
|
685
|
+
// Sign-in upserts the `users` row keyed by address.
|
|
686
|
+
onAuthenticate: async ({ address }) => {
|
|
687
|
+
await Users.upsertByAddress(Db.get(db), { address })
|
|
688
|
+
},
|
|
689
|
+
// No pinned origin: on Workers the edge proxy headers are trustworthy, so
|
|
690
|
+
// opt in explicitly (the SDK's guard ignores its own Workers default).
|
|
691
|
+
// Off-Workers the SDK keeps throwing until `wallet.origin` is set.
|
|
692
|
+
...(origin
|
|
693
|
+
? { origin }
|
|
694
|
+
: globalThis.navigator?.userAgent === 'Cloudflare-Workers'
|
|
695
|
+
? { trustProxy: true }
|
|
696
|
+
: {}),
|
|
697
|
+
// The app's KV store under the SDK's Kv contract: JSON values,
|
|
698
|
+
// second-based TTLs, `auth:`-prefixed keys disjoint from API-key
|
|
699
|
+
// records. `take` is omitted; the SDK falls back to get+delete.
|
|
700
|
+
store: {
|
|
701
|
+
async delete(key) {
|
|
702
|
+
await kv.store.delete(`auth:${key}`)
|
|
703
|
+
},
|
|
704
|
+
async get(key) {
|
|
705
|
+
const value = await kv.store.get(`auth:${key}`)
|
|
706
|
+
return value === null ? undefined : (JSON.parse(value) as never)
|
|
707
|
+
},
|
|
708
|
+
async set(key, value, options) {
|
|
709
|
+
await kv.store.put(
|
|
710
|
+
`auth:${key}`,
|
|
711
|
+
JSON.stringify(value),
|
|
712
|
+
options?.ttl ? { ttl: options.ttl * 1_000 } : {},
|
|
713
|
+
)
|
|
714
|
+
},
|
|
715
|
+
},
|
|
716
|
+
...(ttl ? { ttl } : {}),
|
|
717
|
+
})
|
|
718
|
+
|
|
719
|
+
const session: Session = {
|
|
720
|
+
resolve: async (c) => {
|
|
721
|
+
const payload = await handler.getSession(c.req.raw)
|
|
722
|
+
if (!payload) return null
|
|
723
|
+
const database = Db.get(db)
|
|
724
|
+
const user = await Users.getByAddress(database, payload.address)
|
|
725
|
+
if (!user) return null
|
|
726
|
+
// Fold a newly verified email onto the user row: identity tokens are
|
|
727
|
+
// optional per sign-in, so the column fills lazily.
|
|
728
|
+
if (payload.email && user.email !== payload.email)
|
|
729
|
+
await Users.setEmail(database, user.id, payload.email)
|
|
730
|
+
const email = payload.email ?? user.email ?? undefined
|
|
731
|
+
return {
|
|
732
|
+
...(email ? { email } : {}),
|
|
733
|
+
id: user.id,
|
|
734
|
+
identity: { provider: 'wallet', subject: payload.address },
|
|
735
|
+
type: 'session',
|
|
736
|
+
}
|
|
737
|
+
},
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
const policy_public = policy({ public: true })
|
|
741
|
+
const describe = OpenApi.describeRoute({ hide: true })
|
|
742
|
+
const app = new Hono<App.Environment>()
|
|
743
|
+
.use('/v1/auth/siwe', policy_public, describe)
|
|
744
|
+
.use('/v1/auth/siwe/*', policy_public, describe)
|
|
745
|
+
// Documented operations: pre-routes carrying the OpenAPI metadata and the
|
|
746
|
+
// envelope-shaped body validation; the mounted handler owns behavior.
|
|
747
|
+
// Statement form keeps them off the app type (`hc` surface).
|
|
748
|
+
app.post(
|
|
749
|
+
'/v1/auth/siwe/challenge',
|
|
750
|
+
policy_public,
|
|
751
|
+
OpenApi.validate('json', schema.challenge.Body, {
|
|
752
|
+
code: 'body_invalid',
|
|
753
|
+
message: 'Invalid request body',
|
|
754
|
+
}),
|
|
755
|
+
OpenApi.describeRoute({
|
|
756
|
+
description:
|
|
757
|
+
'Issues a single-use SIWE challenge message to sign. The challenge expires after a short TTL.',
|
|
758
|
+
// Overrides the surface-wide hidden mount guard for this documented route.
|
|
759
|
+
hide: false,
|
|
760
|
+
operationId: 'createSiweChallenge',
|
|
761
|
+
responses: {
|
|
762
|
+
200: {
|
|
763
|
+
content: { 'application/json': { schema: OpenApi.resolver(schema.challenge.Response) } }, // prettier-ignore
|
|
764
|
+
description: 'The challenge message to sign.',
|
|
765
|
+
headers: { ...OpenApi.successHeaders },
|
|
766
|
+
},
|
|
767
|
+
400: OpenApi.standardError(400, 'Malformed request body.', ['body_invalid']),
|
|
768
|
+
429: OpenApi.standardError(429, 'Rate limited.'),
|
|
769
|
+
500: OpenApi.standardError(500, 'Internal server error.'),
|
|
770
|
+
},
|
|
771
|
+
summary: 'Create challenge (SIWE)',
|
|
772
|
+
tags: ['Authentication'],
|
|
773
|
+
}),
|
|
774
|
+
)
|
|
775
|
+
app.post(
|
|
776
|
+
'/v1/auth/siwe',
|
|
777
|
+
policy_public,
|
|
778
|
+
OpenApi.validate('json', schema.verify.Body, {
|
|
779
|
+
code: 'body_invalid',
|
|
780
|
+
message: 'Invalid request body',
|
|
781
|
+
}),
|
|
782
|
+
OpenApi.describeRoute({
|
|
783
|
+
description:
|
|
784
|
+
'Verifies the signed challenge and establishes a session. Sets a session cookie by default; pass `returnToken` to receive a bearer token instead.',
|
|
785
|
+
hide: false,
|
|
786
|
+
operationId: 'verifySiwe',
|
|
787
|
+
responses: {
|
|
788
|
+
200: {
|
|
789
|
+
content: { 'application/json': { schema: OpenApi.resolver(schema.verify.Response) } },
|
|
790
|
+
description: 'Session established. Cookie mode sets `Set-Cookie`; token mode returns `token`.', // prettier-ignore
|
|
791
|
+
headers: {
|
|
792
|
+
...OpenApi.successHeaders,
|
|
793
|
+
'Set-Cookie': {
|
|
794
|
+
description: 'Session cookie (`accounts_auth`); omitted when `returnToken` is true.', // prettier-ignore
|
|
795
|
+
schema: { type: 'string' },
|
|
796
|
+
},
|
|
797
|
+
},
|
|
798
|
+
},
|
|
799
|
+
400: handlerError('Invalid, expired, or mismatched challenge message.'),
|
|
800
|
+
401: handlerError('Signature or identity verification failed.'),
|
|
801
|
+
409: handlerError('Challenge nonce already used or unknown.'),
|
|
802
|
+
429: OpenApi.standardError(429, 'Rate limited.'),
|
|
803
|
+
500: OpenApi.standardError(500, 'Internal server error.'),
|
|
804
|
+
},
|
|
805
|
+
summary: 'Authenticate (SIWE)',
|
|
806
|
+
tags: ['Authentication'],
|
|
807
|
+
}),
|
|
808
|
+
)
|
|
809
|
+
app.post(
|
|
810
|
+
'/v1/auth/siwe/logout',
|
|
811
|
+
policy_public,
|
|
812
|
+
OpenApi.describeRoute({
|
|
813
|
+
description: 'Revokes the current session and clears the session cookie.',
|
|
814
|
+
hide: false,
|
|
815
|
+
operationId: 'siweLogout',
|
|
816
|
+
responses: {
|
|
817
|
+
204: { description: 'Session revoked and cookie cleared.' },
|
|
818
|
+
429: OpenApi.standardError(429, 'Rate limited.'),
|
|
819
|
+
500: OpenApi.standardError(500, 'Internal server error.'),
|
|
820
|
+
},
|
|
821
|
+
summary: 'Sign out',
|
|
822
|
+
tags: ['Authentication'],
|
|
823
|
+
}),
|
|
824
|
+
)
|
|
825
|
+
return Object.assign(
|
|
826
|
+
// The tag joins the `Platform API` group at `platform()`'s chosen slot;
|
|
827
|
+
// group listings filter to defined tags, so it drops out when unmounted.
|
|
828
|
+
Metadata.attach(app.route('/v1/auth/siwe', handler), {
|
|
829
|
+
tags: [{ name: 'Authentication', description: 'Authenticate into the Tempo Platform.' }],
|
|
830
|
+
}),
|
|
831
|
+
{ session },
|
|
832
|
+
)
|
|
833
|
+
})()
|
|
834
|
+
|
|
835
|
+
const middleware_ =
|
|
836
|
+
options.auth === false
|
|
837
|
+
? undefined
|
|
838
|
+
: middleware({ ...options_middleware, ...(app ? { session: app.session } : {}) })
|
|
839
|
+
|
|
840
|
+
return { app, middleware: middleware_ }
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
export declare namespace install {
|
|
844
|
+
/** Options for building the built-in auth pieces. */
|
|
845
|
+
type Options = {
|
|
846
|
+
auth:
|
|
847
|
+
| (Omit<middleware.Options, 'session'> & {
|
|
848
|
+
/** Session sign-in surface: mounts `/v1/auth/siwe` (challenge/verify/logout) and enables the session lane. Pass true for defaults or a wallet config. Requires `kv`. */
|
|
849
|
+
session?: boolean | { wallet?: Wallet | undefined } | undefined
|
|
850
|
+
})
|
|
851
|
+
| false
|
|
852
|
+
| undefined
|
|
853
|
+
/** Authoritative database holding user rows. */
|
|
854
|
+
db: Db.Source
|
|
855
|
+
/** KV state store, or undefined when key auth is closed. */
|
|
856
|
+
kv: { store: Store.State } | undefined
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
/** SIWE wallet sign-in configuration. */
|
|
860
|
+
type Wallet = {
|
|
861
|
+
/** Identity (verified email) issuer override; defaults to the Tempo wallet's production OIDC mount. */
|
|
862
|
+
issuer?: string | undefined
|
|
863
|
+
/** Pinned absolute origin for SIWE domain binding. Required off-Workers; on Workers the edge proxy headers are trusted by default. */
|
|
864
|
+
origin?: string | undefined
|
|
865
|
+
/** Session and challenge TTL overrides, in seconds. */
|
|
866
|
+
ttl?: { challenge?: number | undefined; session?: number | undefined } | undefined
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
|
|
321
870
|
/** Creates route middleware that contributes an auth policy override. */
|
|
322
871
|
export function policy(options: PolicyOverride): MiddlewareHandler {
|
|
323
872
|
const middleware = createMiddleware((_, next) => next()) as MiddlewareHandler & PolicyHandler
|
|
@@ -361,6 +910,7 @@ type LaneDefaults = {
|
|
|
361
910
|
apiKey: NonNullable<require.Policy['apiKey']>
|
|
362
911
|
mpp: NonNullable<require.Policy['mpp']>
|
|
363
912
|
public: NonNullable<require.Policy['public']>
|
|
913
|
+
session: NonNullable<require.Policy['session']>
|
|
364
914
|
}
|
|
365
915
|
|
|
366
916
|
type OpenApiHandler = {
|
|
@@ -394,19 +944,10 @@ function laneDefaults(options: middleware.Options): LaneDefaults {
|
|
|
394
944
|
},
|
|
395
945
|
mpp: { charge: {} },
|
|
396
946
|
public: { rateLimit: public_?.rateLimit ?? { perMinute: 60 } },
|
|
947
|
+
session: true,
|
|
397
948
|
}
|
|
398
949
|
}
|
|
399
950
|
|
|
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
951
|
function resolvePolicy(
|
|
411
952
|
base: require.Policy,
|
|
412
953
|
defaults: LaneDefaults,
|
|
@@ -416,6 +957,7 @@ function resolvePolicy(
|
|
|
416
957
|
let apiKey = base.apiKey
|
|
417
958
|
let mpp = base.mpp
|
|
418
959
|
let public_ = base.public
|
|
960
|
+
let session = base.session
|
|
419
961
|
|
|
420
962
|
for (const override of overrides) {
|
|
421
963
|
if (override === false) return undefined
|
|
@@ -423,6 +965,7 @@ function resolvePolicy(
|
|
|
423
965
|
apiKey = resolveApiKeyLane(apiKey, override.apiKey, defaults.apiKey)
|
|
424
966
|
mpp = resolveMppLane(mpp, override.mpp, defaults.mpp)
|
|
425
967
|
public_ = resolvePublicLane(public_, override.public, defaults.public)
|
|
968
|
+
session = resolveSessionLane(session, override.session, defaults.session)
|
|
426
969
|
}
|
|
427
970
|
|
|
428
971
|
// The OpenAPI summary/description rides along as the payment-challenge
|
|
@@ -431,11 +974,13 @@ function resolvePolicy(
|
|
|
431
974
|
if (mpp && description !== undefined && mpp.charge.description === undefined)
|
|
432
975
|
mpp = { charge: { ...mpp.charge, description } }
|
|
433
976
|
|
|
434
|
-
|
|
977
|
+
// A lane-less policy stays enforced for super admin; only an explicit
|
|
978
|
+
// `false` override opts a route out of auth entirely.
|
|
435
979
|
return {
|
|
436
980
|
...(apiKey ? { apiKey } : {}),
|
|
437
981
|
...(mpp ? { mpp } : {}),
|
|
438
982
|
...(public_ ? { public: public_ } : {}),
|
|
983
|
+
...(session ? { session } : {}),
|
|
439
984
|
}
|
|
440
985
|
}
|
|
441
986
|
|
|
@@ -478,6 +1023,16 @@ function resolvePublicLane(
|
|
|
478
1023
|
return { rateLimit: override.rateLimit ?? base.rateLimit }
|
|
479
1024
|
}
|
|
480
1025
|
|
|
1026
|
+
function resolveSessionLane(
|
|
1027
|
+
current: require.Policy['session'],
|
|
1028
|
+
override: PolicyOverride['session'],
|
|
1029
|
+
default_: LaneDefaults['session'],
|
|
1030
|
+
): require.Policy['session'] {
|
|
1031
|
+
if (override === undefined) return current
|
|
1032
|
+
if (override === false) return undefined
|
|
1033
|
+
return current ?? default_
|
|
1034
|
+
}
|
|
1035
|
+
|
|
481
1036
|
function openApiRoute(c: hono_Context): OpenApiRoute | undefined {
|
|
482
1037
|
const route = matchedRoutes(c)
|
|
483
1038
|
.slice(c.req.routeIndex + 1)
|
|
@@ -515,20 +1070,24 @@ function policyFromRoute(route: RouterRoute) {
|
|
|
515
1070
|
|
|
516
1071
|
/** Resolved access lanes for one documented operation. */
|
|
517
1072
|
export type OperationAccess = {
|
|
1073
|
+
/** Whether API-key access is allowed. */
|
|
1074
|
+
apiKey: boolean
|
|
518
1075
|
/** Whether MPP payment is accepted (enables the `402` payment-challenge response). */
|
|
519
1076
|
mpp: boolean
|
|
520
1077
|
/** Whether anonymous public-quota access is allowed (keeps the anonymous security option). */
|
|
521
1078
|
public: boolean
|
|
522
1079
|
/** Required API-key scopes (`[]` accepts any valid key); empty when the API-key lane is disabled. */
|
|
523
1080
|
scopes: readonly Scope.Id[]
|
|
1081
|
+
/** Whether signed-in sessions are allowed. */
|
|
1082
|
+
session: boolean
|
|
524
1083
|
}
|
|
525
1084
|
|
|
526
1085
|
/**
|
|
527
1086
|
* Maps each documented operation id to its resolved access lanes by reading the
|
|
528
1087
|
* `policy` overrides co-located on the app's routes. This is the single source
|
|
529
1088
|
* of truth for per-operation `security` and the `402` response in the generated
|
|
530
|
-
* OpenAPI document (see `App.create`):
|
|
531
|
-
*
|
|
1089
|
+
* OpenAPI document (see `App.create`): all lanes are default-closed, and an
|
|
1090
|
+
* operation surfaces only the lanes its route opts into via {@link policy}.
|
|
532
1091
|
*/
|
|
533
1092
|
export function describeAccess(app: Hono<any, any>): Record<string, OperationAccess> {
|
|
534
1093
|
const groups = new Map<string, { operationId?: string; overrides: PolicyOverride[] }>()
|
|
@@ -545,18 +1104,20 @@ export function describeAccess(app: Hono<any, any>): Record<string, OperationAcc
|
|
|
545
1104
|
const access: Record<string, OperationAccess> = {}
|
|
546
1105
|
for (const { operationId, overrides } of groups.values()) {
|
|
547
1106
|
if (!operationId) continue
|
|
548
|
-
let apiKey =
|
|
1107
|
+
let apiKey = false
|
|
549
1108
|
let scopes: readonly Scope.Id[] = []
|
|
550
1109
|
let public_ = false
|
|
551
1110
|
let mpp = false
|
|
1111
|
+
let session = false
|
|
552
1112
|
for (const override of overrides) {
|
|
553
1113
|
if (override.apiKey !== undefined) apiKey = override.apiKey !== false
|
|
554
1114
|
if (override.apiKey && override.apiKey !== true && override.apiKey.scopes)
|
|
555
1115
|
scopes = override.apiKey.scopes
|
|
556
1116
|
if (override.public !== undefined) public_ = override.public !== false
|
|
557
1117
|
if (override.mpp !== undefined) mpp = override.mpp !== false
|
|
1118
|
+
if (override.session !== undefined) session = override.session !== false
|
|
558
1119
|
}
|
|
559
|
-
access[operationId] = { mpp, public: public_, scopes: apiKey ? scopes : [] }
|
|
1120
|
+
access[operationId] = { apiKey, mpp, public: public_, scopes: apiKey ? scopes : [], session }
|
|
560
1121
|
}
|
|
561
1122
|
return access
|
|
562
1123
|
}
|
|
@@ -612,13 +1173,31 @@ export function require<environment extends Environment = Environment>(
|
|
|
612
1173
|
|
|
613
1174
|
const hasPayment = hasPaymentCredential(c) && isPaymentEnabled(auth, policy)
|
|
614
1175
|
if (credential) {
|
|
615
|
-
|
|
616
|
-
|
|
1176
|
+
// The super admin secret precedes key lookup: it is not a stored key.
|
|
1177
|
+
// It must never reach KV resolution or scope/quota checks.
|
|
1178
|
+
if (auth.superAdmin && ApiKey.hash(credential.token) === auth.superAdmin.tokenHash) {
|
|
1179
|
+
c.set('principal', { actor: 'super_admin', id: 'super_admin', type: 'super_admin' })
|
|
1180
|
+
return next()
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
const apiKey = await auth.apiKey?.resolve(c, credential.token)
|
|
1184
|
+
if (!apiKey) {
|
|
1185
|
+
// A bearer session token is indistinguishable from an API key at the
|
|
1186
|
+
// header, so a failed key lookup falls through to the session lane
|
|
1187
|
+
// before rejecting.
|
|
1188
|
+
if (policy.session) {
|
|
1189
|
+
const session = await resolveSession(c, auth)
|
|
1190
|
+
if (session) {
|
|
1191
|
+
c.set('principal', session)
|
|
1192
|
+
return next()
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
617
1195
|
return jsonError(c, {
|
|
618
1196
|
code: 'api_key_invalid',
|
|
619
1197
|
message: 'Invalid API key',
|
|
620
1198
|
status: 401,
|
|
621
1199
|
})
|
|
1200
|
+
}
|
|
622
1201
|
|
|
623
1202
|
// Sandbox keys are restricted to non-mainnet chains. An explicit
|
|
624
1203
|
// `chainId` of mainnet makes the key unusable for this request, so it is
|
|
@@ -643,6 +1222,14 @@ export function require<environment extends Environment = Environment>(
|
|
|
643
1222
|
|
|
644
1223
|
const principal = principalFromApiKey(apiKey)
|
|
645
1224
|
c.set('principal', principal)
|
|
1225
|
+
// Default-deny: routes with no API-key lane allow only the super admin.
|
|
1226
|
+
// A valid key is authenticated but forbidden.
|
|
1227
|
+
if (!policy.apiKey)
|
|
1228
|
+
return jsonError(c, {
|
|
1229
|
+
code: 'forbidden',
|
|
1230
|
+
message: 'API key not permitted for this route',
|
|
1231
|
+
status: 403,
|
|
1232
|
+
})
|
|
646
1233
|
const missing = apiKey.scopes.includes(Scope.wildcard)
|
|
647
1234
|
? []
|
|
648
1235
|
: (policy.apiKey?.scopes.filter((scope) => !apiKey.scopes.includes(scope)) ?? [])
|
|
@@ -692,6 +1279,17 @@ export function require<environment extends Environment = Environment>(
|
|
|
692
1279
|
}
|
|
693
1280
|
}
|
|
694
1281
|
|
|
1282
|
+
// Cookie sessions carry no Authorization credential; resolve the session
|
|
1283
|
+
// lane before the public/paid lanes so signed-in browsers skip anonymous
|
|
1284
|
+
// quotas.
|
|
1285
|
+
if (policy.session) {
|
|
1286
|
+
const session = await resolveSession(c, auth)
|
|
1287
|
+
if (session) {
|
|
1288
|
+
c.set('principal', session)
|
|
1289
|
+
return next()
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
|
|
695
1293
|
const publicPrincipal = principalFromPublic(c)
|
|
696
1294
|
if (hasPayment) {
|
|
697
1295
|
c.set('principal', publicPrincipal)
|
|
@@ -773,6 +1371,8 @@ export declare namespace require {
|
|
|
773
1371
|
rateLimit: RateLimit.Limit
|
|
774
1372
|
}
|
|
775
1373
|
| undefined
|
|
1374
|
+
/** Session access policy: `true` allows signed-in wallet sessions. */
|
|
1375
|
+
session?: boolean | undefined
|
|
776
1376
|
}
|
|
777
1377
|
}
|
|
778
1378
|
|
|
@@ -791,19 +1391,65 @@ type RateLimitAccess = PaymentAccess & {
|
|
|
791
1391
|
rateLimit: RateLimit.Result
|
|
792
1392
|
}
|
|
793
1393
|
|
|
1394
|
+
/**
|
|
1395
|
+
* How long to wait for the rate-limit counter before failing open. A hot
|
|
1396
|
+
* counter backend (e.g. a single Durable Object serving one busy quota bucket)
|
|
1397
|
+
* can stall for tens of seconds during a storage-latency blip; without this cap
|
|
1398
|
+
* every request for that bucket blocks on it, turning a localized backend
|
|
1399
|
+
* hiccup into an API-wide latency spike. The abandoned increment still applies
|
|
1400
|
+
* server-side — we just stop waiting for it.
|
|
1401
|
+
*/
|
|
1402
|
+
const rateLimitConsumeTimeout = 300
|
|
1403
|
+
|
|
794
1404
|
function consumeRateLimit(
|
|
795
1405
|
auth: Context,
|
|
796
1406
|
options: RateLimit.Store.ConsumeOptions,
|
|
797
1407
|
): Promise<RateLimit.Result | null> {
|
|
798
1408
|
if (!auth.rateLimit) return Promise.resolve(null)
|
|
799
|
-
// Fail open: a rate-limit store error (e.g. a lost Durable Object
|
|
800
|
-
//
|
|
801
|
-
|
|
802
|
-
|
|
1409
|
+
// Fail open: a rate-limit store error (e.g. a lost Durable Object connection)
|
|
1410
|
+
// or a stall past `rateLimitConsumeTimeout` degrades to unenforced quota
|
|
1411
|
+
// instead of failing — or blocking — the request.
|
|
1412
|
+
return withTimeout(auth.rateLimit.consume(options), rateLimitConsumeTimeout).catch((error) => {
|
|
1413
|
+
// A timeout or Cloudflare Durable Object overload is an expected
|
|
1414
|
+
// degradation (a slow/hot counter backend), logged at warn so it stays out
|
|
1415
|
+
// of error dashboards; an unexpected store throw is a genuine error.
|
|
1416
|
+
if (error instanceof RateLimitTimeoutError || isDurableObjectOverload(error))
|
|
1417
|
+
console.warn('[auth] rate-limit consume degraded; failing open', error)
|
|
1418
|
+
else console.error('[auth] rate-limit consume failed; failing open', error)
|
|
803
1419
|
return null
|
|
804
1420
|
})
|
|
805
1421
|
}
|
|
806
1422
|
|
|
1423
|
+
function isDurableObjectOverload(error: unknown): boolean {
|
|
1424
|
+
return (
|
|
1425
|
+
typeof error === 'object' &&
|
|
1426
|
+
error !== null &&
|
|
1427
|
+
'overloaded' in error &&
|
|
1428
|
+
(error as { overloaded?: unknown }).overloaded === true
|
|
1429
|
+
)
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
/**
|
|
1433
|
+
* Rejects with a {@link RateLimitTimeoutError} if `promise` does not settle
|
|
1434
|
+
* within `ms`, clearing the timer on either outcome so it never keeps the
|
|
1435
|
+
* isolate alive.
|
|
1436
|
+
*/
|
|
1437
|
+
function withTimeout<value>(promise: Promise<value>, ms: number): Promise<value> {
|
|
1438
|
+
return new Promise<value>((resolve, reject) => {
|
|
1439
|
+
const timer = setTimeout(() => reject(new RateLimitTimeoutError(ms)), ms)
|
|
1440
|
+
promise.then(
|
|
1441
|
+
(value) => {
|
|
1442
|
+
clearTimeout(timer)
|
|
1443
|
+
resolve(value)
|
|
1444
|
+
},
|
|
1445
|
+
(error) => {
|
|
1446
|
+
clearTimeout(timer)
|
|
1447
|
+
reject(error)
|
|
1448
|
+
},
|
|
1449
|
+
)
|
|
1450
|
+
})
|
|
1451
|
+
}
|
|
1452
|
+
|
|
807
1453
|
/**
|
|
808
1454
|
* Resolves the rate limit for an API-key request against a quota bucket.
|
|
809
1455
|
* Most-specific wins: a per-key per-scope grant is the only thing that pierces a
|
|
@@ -944,6 +1590,7 @@ function principalFromApiKey(apiKey: ApiKey.ApiKey): Principal {
|
|
|
944
1590
|
environment: apiKey.environment,
|
|
945
1591
|
id: apiKey.id,
|
|
946
1592
|
orgId: apiKey.orgId,
|
|
1593
|
+
...(apiKey.projectId === undefined ? {} : { projectId: apiKey.projectId }),
|
|
947
1594
|
type: 'api_key',
|
|
948
1595
|
}
|
|
949
1596
|
}
|
|
@@ -1010,3 +1657,95 @@ function setRateLimitHeaders(c: hono_Context, result: RateLimit.Result) {
|
|
|
1010
1657
|
c.header('RateLimit-Remaining', String(result.remaining))
|
|
1011
1658
|
c.header('RateLimit-Reset', String(result.reset))
|
|
1012
1659
|
}
|
|
1660
|
+
|
|
1661
|
+
// Reads the app's KV state store from request context (set by `App.create`
|
|
1662
|
+
// from its `kv` option); `undefined` leaves API-key auth closed.
|
|
1663
|
+
function contextKv(c: hono_Context): { store: Store.State } | undefined {
|
|
1664
|
+
return (c.get as (key: string) => unknown)('kv') as { store: Store.State } | undefined
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
/** Session principal resolved by the session lane. */
|
|
1668
|
+
export type SessionPrincipal = Extract<Principal, { type: 'session' }>
|
|
1669
|
+
|
|
1670
|
+
/**
|
|
1671
|
+
* Session resolution capability installed on app context (`session`) by
|
|
1672
|
+
* `App.create` when session auth is configured; `undefined` leaves the session
|
|
1673
|
+
* lane closed.
|
|
1674
|
+
*/
|
|
1675
|
+
export type Session = {
|
|
1676
|
+
/** Resolves the request's session (cookie or bearer token) to a principal. */
|
|
1677
|
+
resolve: (c: hono_Context) => Promise<SessionPrincipal | null>
|
|
1678
|
+
}
|
|
1679
|
+
|
|
1680
|
+
// Resolve the session lane from the auth context's capability. Failures
|
|
1681
|
+
// resolve to `null` so the remaining lanes and the final `401` still apply.
|
|
1682
|
+
async function resolveSession(c: hono_Context, auth: Context): Promise<SessionPrincipal | null> {
|
|
1683
|
+
const session = auth.session
|
|
1684
|
+
if (!session) return null
|
|
1685
|
+
try {
|
|
1686
|
+
return await session.resolve(c)
|
|
1687
|
+
} catch (error) {
|
|
1688
|
+
console.error('[auth] session resolve failed; treating as no session', error)
|
|
1689
|
+
return null
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
/** Maximum resolve-cache entries held per isolate before eviction. */
|
|
1694
|
+
const cachedMaxEntries = 10_000
|
|
1695
|
+
|
|
1696
|
+
/**
|
|
1697
|
+
* Wraps token resolution with a short-TTL per-isolate cache so hot keys skip
|
|
1698
|
+
* the backend read on every request. Positive results cache for `ttl`
|
|
1699
|
+
* (default 60s), negative for `negativeTtl` (default 10s) so a freshly minted
|
|
1700
|
+
* key is usable quickly; concurrent misses for one token are single-flighted.
|
|
1701
|
+
* Entries are keyed by the token's sha256 (`ApiKey.hash`) — never the
|
|
1702
|
+
* plaintext. A revoked key may stay resolvable for up to `ttl` — the accepted
|
|
1703
|
+
* trade-off for removing a backend read from the auth path.
|
|
1704
|
+
*/
|
|
1705
|
+
function cachedResolve(
|
|
1706
|
+
resolve: (c: hono_Context, token: string) => Promise<ApiKey.ApiKey | null>,
|
|
1707
|
+
options: middleware.Cache,
|
|
1708
|
+
): (c: hono_Context, token: string) => Promise<ApiKey.ApiKey | null> {
|
|
1709
|
+
const { negativeTtl = 10_000, now = Date.now, ttl = 60_000 } = options
|
|
1710
|
+
type Cell = { expiresAt: number; value: ApiKey.ApiKey | null }
|
|
1711
|
+
const cells = new Map<string, Cell>()
|
|
1712
|
+
const flights = new Map<string, Promise<ApiKey.ApiKey | null>>()
|
|
1713
|
+
|
|
1714
|
+
return async (c, token) => {
|
|
1715
|
+
// Versioned so a future record-shape change can't serve stale shapes.
|
|
1716
|
+
const key = `v1:${ApiKey.hash(token)}`
|
|
1717
|
+
const cell = cells.get(key)
|
|
1718
|
+
if (cell && cell.expiresAt > now()) return cell.value
|
|
1719
|
+
if (cell) cells.delete(key)
|
|
1720
|
+
|
|
1721
|
+
const existing = flights.get(key)
|
|
1722
|
+
if (existing) return existing
|
|
1723
|
+
|
|
1724
|
+
const flight = (async () => {
|
|
1725
|
+
const value = await resolve(c, token)
|
|
1726
|
+
// Bound the cache: random invalid tokens each add a (negative) entry,
|
|
1727
|
+
// so sweep expired cells at the cap and then drop the oldest.
|
|
1728
|
+
if (cells.size >= cachedMaxEntries) {
|
|
1729
|
+
const time = now()
|
|
1730
|
+
for (const [name, entry] of cells) if (entry.expiresAt <= time) cells.delete(name)
|
|
1731
|
+
while (cells.size >= cachedMaxEntries) cells.delete(cells.keys().next().value as string)
|
|
1732
|
+
}
|
|
1733
|
+
cells.set(key, { expiresAt: now() + (value ? ttl : negativeTtl), value })
|
|
1734
|
+
return value
|
|
1735
|
+
})()
|
|
1736
|
+
flights.set(key, flight)
|
|
1737
|
+
try {
|
|
1738
|
+
return await flight
|
|
1739
|
+
} finally {
|
|
1740
|
+
flights.delete(key)
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
/** Thrown when a rate-limit consume exceeds {@link rateLimitConsumeTimeout}. */
|
|
1746
|
+
export class RateLimitTimeoutError extends Error {
|
|
1747
|
+
override name = 'Auth.RateLimitTimeoutError'
|
|
1748
|
+
constructor(ms: number) {
|
|
1749
|
+
super(`Rate-limit consume timed out after ${ms}ms; failing open.`)
|
|
1750
|
+
}
|
|
1751
|
+
}
|