tapimo 0.1.0 → 0.3.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 +57 -16
- package/dist/ApiKey.d.ts +4 -1
- package/dist/ApiKey.d.ts.map +1 -1
- package/dist/ApiKey.js +6 -2
- package/dist/ApiKey.js.map +1 -1
- package/dist/ApiKeys.d.ts +98 -3
- package/dist/ApiKeys.d.ts.map +1 -1
- package/dist/ApiKeys.js +150 -1
- package/dist/ApiKeys.js.map +1 -1
- package/dist/App.d.ts +7917 -2315
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js +141 -329
- package/dist/App.js.map +1 -1
- package/dist/Scope.d.ts +20 -4
- package/dist/Scope.d.ts.map +1 -1
- package/dist/Scope.js +23 -4
- package/dist/Scope.js.map +1 -1
- package/dist/Webhooks.d.ts +2 -2
- package/dist/Webhooks.d.ts.map +1 -1
- package/dist/Webhooks.js +2 -2
- package/dist/Webhooks.js.map +1 -1
- package/dist/admin/App.d.ts +203 -120
- package/dist/admin/App.d.ts.map +1 -1
- package/dist/admin/apps/api-keys.d.ts +111 -6
- package/dist/admin/apps/api-keys.d.ts.map +1 -1
- package/dist/admin/apps/api-keys.js +67 -0
- package/dist/admin/apps/api-keys.js.map +1 -1
- package/dist/admin/apps/scopes.d.ts +12 -1
- package/dist/admin/apps/scopes.d.ts.map +1 -1
- package/dist/admin/apps/scopes.js +3 -0
- package/dist/admin/apps/scopes.js.map +1 -1
- package/dist/admin/apps/verified-tokens.d.ts +116 -116
- package/dist/admin/apps/verified-tokens.js +1 -1
- 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 +13 -13
- package/dist/admin/ui.generated.js.map +1 -1
- package/dist/analytics/Analytics.d.ts +151 -0
- package/dist/analytics/Analytics.d.ts.map +1 -0
- package/dist/analytics/Analytics.js +212 -0
- package/dist/analytics/Analytics.js.map +1 -0
- package/dist/analytics/tables/requestEvents.d.ts +215 -0
- package/dist/analytics/tables/requestEvents.d.ts.map +1 -0
- package/dist/analytics/tables/requestEvents.js +264 -0
- package/dist/analytics/tables/requestEvents.js.map +1 -0
- package/dist/apps/data/App.d.ts +19576 -0
- package/dist/apps/data/App.d.ts.map +1 -0
- package/dist/apps/data/App.js +272 -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} +4 -4
- 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} +8 -8
- 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} +108 -108
- 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} +39 -39
- 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/data/routes/funding.d.ts +765 -0
- package/dist/apps/data/routes/funding.d.ts.map +1 -0
- package/dist/apps/data/routes/funding.js +383 -0
- package/dist/apps/data/routes/funding.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} +49 -49
- package/dist/apps/data/routes/receipts.d.ts.map +1 -0
- package/dist/apps/{receipts.js → data/routes/receipts.js} +14 -20
- package/dist/apps/data/routes/receipts.js.map +1 -0
- package/dist/apps/{rpc.d.ts → data/routes/rpc.d.ts} +33 -40
- package/dist/apps/data/routes/rpc.d.ts.map +1 -0
- package/dist/apps/{rpc.js → data/routes/rpc.js} +35 -7
- package/dist/apps/data/routes/rpc.js.map +1 -0
- package/dist/apps/{tokenlist.d.ts → data/routes/tokenlist.d.ts} +40 -40
- 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} +468 -468
- 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} +304 -304
- 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} +62 -62
- 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} +6 -6
- 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} +17 -17
- package/dist/apps/{webhooks.d.ts.map → data/routes/webhooks.d.ts.map} +1 -1
- package/dist/apps/{webhooks.js → data/routes/webhooks.js} +38 -34
- package/dist/apps/data/routes/webhooks.js.map +1 -0
- package/dist/apps/docs/App.d.ts +29 -0
- package/dist/apps/docs/App.d.ts.map +1 -0
- package/dist/apps/docs/App.js +54 -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/docs/reference.d.ts +21 -0
- package/dist/apps/docs/reference.d.ts.map +1 -0
- package/dist/apps/docs/reference.js +112 -0
- package/dist/apps/docs/reference.js.map +1 -0
- package/dist/apps/index.d.ts +5 -0
- package/dist/apps/index.d.ts.map +1 -0
- package/dist/apps/index.js +5 -0
- package/dist/apps/index.js.map +1 -0
- package/dist/apps/mcp.d.ts +76 -0
- package/dist/apps/mcp.d.ts.map +1 -0
- package/dist/apps/mcp.js +188 -0
- package/dist/apps/mcp.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 +5262 -0
- package/dist/apps/platform/App.d.ts.map +1 -0
- package/dist/apps/platform/App.js +84 -0
- package/dist/apps/platform/App.js.map +1 -0
- package/dist/apps/platform/Billing.d.ts +279 -0
- package/dist/apps/platform/Billing.d.ts.map +1 -0
- package/dist/apps/platform/Billing.js +477 -0
- package/dist/apps/platform/Billing.js.map +1 -0
- package/dist/apps/platform/index.d.ts +3 -0
- package/dist/apps/platform/index.d.ts.map +1 -0
- package/dist/apps/platform/index.js +3 -0
- package/dist/apps/platform/index.js.map +1 -0
- package/dist/apps/platform/routes/api-keys.d.ts +973 -0
- package/dist/apps/platform/routes/api-keys.d.ts.map +1 -0
- package/dist/apps/platform/routes/api-keys.js +430 -0
- package/dist/apps/platform/routes/api-keys.js.map +1 -0
- package/dist/apps/platform/routes/billing.d.ts +1520 -0
- package/dist/apps/platform/routes/billing.d.ts.map +1 -0
- package/dist/apps/platform/routes/billing.js +795 -0
- package/dist/apps/platform/routes/billing.js.map +1 -0
- package/dist/apps/platform/routes/invitations.d.ts +851 -0
- package/dist/apps/platform/routes/invitations.d.ts.map +1 -0
- package/dist/apps/platform/routes/invitations.js +471 -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 +596 -0
- package/dist/apps/platform/routes/orgs.d.ts.map +1 -0
- package/dist/apps/platform/routes/orgs.js +276 -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/platform/routes/scopes.d.ts +107 -0
- package/dist/apps/platform/routes/scopes.d.ts.map +1 -0
- package/dist/apps/platform/routes/scopes.js +66 -0
- package/dist/apps/platform/routes/scopes.js.map +1 -0
- package/dist/apps/platform/routes/usage.d.ts +588 -0
- package/dist/apps/platform/routes/usage.d.ts.map +1 -0
- package/dist/apps/platform/routes/usage.js +391 -0
- package/dist/apps/platform/routes/usage.js.map +1 -0
- package/dist/apps/relay/App.d.ts +44 -0
- package/dist/apps/relay/App.d.ts.map +1 -0
- package/dist/apps/relay/App.js +211 -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 +194 -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/Cli.d.ts +9 -2
- package/dist/cli/Cli.d.ts.map +1 -1
- package/dist/cli/Cli.js +12 -4
- package/dist/cli/Cli.js.map +1 -1
- package/dist/cli/commands/admin.d.ts +5 -1
- package/dist/cli/commands/admin.d.ts.map +1 -1
- package/dist/cli/commands/admin.js +82 -3
- package/dist/cli/commands/admin.js.map +1 -1
- package/dist/db/Db.d.ts +17 -0
- package/dist/db/Db.d.ts.map +1 -1
- package/dist/db/Db.js +254 -0
- package/dist/db/Db.js.map +1 -1
- package/dist/db/Schema.d.ts +159 -0
- package/dist/db/Schema.d.ts.map +1 -1
- package/dist/db/Schema.js +187 -2
- package/dist/db/Schema.js.map +1 -1
- package/dist/db/tables/billingSettings.d.ts +55 -0
- package/dist/db/tables/billingSettings.d.ts.map +1 -0
- package/dist/db/tables/billingSettings.js +70 -0
- package/dist/db/tables/billingSettings.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 +48 -0
- package/dist/db/tables/organizations.d.ts.map +1 -1
- package/dist/db/tables/organizations.js +97 -2
- package/dist/db/tables/organizations.js.map +1 -1
- 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/requestUsage.d.ts +121 -0
- package/dist/db/tables/requestUsage.d.ts.map +1 -0
- package/dist/db/tables/requestUsage.js +150 -0
- package/dist/db/tables/requestUsage.js.map +1 -0
- package/dist/db/tables/sponsoredTransactions.d.ts +239 -0
- package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -0
- package/dist/db/tables/sponsoredTransactions.js +314 -0
- package/dist/db/tables/sponsoredTransactions.js.map +1 -0
- package/dist/db/tables/stripeCustomers.d.ts +81 -0
- package/dist/db/tables/stripeCustomers.d.ts.map +1 -0
- package/dist/db/tables/stripeCustomers.js +120 -0
- package/dist/db/tables/stripeCustomers.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/handlers/internal/executionError.d.ts.map +1 -1
- package/dist/handlers/internal/executionError.js +2 -0
- package/dist/handlers/internal/executionError.js.map +1 -1
- package/dist/handlers/internal/multisig.d.ts +2 -6
- package/dist/handlers/internal/multisig.d.ts.map +1 -1
- package/dist/handlers/internal/multisig.js +11 -36
- package/dist/handlers/internal/multisig.js.map +1 -1
- package/dist/handlers/internal/sponsorship.d.ts +35 -2
- package/dist/handlers/internal/sponsorship.d.ts.map +1 -1
- package/dist/handlers/internal/sponsorship.js +53 -14
- package/dist/handlers/internal/sponsorship.js.map +1 -1
- package/dist/handlers/internal/utils.d.ts +10 -3
- package/dist/handlers/internal/utils.d.ts.map +1 -1
- package/dist/handlers/internal/utils.js +14 -6
- package/dist/handlers/internal/utils.js.map +1 -1
- package/dist/handlers/relay.d.ts +6 -2
- package/dist/handlers/relay.d.ts.map +1 -1
- package/dist/handlers/relay.js +16 -2
- package/dist/handlers/relay.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/internal/Auth.d.ts +222 -6
- package/dist/internal/Auth.d.ts.map +1 -1
- package/dist/internal/Auth.js +625 -74
- package/dist/internal/Auth.js.map +1 -1
- package/dist/internal/Cache.d.ts.map +1 -1
- package/dist/internal/Cache.js +4 -1
- package/dist/internal/Cache.js.map +1 -1
- package/dist/internal/EdgeCache.d.ts +10 -0
- package/dist/internal/EdgeCache.d.ts.map +1 -1
- package/dist/internal/EdgeCache.js +39 -8
- package/dist/internal/EdgeCache.js.map +1 -1
- package/dist/internal/Fees.d.ts +27 -0
- package/dist/internal/Fees.d.ts.map +1 -0
- package/dist/internal/Fees.js +48 -0
- package/dist/internal/Fees.js.map +1 -0
- package/dist/internal/FundingRoutes.d.ts +183 -0
- package/dist/internal/FundingRoutes.d.ts.map +1 -0
- package/dist/internal/FundingRoutes.js +490 -0
- package/dist/internal/FundingRoutes.js.map +1 -0
- package/dist/internal/Id.d.ts +15 -0
- package/dist/internal/Id.d.ts.map +1 -0
- package/dist/internal/Id.js +22 -0
- package/dist/internal/Id.js.map +1 -0
- package/dist/internal/Log.d.ts +9 -3
- package/dist/internal/Log.d.ts.map +1 -1
- package/dist/internal/Log.js +7 -0
- package/dist/internal/Log.js.map +1 -1
- package/dist/internal/OpenApi.d.ts +10 -20
- package/dist/internal/OpenApi.d.ts.map +1 -1
- package/dist/internal/OpenApi.js +8 -58
- package/dist/internal/OpenApi.js.map +1 -1
- package/dist/internal/RateLimit.d.ts +25 -0
- package/dist/internal/RateLimit.d.ts.map +1 -1
- package/dist/internal/RateLimit.js +51 -0
- package/dist/internal/RateLimit.js.map +1 -1
- package/dist/internal/Response.d.ts +1 -1
- package/dist/internal/Response.d.ts.map +1 -1
- package/dist/internal/Schema.d.ts +8 -0
- package/dist/internal/Schema.d.ts.map +1 -1
- package/dist/internal/Schema.js +7 -0
- package/dist/internal/Schema.js.map +1 -1
- package/dist/internal/Store.d.ts +7 -1
- package/dist/internal/Store.d.ts.map +1 -1
- package/dist/internal/Store.js +16 -2
- package/dist/internal/Store.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 +1 -1
- package/dist/internal/Webhooks.d.ts.map +1 -1
- package/dist/internal/Webhooks.js +3 -3
- package/dist/internal/Webhooks.js.map +1 -1
- package/package.json +33 -6
- package/src/ApiKey.ts +10 -2
- package/src/ApiKeys.test.ts +155 -1
- package/src/ApiKeys.ts +203 -1
- package/src/App.test.ts +64 -75741
- package/src/App.ts +218 -420
- package/src/Client.test-d.ts +657 -15
- package/src/Scope.ts +26 -4
- package/src/Webhooks.ts +2 -2
- package/src/admin/apps/api-keys.test.ts +51 -0
- package/src/admin/apps/api-keys.ts +84 -0
- package/src/admin/apps/scopes.test.ts +11 -1
- package/src/admin/apps/scopes.ts +3 -0
- package/src/admin/apps/verified-tokens.ts +1 -1
- package/src/admin/ui/src/components/Page.tsx +2 -3
- package/src/admin/ui/src/routes/__root.tsx +2 -2
- package/src/admin/ui/src/routes/api-keys.tsx +2 -2
- package/src/admin/ui.generated.ts +13 -13
- package/src/analytics/Analytics.test.ts +527 -0
- package/src/analytics/Analytics.ts +318 -0
- package/src/analytics/tables/requestEvents.ts +523 -0
- package/src/apps/composition.test.ts +66 -0
- package/src/apps/data/App.test.ts +72 -0
- package/src/apps/data/App.ts +330 -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} +2 -2
- package/src/apps/{balances.ts → data/routes/balances.ts} +46 -12
- package/src/apps/{blocks.test.ts → data/routes/blocks.test.ts} +3 -3
- 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} +1 -1
- 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/data/routes/funding.test.ts +214 -0
- package/src/apps/data/routes/funding.ts +584 -0
- package/src/apps/{indexer.test.ts → data/routes/indexer.test.ts} +26 -5
- package/src/apps/{indexer.ts → data/routes/indexer.ts} +13 -10
- package/src/apps/{receipts.test.ts → data/routes/receipts.test.ts} +3 -3
- package/src/apps/{receipts.ts → data/routes/receipts.ts} +15 -22
- package/src/apps/{rpc.test.ts → data/routes/rpc.test.ts} +122 -7
- package/src/apps/{rpc.ts → data/routes/rpc.ts} +38 -8
- package/src/apps/{tokenlist.test.ts → data/routes/tokenlist.test.ts} +3 -3
- package/src/apps/{tokenlist.ts → data/routes/tokenlist.ts} +9 -9
- package/src/apps/{tokens.test.ts → data/routes/tokens.test.ts} +2 -2
- package/src/apps/{tokens.ts → data/routes/tokens.ts} +20 -20
- package/src/apps/{transactions.test.ts → data/routes/transactions.test.ts} +3 -3
- package/src/apps/{transactions.ts → data/routes/transactions.ts} +15 -15
- package/src/apps/{transfers.test.ts → data/routes/transfers.test.ts} +2 -2
- package/src/apps/{transfers.ts → data/routes/transfers.ts} +15 -15
- package/src/apps/{verified-tokens.test.ts → data/routes/verified-tokens.test.ts} +3 -3
- package/src/apps/{verified-tokens.ts → data/routes/verified-tokens.ts} +10 -10
- package/src/apps/{webhooks.test.ts → data/routes/webhooks.test.ts} +7 -7
- package/src/apps/{webhooks.ts → data/routes/webhooks.ts} +50 -43
- package/src/apps/docs/App.ts +65 -0
- package/src/apps/docs/index.ts +1 -0
- package/src/apps/docs/reference.ts +132 -0
- package/src/apps/index.ts +4 -0
- package/src/apps/mcp.test.ts +206 -0
- package/src/apps/mcp.ts +250 -0
- package/src/apps/metadata.ts +23 -0
- package/src/apps/platform/App.ts +169 -0
- package/src/apps/platform/Billing.test.ts +530 -0
- package/src/apps/platform/Billing.ts +682 -0
- package/src/apps/platform/index.ts +2 -0
- package/src/apps/platform/routes/api-keys.test.ts +493 -0
- package/src/apps/platform/routes/api-keys.ts +543 -0
- package/src/apps/platform/routes/billing.test.ts +1100 -0
- package/src/apps/platform/routes/billing.ts +966 -0
- package/src/apps/platform/routes/invitations.test.ts +409 -0
- package/src/apps/platform/routes/invitations.ts +587 -0
- package/src/apps/platform/routes/me.test.ts +351 -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 +365 -0
- package/src/apps/platform/routes/projects.test.ts +181 -0
- package/src/apps/platform/routes/projects.ts +367 -0
- package/src/apps/platform/routes/scopes.test.ts +29 -0
- package/src/apps/platform/routes/scopes.ts +85 -0
- package/src/apps/platform/routes/usage.test.ts +422 -0
- package/src/apps/platform/routes/usage.ts +527 -0
- package/src/apps/relay/App.test.ts +933 -0
- package/src/apps/relay/App.ts +236 -0
- package/src/apps/relay/Sponsorships.test.ts +375 -0
- package/src/apps/relay/Sponsorships.ts +306 -0
- package/src/apps/relay/index.ts +2 -0
- package/src/cli/Cli.test.ts +25 -2
- package/src/cli/Cli.ts +18 -3
- package/src/cli/commands/admin.test.ts +83 -0
- package/src/cli/commands/admin.ts +91 -3
- package/src/db/Db.ts +296 -0
- package/src/db/Schema.ts +241 -2
- package/src/db/tables/billingSettings.test.ts +87 -0
- package/src/db/tables/billingSettings.ts +108 -0
- package/src/db/tables/invitations.ts +198 -0
- package/src/db/tables/memberships.ts +208 -0
- package/src/db/tables/organizations.test.ts +38 -2
- package/src/db/tables/organizations.ts +117 -2
- package/src/db/tables/projects.ts +107 -0
- package/src/db/tables/requestUsage.ts +231 -0
- package/src/db/tables/sponsoredTransactions.test.ts +325 -0
- package/src/db/tables/sponsoredTransactions.ts +464 -0
- package/src/db/tables/stripeCustomers.test.ts +109 -0
- package/src/db/tables/stripeCustomers.ts +162 -0
- package/src/db/tables/users.ts +85 -0
- package/src/handlers/internal/executionError.ts +2 -0
- package/src/handlers/internal/multisig.test.ts +84 -10
- package/src/handlers/internal/multisig.ts +15 -50
- package/src/handlers/internal/sponsorship.ts +106 -20
- package/src/handlers/internal/utils.ts +19 -7
- package/src/handlers/{internal/relay.localnet.test.ts → relay.test.ts} +2 -2
- package/src/handlers/relay.ts +23 -5
- package/src/index.ts +1 -0
- package/src/internal/Auth.test.ts +491 -25
- package/src/internal/Auth.ts +839 -84
- package/src/internal/Cache.ts +5 -1
- package/src/internal/Cursor.test.ts +3 -2
- package/src/internal/EdgeCache.test.ts +27 -4
- package/src/internal/EdgeCache.ts +47 -9
- package/src/internal/Fees.test.ts +32 -0
- package/src/internal/Fees.ts +51 -0
- package/src/internal/FundingRoutes.ts +583 -0
- package/src/internal/Id.test.ts +17 -0
- package/src/internal/Id.ts +24 -0
- package/src/internal/Log.test.ts +2 -1
- package/src/internal/Log.ts +16 -3
- package/src/internal/MetricSink.ts +1 -1
- package/src/internal/OpenApi.ts +12 -60
- package/src/internal/RateLimit.test.ts +84 -0
- package/src/internal/RateLimit.ts +70 -0
- package/src/internal/Response.ts +1 -1
- package/src/internal/Schema.ts +8 -0
- package/src/internal/Store.test.ts +52 -0
- package/src/internal/Store.ts +24 -5
- package/src/internal/Tidx.local.test.ts +1 -1
- package/src/internal/VerifiedTokens.test.ts +1 -1
- package/src/internal/WebhookDestination.test.ts +1 -1
- package/src/internal/Webhooks.test.ts +7 -7
- package/src/internal/Webhooks.ts +4 -4
- 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/src/handlers/internal/multisig.localnet.test.ts +0 -146
- /package/src/{internal/OpenApi.fonts.ts → apps/docs/fonts.ts} +0 -0
- /package/src/{internal/OpenApi.logo.ts → apps/docs/logo.ts} +0 -0
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
import { serve } from '@hono/node-server'
|
|
2
|
+
import * as Accounts from 'accounts/server'
|
|
3
|
+
import { exportJWK, generateKeyPair } from 'jose'
|
|
4
|
+
import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'
|
|
5
|
+
|
|
6
|
+
import * as TestApp from '../../../../test/App.js'
|
|
7
|
+
import * as Me from './me.js'
|
|
8
|
+
|
|
9
|
+
/** Origin pinned for SIWE domain binding; Hono test requests use this host. */
|
|
10
|
+
const origin = 'http://localhost'
|
|
11
|
+
|
|
12
|
+
const superAdminSecret = 'tempo:sk:c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2'
|
|
13
|
+
|
|
14
|
+
function createApp() {
|
|
15
|
+
return TestApp.create({
|
|
16
|
+
auth: { keys: [TestApp.key], superAdmin: { secret: superAdminSecret } },
|
|
17
|
+
session: { wallet: { origin } },
|
|
18
|
+
})
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
describe('GET /me', () => {
|
|
22
|
+
test('returns the signed-in user', async () => {
|
|
23
|
+
const app = createApp()
|
|
24
|
+
const account = privateKeyToAccount(generatePrivateKey())
|
|
25
|
+
const { cookie, verifyResponse } = await TestApp.signIn(app, account)
|
|
26
|
+
|
|
27
|
+
expect(verifyResponse.status).toBe(200)
|
|
28
|
+
expect(cookie).toBeDefined()
|
|
29
|
+
|
|
30
|
+
const response = await app.request('/v1/me', { headers: { cookie: cookie! } })
|
|
31
|
+
expect(response.status).toBe(200)
|
|
32
|
+
const body = await TestApp.json(response, Me.schema.User)
|
|
33
|
+
expect(body.address).toBe(account.address.toLowerCase())
|
|
34
|
+
expect(body.id.startsWith('usr_')).toBe(true)
|
|
35
|
+
expect(body.email).toBeUndefined()
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
test('sign-in is idempotent per address', async () => {
|
|
39
|
+
const app = createApp()
|
|
40
|
+
const account = privateKeyToAccount(generatePrivateKey())
|
|
41
|
+
|
|
42
|
+
const first = await TestApp.signIn(app, account)
|
|
43
|
+
const firstMe = await app.request('/v1/me', { headers: { cookie: first.cookie! } })
|
|
44
|
+
const firstBody = await TestApp.json(firstMe, Me.schema.User)
|
|
45
|
+
|
|
46
|
+
const second = await TestApp.signIn(app, account)
|
|
47
|
+
const secondMe = await app.request('/v1/me', { headers: { cookie: second.cookie! } })
|
|
48
|
+
const secondBody = await TestApp.json(secondMe, Me.schema.User)
|
|
49
|
+
|
|
50
|
+
expect(secondBody.id).toBe(firstBody.id)
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
test('supports bearer-token sessions', async () => {
|
|
54
|
+
const app = createApp()
|
|
55
|
+
const account = privateKeyToAccount(generatePrivateKey())
|
|
56
|
+
const { verifyResponse } = await TestApp.signIn(app, account, { returnToken: true })
|
|
57
|
+
const { token } = (await verifyResponse.json()) as { token: string }
|
|
58
|
+
|
|
59
|
+
expect(typeof token).toBe('string')
|
|
60
|
+
// The bearer session token takes the API-key header path first; the failed
|
|
61
|
+
// key lookup must fall through to the session lane.
|
|
62
|
+
const response = await app.request('/v1/me', {
|
|
63
|
+
headers: { authorization: `Bearer ${token}` },
|
|
64
|
+
})
|
|
65
|
+
expect(response.status).toBe(200)
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
test('logout revokes the session', async () => {
|
|
69
|
+
const app = createApp()
|
|
70
|
+
const account = privateKeyToAccount(generatePrivateKey())
|
|
71
|
+
const { cookie } = await TestApp.signIn(app, account)
|
|
72
|
+
|
|
73
|
+
const logout = await app.request('/v1/auth/siwe/logout', {
|
|
74
|
+
headers: { cookie: cookie! },
|
|
75
|
+
method: 'POST',
|
|
76
|
+
})
|
|
77
|
+
expect(logout.status).toBe(204)
|
|
78
|
+
|
|
79
|
+
const response = await app.request('/v1/me', { headers: { cookie: cookie! } })
|
|
80
|
+
expect(response.status).toBe(401)
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
test('logout revokes bearer-token sessions', async () => {
|
|
84
|
+
const app = createApp()
|
|
85
|
+
const account = privateKeyToAccount(generatePrivateKey())
|
|
86
|
+
const { verifyResponse } = await TestApp.signIn(app, account, { returnToken: true })
|
|
87
|
+
const { token } = (await verifyResponse.json()) as { token: string }
|
|
88
|
+
|
|
89
|
+
const logout = await app.request('/v1/auth/siwe/logout', {
|
|
90
|
+
headers: { authorization: `Bearer ${token}` },
|
|
91
|
+
method: 'POST',
|
|
92
|
+
})
|
|
93
|
+
expect(logout.status).toBe(204)
|
|
94
|
+
|
|
95
|
+
const response = await app.request('/v1/me', {
|
|
96
|
+
headers: { authorization: `Bearer ${token}` },
|
|
97
|
+
})
|
|
98
|
+
expect(response.status).toBe(401)
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
test('rejects anonymous requests', async () => {
|
|
102
|
+
const app = createApp()
|
|
103
|
+
const response = await app.request('/v1/me')
|
|
104
|
+
expect(response.status).toBe(401)
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
test('rejects API keys', async () => {
|
|
108
|
+
const app = createApp()
|
|
109
|
+
const response = await app.request('/v1/me', {
|
|
110
|
+
headers: { 'tempo-api-key': TestApp.key.token },
|
|
111
|
+
})
|
|
112
|
+
expect(response.status).toBe(403)
|
|
113
|
+
const { requestId, ...body } = (await response.json()) as { requestId?: string }
|
|
114
|
+
expect(requestId).toBeDefined()
|
|
115
|
+
expect(body).toMatchInlineSnapshot(`
|
|
116
|
+
{
|
|
117
|
+
"error": {
|
|
118
|
+
"code": "forbidden",
|
|
119
|
+
"message": "API key not permitted for this route",
|
|
120
|
+
},
|
|
121
|
+
}
|
|
122
|
+
`)
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
test('rejects the super admin (no user row)', async () => {
|
|
126
|
+
const app = createApp()
|
|
127
|
+
const response = await app.request('/v1/me', {
|
|
128
|
+
headers: { 'tempo-api-key': superAdminSecret },
|
|
129
|
+
})
|
|
130
|
+
expect(response.status).toBe(403)
|
|
131
|
+
const { requestId, ...body } = (await response.json()) as { requestId?: string }
|
|
132
|
+
expect(requestId).toBeDefined()
|
|
133
|
+
expect(body).toMatchInlineSnapshot(`
|
|
134
|
+
{
|
|
135
|
+
"error": {
|
|
136
|
+
"code": "forbidden",
|
|
137
|
+
"message": "Session required",
|
|
138
|
+
},
|
|
139
|
+
}
|
|
140
|
+
`)
|
|
141
|
+
})
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
describe('sign-in email requirement', () => {
|
|
145
|
+
test('rejects sign-ins without an identity token', async () => {
|
|
146
|
+
const app = TestApp.create({
|
|
147
|
+
auth: { keys: [TestApp.key] },
|
|
148
|
+
session: { wallet: { origin, requireEmail: true } },
|
|
149
|
+
})
|
|
150
|
+
const account = privateKeyToAccount(generatePrivateKey())
|
|
151
|
+
const { cookie, verifyResponse } = await TestApp.signIn(app, account)
|
|
152
|
+
|
|
153
|
+
expect(verifyResponse.status).toBe(400)
|
|
154
|
+
expect(cookie).toBeUndefined()
|
|
155
|
+
expect(await verifyResponse.json()).toMatchInlineSnapshot(`
|
|
156
|
+
{
|
|
157
|
+
"error": "identity token required",
|
|
158
|
+
}
|
|
159
|
+
`)
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
test('folds the token-verified email onto the session', async () => {
|
|
163
|
+
const oidc = await createIssuer({ email: 'dev@example.com' })
|
|
164
|
+
try {
|
|
165
|
+
const app = TestApp.create({
|
|
166
|
+
auth: { keys: [TestApp.key] },
|
|
167
|
+
session: { wallet: { issuer: oidc.url, origin, requireEmail: true } },
|
|
168
|
+
})
|
|
169
|
+
const account = privateKeyToAccount(generatePrivateKey())
|
|
170
|
+
const { cookie, verifyResponse } = await TestApp.signIn(app, account, {
|
|
171
|
+
idToken: ({ nonce }) => oidc.mint({ address: account.address, nonce }),
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
expect(verifyResponse.status).toBe(200)
|
|
175
|
+
const response = await app.request('/v1/me', { headers: { cookie: cookie! } })
|
|
176
|
+
expect(response.status).toBe(200)
|
|
177
|
+
const body = await TestApp.json(response, Me.schema.User)
|
|
178
|
+
expect(body.email).toBe('dev@example.com')
|
|
179
|
+
} finally {
|
|
180
|
+
await oidc.close()
|
|
181
|
+
}
|
|
182
|
+
})
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
describe('sign-up email domains', () => {
|
|
186
|
+
test('allows new users on an allowlisted domain', async () => {
|
|
187
|
+
const oidc = await createIssuer({ email: 'dev@TEMPO.xyz' })
|
|
188
|
+
try {
|
|
189
|
+
const app = TestApp.create({
|
|
190
|
+
auth: { keys: [TestApp.key] },
|
|
191
|
+
session: { emailAllowlist: ['Tempo.XYZ'], wallet: { issuer: oidc.url, origin } },
|
|
192
|
+
})
|
|
193
|
+
const account = privateKeyToAccount(generatePrivateKey())
|
|
194
|
+
const { cookie, verifyResponse } = await TestApp.signIn(app, account, {
|
|
195
|
+
idToken: ({ nonce }) => oidc.mint({ address: account.address, nonce }),
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
expect(verifyResponse.status).toBe(200)
|
|
199
|
+
const response = await app.request('/v1/me', { headers: { cookie: cookie! } })
|
|
200
|
+
expect(response.status).toBe(200)
|
|
201
|
+
const body = await TestApp.json(response, Me.schema.User)
|
|
202
|
+
expect(body.email).toBe('dev@TEMPO.xyz')
|
|
203
|
+
} finally {
|
|
204
|
+
await oidc.close()
|
|
205
|
+
}
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
test('rejects new users on other domains', async () => {
|
|
209
|
+
const oidc = await createIssuer({ email: 'dev@example.com' })
|
|
210
|
+
try {
|
|
211
|
+
const app = TestApp.create({
|
|
212
|
+
auth: { keys: [TestApp.key] },
|
|
213
|
+
session: { emailAllowlist: ['tempo.xyz'], wallet: { issuer: oidc.url, origin } },
|
|
214
|
+
})
|
|
215
|
+
const account = privateKeyToAccount(generatePrivateKey())
|
|
216
|
+
const { cookie, verifyResponse } = await TestApp.signIn(app, account, {
|
|
217
|
+
idToken: ({ nonce }) => oidc.mint({ address: account.address, nonce }),
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
expect(verifyResponse.status).toBe(403)
|
|
221
|
+
expect(cookie).toBeUndefined()
|
|
222
|
+
expect(await verifyResponse.json()).toMatchInlineSnapshot(`
|
|
223
|
+
{
|
|
224
|
+
"error": "sign-ups are restricted to approved email domains or invited emails",
|
|
225
|
+
}
|
|
226
|
+
`)
|
|
227
|
+
} finally {
|
|
228
|
+
await oidc.close()
|
|
229
|
+
}
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
test('rejects new users without an identity token', async () => {
|
|
233
|
+
const app = TestApp.create({
|
|
234
|
+
auth: { keys: [TestApp.key] },
|
|
235
|
+
session: { emailAllowlist: ['tempo.xyz'], wallet: { origin } },
|
|
236
|
+
})
|
|
237
|
+
const account = privateKeyToAccount(generatePrivateKey())
|
|
238
|
+
const { cookie, verifyResponse } = await TestApp.signIn(app, account)
|
|
239
|
+
|
|
240
|
+
expect(verifyResponse.status).toBe(403)
|
|
241
|
+
expect(cookie).toBeUndefined()
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
test('allows invited emails regardless of domain', async () => {
|
|
245
|
+
const oidc = await createIssuer({ email: 'Invited@Example.com' })
|
|
246
|
+
try {
|
|
247
|
+
const db = TestApp.database()
|
|
248
|
+
|
|
249
|
+
// Owner signs up while sign-up is open and issues the invitation.
|
|
250
|
+
const open = TestApp.create({
|
|
251
|
+
auth: { keys: [TestApp.key] },
|
|
252
|
+
db,
|
|
253
|
+
session: { wallet: { origin } },
|
|
254
|
+
})
|
|
255
|
+
const owner = privateKeyToAccount(generatePrivateKey())
|
|
256
|
+
const { cookie } = await TestApp.signIn(open, owner)
|
|
257
|
+
const created = await open.request('/v1/orgs', {
|
|
258
|
+
body: JSON.stringify({ name: 'Acme' }),
|
|
259
|
+
headers: { 'content-type': 'application/json', cookie: cookie! },
|
|
260
|
+
method: 'POST',
|
|
261
|
+
})
|
|
262
|
+
const org = (await created.json()) as { id: string }
|
|
263
|
+
const invited = await open.request(`/v1/orgs/${org.id}/invitations`, {
|
|
264
|
+
body: JSON.stringify({ email: 'invited@example.com', role: 'member' }),
|
|
265
|
+
headers: { 'content-type': 'application/json', cookie: cookie! },
|
|
266
|
+
method: 'POST',
|
|
267
|
+
})
|
|
268
|
+
expect(invited.status).toBe(200)
|
|
269
|
+
|
|
270
|
+
const restricted = TestApp.create({
|
|
271
|
+
auth: { keys: [TestApp.key] },
|
|
272
|
+
db,
|
|
273
|
+
session: { emailAllowlist: ['tempo.xyz'], wallet: { issuer: oidc.url, origin } },
|
|
274
|
+
})
|
|
275
|
+
const account = privateKeyToAccount(generatePrivateKey())
|
|
276
|
+
const { verifyResponse } = await TestApp.signIn(restricted, account, {
|
|
277
|
+
idToken: ({ nonce }) => oidc.mint({ address: account.address, nonce }),
|
|
278
|
+
})
|
|
279
|
+
expect(verifyResponse.status).toBe(200)
|
|
280
|
+
} finally {
|
|
281
|
+
await oidc.close()
|
|
282
|
+
}
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
test('allows existing users regardless of domain', async () => {
|
|
286
|
+
const db = TestApp.database()
|
|
287
|
+
const account = privateKeyToAccount(generatePrivateKey())
|
|
288
|
+
|
|
289
|
+
// Row created before the restriction (open sign-up deployment).
|
|
290
|
+
const open = TestApp.create({
|
|
291
|
+
auth: { keys: [TestApp.key] },
|
|
292
|
+
db,
|
|
293
|
+
session: { wallet: { origin } },
|
|
294
|
+
})
|
|
295
|
+
const first = await TestApp.signIn(open, account)
|
|
296
|
+
expect(first.verifyResponse.status).toBe(200)
|
|
297
|
+
|
|
298
|
+
const restricted = TestApp.create({
|
|
299
|
+
auth: { keys: [TestApp.key] },
|
|
300
|
+
db,
|
|
301
|
+
session: { emailAllowlist: ['tempo.xyz'], wallet: { origin } },
|
|
302
|
+
})
|
|
303
|
+
const second = await TestApp.signIn(restricted, account)
|
|
304
|
+
expect(second.verifyResponse.status).toBe(200)
|
|
305
|
+
})
|
|
306
|
+
})
|
|
307
|
+
|
|
308
|
+
/** Local OIDC issuer standing in for the wallet: the SDK's own provider (discovery, JWKS, token mint). */
|
|
309
|
+
async function createIssuer(options: { email: string }) {
|
|
310
|
+
// Extractable: the provider takes key material as JWK strings.
|
|
311
|
+
const { privateKey, publicKey } = await generateKeyPair('EdDSA', { extractable: true })
|
|
312
|
+
// The provider pins its `issuer` URL at construction, but the ephemeral
|
|
313
|
+
// port is only known after bind — listen through a closure, assign after.
|
|
314
|
+
let provider!: ReturnType<typeof Accounts.Handler.oidcProvider>
|
|
315
|
+
type Listener = { close: () => Promise<void>; url: string }
|
|
316
|
+
const { close, url } = await new Promise<Listener>((resolve) => {
|
|
317
|
+
const server = serve({ fetch: (request) => provider.fetch(request), port: 0 }, (info) =>
|
|
318
|
+
resolve({
|
|
319
|
+
close: () => new Promise((done) => server.close(() => done())),
|
|
320
|
+
url: `http://127.0.0.1:${info.port}`,
|
|
321
|
+
}),
|
|
322
|
+
)
|
|
323
|
+
})
|
|
324
|
+
provider = Accounts.Handler.oidcProvider({
|
|
325
|
+
// Test-only: no `authenticate`, so the request body's `subject` is trusted.
|
|
326
|
+
getClaims: () => ({ email: options.email, email_verified: true }),
|
|
327
|
+
issuer: url,
|
|
328
|
+
publicKey: JSON.stringify(await exportJWK(publicKey)),
|
|
329
|
+
signingKey: JSON.stringify(await exportJWK(privateKey)),
|
|
330
|
+
})
|
|
331
|
+
return {
|
|
332
|
+
close,
|
|
333
|
+
/** Mints a wallet-style identity token bound to the SIWE nonce and signer. */
|
|
334
|
+
async mint(mintOptions: { address: string; nonce: string }) {
|
|
335
|
+
const response = await provider.fetch(
|
|
336
|
+
new Request(`${url}/token`, {
|
|
337
|
+
body: JSON.stringify({
|
|
338
|
+
audience: origin,
|
|
339
|
+
nonce: mintOptions.nonce,
|
|
340
|
+
subject: mintOptions.address,
|
|
341
|
+
}),
|
|
342
|
+
headers: { 'content-type': 'application/json' },
|
|
343
|
+
method: 'POST',
|
|
344
|
+
}),
|
|
345
|
+
)
|
|
346
|
+
const { idToken } = (await response.json()) as { idToken: string }
|
|
347
|
+
return idToken
|
|
348
|
+
},
|
|
349
|
+
url,
|
|
350
|
+
}
|
|
351
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { Hono } from 'hono'
|
|
2
|
+
import * as z from 'zod/mini'
|
|
3
|
+
|
|
4
|
+
import type * as App from '../../../App.js'
|
|
5
|
+
import * as Auth from '../../../internal/Auth.js'
|
|
6
|
+
import * as Db from '../../../db/Db.js'
|
|
7
|
+
import * as OpenApi from '../../../internal/OpenApi.js'
|
|
8
|
+
import * as Response from '../../../internal/Response.js'
|
|
9
|
+
import * as Schema from '../../../internal/Schema.js'
|
|
10
|
+
import * as Users from '../../../db/tables/users.js'
|
|
11
|
+
|
|
12
|
+
/** Zod schemas owned by the current-user resource. */
|
|
13
|
+
export namespace schema {
|
|
14
|
+
/** The signed-in developer. */
|
|
15
|
+
export const User = Schema.describe(
|
|
16
|
+
z.object({
|
|
17
|
+
address: z
|
|
18
|
+
.optional(z.string())
|
|
19
|
+
.check(
|
|
20
|
+
z.describe('Wallet address bound to this user, when wallet sign-in established it.'),
|
|
21
|
+
z.meta({ examples: ['0x0000000000000000000000000000000000000001'] }),
|
|
22
|
+
),
|
|
23
|
+
createdAt: z
|
|
24
|
+
.string()
|
|
25
|
+
.check(
|
|
26
|
+
z.describe('When the user was created (ISO 8601).'),
|
|
27
|
+
z.meta({ examples: ['2026-01-01T00:00:00.000Z'] }),
|
|
28
|
+
),
|
|
29
|
+
email: z
|
|
30
|
+
.optional(z.string())
|
|
31
|
+
.check(z.describe('Verified email from the wallet identity token, when present.')),
|
|
32
|
+
id: z
|
|
33
|
+
.string()
|
|
34
|
+
.check(
|
|
35
|
+
z.describe('Opaque user id (`usr_…`).'),
|
|
36
|
+
z.meta({ examples: ['usr_1a2b3c4d5e6f7g8h9j0k1m2n'] }),
|
|
37
|
+
),
|
|
38
|
+
updatedAt: z
|
|
39
|
+
.string()
|
|
40
|
+
.check(
|
|
41
|
+
z.describe('When the user was last updated (ISO 8601).'),
|
|
42
|
+
z.meta({ examples: ['2026-01-01T00:00:00.000Z'] }),
|
|
43
|
+
),
|
|
44
|
+
}),
|
|
45
|
+
'The signed-in developer.',
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Mounts the `/me` resource: the session's user. Session lane only — API keys
|
|
51
|
+
* identify workloads, not developers.
|
|
52
|
+
*/
|
|
53
|
+
export function me() {
|
|
54
|
+
return new Hono<App.Environment>().get(
|
|
55
|
+
'/v1/me',
|
|
56
|
+
Auth.policy({ session: true }),
|
|
57
|
+
OpenApi.describeRoute({
|
|
58
|
+
operationId: 'getMe',
|
|
59
|
+
responses: OpenApi.responses({
|
|
60
|
+
errors: {
|
|
61
|
+
403: 'The caller is not a signed-in user.',
|
|
62
|
+
404: 'No user exists for this session.',
|
|
63
|
+
},
|
|
64
|
+
success: { description: 'The authenticated user.', schema: schema.User },
|
|
65
|
+
}),
|
|
66
|
+
summary: 'Get current user',
|
|
67
|
+
tags: ['Users'],
|
|
68
|
+
}),
|
|
69
|
+
async (c) => {
|
|
70
|
+
if (Auth.narrowAccess) return Auth.superAdminAccessError(c)
|
|
71
|
+
|
|
72
|
+
// The session lane is the only lane here; `super_admin` bypasses lanes
|
|
73
|
+
// but has no user row, so it is rejected too.
|
|
74
|
+
const principal = Auth.getPrincipal(c)
|
|
75
|
+
if (principal?.type !== 'session')
|
|
76
|
+
return Response.error(c, {
|
|
77
|
+
code: 'forbidden',
|
|
78
|
+
message: 'Session required',
|
|
79
|
+
status: 403,
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
const db = Db.get(c.get('db'))
|
|
83
|
+
try {
|
|
84
|
+
const user = await Users.get(db, principal.id)
|
|
85
|
+
if (!user)
|
|
86
|
+
return Response.error(c, {
|
|
87
|
+
code: 'user_not_found',
|
|
88
|
+
message: 'User not found',
|
|
89
|
+
status: 404,
|
|
90
|
+
})
|
|
91
|
+
return c.json(
|
|
92
|
+
Response.validated(schema.User, {
|
|
93
|
+
...(user.address ? { address: user.address } : {}),
|
|
94
|
+
createdAt: user.createdAt,
|
|
95
|
+
...(user.email ? { email: user.email } : {}),
|
|
96
|
+
id: user.id,
|
|
97
|
+
updatedAt: user.updatedAt,
|
|
98
|
+
}),
|
|
99
|
+
200,
|
|
100
|
+
)
|
|
101
|
+
} catch (cause) {
|
|
102
|
+
return Response.upstream(c, cause)
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
)
|
|
106
|
+
}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'
|
|
2
|
+
|
|
3
|
+
import * as TestApp from '../../../../test/App.js'
|
|
4
|
+
import * as Memberships from '../../../db/tables/memberships.js'
|
|
5
|
+
import * as Members from './members.js'
|
|
6
|
+
import * as Orgs from './orgs.js'
|
|
7
|
+
|
|
8
|
+
/** Origin pinned for SIWE domain binding; Hono test requests use this host. */
|
|
9
|
+
const origin = 'http://localhost'
|
|
10
|
+
|
|
11
|
+
/** RequestInit for a JSON mutation carrying the session cookie. */
|
|
12
|
+
function json(method: 'PATCH' | 'POST', cookie: string, body: unknown) {
|
|
13
|
+
return {
|
|
14
|
+
body: JSON.stringify(body),
|
|
15
|
+
headers: { 'content-type': 'application/json', cookie },
|
|
16
|
+
method,
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function createApp(db = TestApp.database()) {
|
|
21
|
+
return TestApp.create({ db, session: { wallet: { origin } } })
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Signs in a fresh scripted account; returns its cookie and user id. */
|
|
25
|
+
async function session(app: ReturnType<typeof createApp>) {
|
|
26
|
+
const account = privateKeyToAccount(generatePrivateKey())
|
|
27
|
+
const { cookie } = await TestApp.signIn(app, account)
|
|
28
|
+
const me = await app.request('/v1/me', { headers: { cookie: cookie! } })
|
|
29
|
+
const { id } = (await me.json()) as { id: string }
|
|
30
|
+
return { cookie: cookie!, userId: id }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** An org owned by `owner`, with `member` seeded at the given role. */
|
|
34
|
+
async function team(
|
|
35
|
+
app: ReturnType<typeof createApp>,
|
|
36
|
+
db: ReturnType<typeof TestApp.database>,
|
|
37
|
+
role: Memberships.Role,
|
|
38
|
+
) {
|
|
39
|
+
const owner = await session(app)
|
|
40
|
+
const member = await session(app)
|
|
41
|
+
const created = await app.request('/v1/orgs', json('POST', owner.cookie, { name: 'Acme' }))
|
|
42
|
+
const org = await TestApp.json(created, Orgs.schema.Organization)
|
|
43
|
+
await Memberships.create(db, { orgId: org.id, role, userId: member.userId })
|
|
44
|
+
return { member, org, owner }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
describe('members', () => {
|
|
48
|
+
test('lists the team with roles and identity', async () => {
|
|
49
|
+
const db = TestApp.database()
|
|
50
|
+
const app = createApp(db)
|
|
51
|
+
const { member, org, owner } = await team(app, db, 'member')
|
|
52
|
+
|
|
53
|
+
const response = await app.request(`/v1/orgs/${org.id}/members`, {
|
|
54
|
+
headers: { cookie: member.cookie },
|
|
55
|
+
})
|
|
56
|
+
expect(response.status).toBe(200)
|
|
57
|
+
const body = await TestApp.json(response, Members.schema.listMembers.Response)
|
|
58
|
+
expect(body.data.map(({ role, userId }) => ({ role, userId }))).toEqual([
|
|
59
|
+
{ role: 'owner', userId: owner.userId },
|
|
60
|
+
{ role: 'member', userId: member.userId },
|
|
61
|
+
])
|
|
62
|
+
expect(body.data[0]!.address).toMatch(/^0x/)
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
test('tiers write access by role across the surface', async () => {
|
|
66
|
+
const db = TestApp.database()
|
|
67
|
+
const app = createApp(db)
|
|
68
|
+
const { member, org } = await team(app, db, 'member')
|
|
69
|
+
|
|
70
|
+
// Members read the org but cannot manage it or its projects.
|
|
71
|
+
const read = await app.request(`/v1/orgs/${org.id}`, { headers: { cookie: member.cookie } })
|
|
72
|
+
expect(read.status).toBe(200)
|
|
73
|
+
const rename = await app.request(`/v1/orgs/${org.id}`, json('PATCH', member.cookie, { name: 'Steal' })) // prettier-ignore
|
|
74
|
+
expect(rename.status).toBe(403)
|
|
75
|
+
const project = await app.request(`/v1/orgs/${org.id}/projects`, json('POST', member.cookie, { name: 'Nope' })) // prettier-ignore
|
|
76
|
+
expect(project.status).toBe(403)
|
|
77
|
+
|
|
78
|
+
// Admins manage projects but not the org itself.
|
|
79
|
+
const admin = await session(app)
|
|
80
|
+
await Memberships.create(db, { orgId: org.id, role: 'admin', userId: admin.userId })
|
|
81
|
+
const adminProject = await app.request(`/v1/orgs/${org.id}/projects`, json('POST', admin.cookie, { name: 'Checkout' })) // prettier-ignore
|
|
82
|
+
expect(adminProject.status).toBe(200)
|
|
83
|
+
const adminRename = await app.request(`/v1/orgs/${org.id}`, json('PATCH', admin.cookie, { name: 'Steal' })) // prettier-ignore
|
|
84
|
+
expect(adminRename.status).toBe(403)
|
|
85
|
+
const adminDelete = await app.request(`/v1/orgs/${org.id}`, {
|
|
86
|
+
headers: { cookie: admin.cookie },
|
|
87
|
+
method: 'DELETE',
|
|
88
|
+
})
|
|
89
|
+
expect(adminDelete.status).toBe(403)
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
test('owners change roles; the last owner cannot be demoted', async () => {
|
|
93
|
+
const db = TestApp.database()
|
|
94
|
+
const app = createApp(db)
|
|
95
|
+
const { member, org, owner } = await team(app, db, 'member')
|
|
96
|
+
|
|
97
|
+
const promoted = await app.request(
|
|
98
|
+
`/v1/orgs/${org.id}/members/${member.userId}`,
|
|
99
|
+
json('PATCH', owner.cookie, { role: 'admin' }),
|
|
100
|
+
)
|
|
101
|
+
expect(promoted.status).toBe(200)
|
|
102
|
+
expect((await TestApp.json(promoted, Members.schema.Member)).role).toBe('admin')
|
|
103
|
+
|
|
104
|
+
// A non-owner cannot change roles.
|
|
105
|
+
const foreign = await app.request(
|
|
106
|
+
`/v1/orgs/${org.id}/members/${owner.userId}`,
|
|
107
|
+
json('PATCH', member.cookie, { role: 'member' }),
|
|
108
|
+
)
|
|
109
|
+
expect(foreign.status).toBe(403)
|
|
110
|
+
|
|
111
|
+
const demoted = await app.request(
|
|
112
|
+
`/v1/orgs/${org.id}/members/${owner.userId}`,
|
|
113
|
+
json('PATCH', owner.cookie, { role: 'member' }),
|
|
114
|
+
)
|
|
115
|
+
expect(demoted.status).toBe(409)
|
|
116
|
+
const { requestId, ...body } = (await demoted.json()) as { requestId?: string }
|
|
117
|
+
expect(requestId).toBeDefined()
|
|
118
|
+
expect(body).toMatchInlineSnapshot(`
|
|
119
|
+
{
|
|
120
|
+
"error": {
|
|
121
|
+
"code": "last_owner",
|
|
122
|
+
"message": "The organization would be left without an owner",
|
|
123
|
+
},
|
|
124
|
+
}
|
|
125
|
+
`)
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
test('concurrent demotions cannot strip the last owner', async () => {
|
|
129
|
+
const db = TestApp.database()
|
|
130
|
+
const app = createApp(db)
|
|
131
|
+
const owner = await session(app)
|
|
132
|
+
const created = await app.request('/v1/orgs', json('POST', owner.cookie, { name: 'Acme' }))
|
|
133
|
+
const org = await TestApp.json(created, Orgs.schema.Organization)
|
|
134
|
+
// Two owners, each demoting the other at the same time: the guard must let
|
|
135
|
+
// exactly one through so the org is never left ownerless.
|
|
136
|
+
const second = await session(app)
|
|
137
|
+
await Memberships.create(db, { orgId: org.id, role: 'owner', userId: second.userId })
|
|
138
|
+
|
|
139
|
+
const [a, b] = await Promise.all([
|
|
140
|
+
app.request(`/v1/orgs/${org.id}/members/${owner.userId}`, json('PATCH', second.cookie, { role: 'member' })), // prettier-ignore
|
|
141
|
+
app.request(`/v1/orgs/${org.id}/members/${second.userId}`, json('PATCH', owner.cookie, { role: 'member' })), // prettier-ignore
|
|
142
|
+
])
|
|
143
|
+
expect([a.status, b.status].sort()).toEqual([200, 409])
|
|
144
|
+
const owners = (await Memberships.listByOrg(db, org.id)).filter(({ role }) => role === 'owner')
|
|
145
|
+
expect(owners).toHaveLength(1)
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
test('owners remove members; members remove only themselves', async () => {
|
|
149
|
+
const db = TestApp.database()
|
|
150
|
+
const app = createApp(db)
|
|
151
|
+
const { member, org, owner } = await team(app, db, 'member')
|
|
152
|
+
const other = await session(app)
|
|
153
|
+
await Memberships.create(db, { orgId: org.id, role: 'member', userId: other.userId })
|
|
154
|
+
|
|
155
|
+
// A member cannot remove someone else.
|
|
156
|
+
const foreign = await app.request(`/v1/orgs/${org.id}/members/${other.userId}`, {
|
|
157
|
+
headers: { cookie: member.cookie },
|
|
158
|
+
method: 'DELETE',
|
|
159
|
+
})
|
|
160
|
+
expect(foreign.status).toBe(403)
|
|
161
|
+
|
|
162
|
+
// A member removes themself (leaves).
|
|
163
|
+
const left = await app.request(`/v1/orgs/${org.id}/members/${member.userId}`, {
|
|
164
|
+
headers: { cookie: member.cookie },
|
|
165
|
+
method: 'DELETE',
|
|
166
|
+
})
|
|
167
|
+
expect(left.status).toBe(200)
|
|
168
|
+
const gone = await app.request(`/v1/orgs/${org.id}`, { headers: { cookie: member.cookie } })
|
|
169
|
+
expect(gone.status).toBe(404)
|
|
170
|
+
|
|
171
|
+
// The owner removes the other member; the last owner cannot leave.
|
|
172
|
+
const removed = await app.request(`/v1/orgs/${org.id}/members/${other.userId}`, {
|
|
173
|
+
headers: { cookie: owner.cookie },
|
|
174
|
+
method: 'DELETE',
|
|
175
|
+
})
|
|
176
|
+
expect(removed.status).toBe(200)
|
|
177
|
+
const lastOwner = await app.request(`/v1/orgs/${org.id}/members/${owner.userId}`, {
|
|
178
|
+
headers: { cookie: owner.cookie },
|
|
179
|
+
method: 'DELETE',
|
|
180
|
+
})
|
|
181
|
+
expect(lastOwner.status).toBe(409)
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
test('hides the team from foreign sessions', async () => {
|
|
185
|
+
const db = TestApp.database()
|
|
186
|
+
const app = createApp(db)
|
|
187
|
+
const { org } = await team(app, db, 'member')
|
|
188
|
+
|
|
189
|
+
const stranger = await session(app)
|
|
190
|
+
const response = await app.request(`/v1/orgs/${org.id}/members`, {
|
|
191
|
+
headers: { cookie: stranger.cookie },
|
|
192
|
+
})
|
|
193
|
+
expect(response.status).toBe(404)
|
|
194
|
+
})
|
|
195
|
+
})
|