tapimo 0.15.0 → 0.18.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 +137 -0
- package/dist/ApiKey.d.ts +1 -0
- package/dist/ApiKey.d.ts.map +1 -1
- package/dist/ApiKey.js +4 -2
- package/dist/ApiKey.js.map +1 -1
- package/dist/ApiKeys.d.ts +37 -11
- package/dist/ApiKeys.d.ts.map +1 -1
- package/dist/ApiKeys.js +163 -10
- package/dist/ApiKeys.js.map +1 -1
- package/dist/App.d.ts +13085 -11974
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js +46 -15
- package/dist/App.js.map +1 -1
- package/dist/Scope.d.ts +1 -1
- package/dist/Scope.d.ts.map +1 -1
- package/dist/Scope.js +1 -1
- package/dist/Scope.js.map +1 -1
- package/dist/admin/App.d.ts +896 -191
- package/dist/admin/App.d.ts.map +1 -1
- package/dist/admin/App.js +5 -1
- package/dist/admin/App.js.map +1 -1
- package/dist/admin/apps/api-keys.d.ts +33 -33
- package/dist/admin/apps/api-keys.d.ts.map +1 -1
- package/dist/admin/apps/api-keys.js +8 -3
- package/dist/admin/apps/api-keys.js.map +1 -1
- package/dist/admin/apps/earn-vaults.d.ts +66 -27
- package/dist/admin/apps/earn-vaults.d.ts.map +1 -1
- package/dist/admin/apps/earn-vaults.js +36 -7
- package/dist/admin/apps/earn-vaults.js.map +1 -1
- package/dist/admin/apps/organizations.d.ts +25 -25
- package/dist/admin/apps/organizations.d.ts.map +1 -1
- package/dist/admin/apps/organizations.js +5 -4
- package/dist/admin/apps/organizations.js.map +1 -1
- package/dist/admin/apps/route-policies.d.ts +632 -0
- package/dist/admin/apps/route-policies.d.ts.map +1 -0
- package/dist/admin/apps/route-policies.js +257 -0
- package/dist/admin/apps/route-policies.js.map +1 -0
- package/dist/admin/apps/routes-support.js +39 -3
- package/dist/admin/apps/routes-support.js.map +1 -1
- package/dist/admin/apps/routes.d.ts +454 -1
- package/dist/admin/apps/routes.d.ts.map +1 -1
- package/dist/admin/apps/routes.js +82 -1
- package/dist/admin/apps/routes.js.map +1 -1
- package/dist/admin/apps/verified-tokens.d.ts +10 -4
- package/dist/admin/apps/verified-tokens.d.ts.map +1 -1
- package/dist/admin/apps/verified-tokens.js +24 -4
- package/dist/admin/apps/verified-tokens.js.map +1 -1
- package/dist/admin/index.d.ts +0 -1
- package/dist/admin/index.d.ts.map +1 -1
- package/dist/admin/index.js +0 -1
- package/dist/admin/index.js.map +1 -1
- package/dist/admin/ui.generated.d.ts +1 -1
- package/dist/admin/ui.generated.d.ts.map +1 -1
- package/dist/admin/ui.generated.js +99 -103
- package/dist/admin/ui.generated.js.map +1 -1
- package/dist/analytics/Analytics.d.ts.map +1 -1
- package/dist/analytics/Analytics.js +65 -1
- package/dist/analytics/Analytics.js.map +1 -1
- package/dist/analytics/tables/requestEvents.d.ts +1 -1
- package/dist/analytics/tables/requestEvents.d.ts.map +1 -1
- package/dist/analytics/tables/requestEvents.js +58 -59
- package/dist/analytics/tables/requestEvents.js.map +1 -1
- package/dist/apps/data/App.d.ts +2709 -2670
- package/dist/apps/data/App.d.ts.map +1 -1
- package/dist/apps/data/routes/activities.d.ts +5 -5
- package/dist/apps/data/routes/activities.d.ts.map +1 -1
- package/dist/apps/data/routes/activities.js +50 -0
- package/dist/apps/data/routes/activities.js.map +1 -1
- package/dist/apps/data/routes/addresses.d.ts +248 -248
- package/dist/apps/data/routes/balances.d.ts +22 -22
- package/dist/apps/data/routes/balances.d.ts.map +1 -1
- package/dist/apps/data/routes/balances.js +5 -7
- package/dist/apps/data/routes/balances.js.map +1 -1
- package/dist/apps/data/routes/coingecko.d.ts +54 -54
- package/dist/apps/data/routes/earn.d.ts +103 -12
- package/dist/apps/data/routes/earn.d.ts.map +1 -1
- package/dist/apps/data/routes/earn.js +456 -220
- package/dist/apps/data/routes/earn.js.map +1 -1
- package/dist/apps/data/routes/exchanges.d.ts.map +1 -1
- package/dist/apps/data/routes/exchanges.js +267 -97
- package/dist/apps/data/routes/exchanges.js.map +1 -1
- package/dist/apps/data/routes/fee-amm.d.ts +1 -19
- package/dist/apps/data/routes/fee-amm.d.ts.map +1 -1
- package/dist/apps/data/routes/fee-amm.js +93 -56
- package/dist/apps/data/routes/fee-amm.js.map +1 -1
- package/dist/apps/data/routes/indexer.d.ts +10 -10
- package/dist/apps/data/routes/rpc.d.ts +30 -30
- package/dist/apps/data/routes/rpc.js +45 -11
- package/dist/apps/data/routes/rpc.js.map +1 -1
- package/dist/apps/data/routes/tokens.d.ts.map +1 -1
- package/dist/apps/data/routes/tokens.js +11 -6
- package/dist/apps/data/routes/tokens.js.map +1 -1
- package/dist/apps/data/routes/transactions.d.ts +2 -0
- package/dist/apps/data/routes/transactions.d.ts.map +1 -1
- package/dist/apps/data/routes/transactions.js +30 -22
- package/dist/apps/data/routes/transactions.js.map +1 -1
- package/dist/apps/data/routes/transfers.d.ts +12 -12
- package/dist/apps/data/routes/valuation.d.ts +30 -30
- package/dist/apps/data/routes/verified-tokens.d.ts +5 -5
- package/dist/apps/data/routes/webhooks.d.ts +1 -1
- package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
- package/dist/apps/data/routes/webhooks.js +18 -11
- package/dist/apps/data/routes/webhooks.js.map +1 -1
- package/dist/apps/data/routes/zones.d.ts +3 -3
- package/dist/apps/management/App.d.ts +3043 -2198
- package/dist/apps/management/App.d.ts.map +1 -1
- package/dist/apps/management/App.js +2 -0
- package/dist/apps/management/App.js.map +1 -1
- package/dist/apps/management/Billing.d.ts +2 -4
- package/dist/apps/management/Billing.d.ts.map +1 -1
- package/dist/apps/management/Billing.js +65 -26
- package/dist/apps/management/Billing.js.map +1 -1
- package/dist/apps/management/routes/api-keys.d.ts +16 -4
- package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
- package/dist/apps/management/routes/api-keys.js +70 -11
- package/dist/apps/management/routes/api-keys.js.map +1 -1
- package/dist/apps/management/routes/billing.d.ts +84 -84
- package/dist/apps/management/routes/cli.d.ts +912 -0
- package/dist/apps/management/routes/cli.d.ts.map +1 -0
- package/dist/apps/management/routes/cli.js +416 -0
- package/dist/apps/management/routes/cli.js.map +1 -0
- package/dist/apps/management/routes/faucet.d.ts +8 -8
- package/dist/apps/management/routes/invitations.d.ts +24 -24
- package/dist/apps/management/routes/invite-links.d.ts +66 -66
- package/dist/apps/management/routes/members.d.ts +16 -16
- package/dist/apps/management/routes/orgs.d.ts +20 -20
- package/dist/apps/management/routes/orgs.d.ts.map +1 -1
- package/dist/apps/management/routes/orgs.js +8 -2
- package/dist/apps/management/routes/orgs.js.map +1 -1
- package/dist/apps/management/routes/projects.d.ts +76 -76
- package/dist/apps/management/routes/usage.d.ts +8 -8
- package/dist/apps/management/routes/verified-token-requests.d.ts +42 -42
- package/dist/apps/management/routes/verified-token-requests.d.ts.map +1 -1
- package/dist/apps/management/routes/verified-token-requests.js +7 -9
- package/dist/apps/management/routes/verified-token-requests.js.map +1 -1
- package/dist/apps/management/routes/webhooks.d.ts +119 -119
- package/dist/apps/mcp.d.ts.map +1 -1
- package/dist/apps/mcp.js +16 -3
- package/dist/apps/mcp.js.map +1 -1
- package/dist/apps/mpp/internal/Policy.d.ts.map +1 -1
- package/dist/apps/mpp/internal/Policy.js +4 -2
- package/dist/apps/mpp/internal/Policy.js.map +1 -1
- package/dist/apps/mpp/internal/Sponsorship.d.ts.map +1 -1
- package/dist/apps/mpp/internal/Sponsorship.js +6 -4
- package/dist/apps/mpp/internal/Sponsorship.js.map +1 -1
- package/dist/apps/relay/App.d.ts +8 -0
- package/dist/apps/relay/App.d.ts.map +1 -1
- package/dist/apps/relay/App.js +16 -4
- package/dist/apps/relay/App.js.map +1 -1
- package/dist/apps/relay/Sponsorships.d.ts +4 -6
- package/dist/apps/relay/Sponsorships.d.ts.map +1 -1
- package/dist/apps/relay/Sponsorships.js +71 -60
- package/dist/apps/relay/Sponsorships.js.map +1 -1
- package/dist/apps/routes/Access.d.ts +53 -0
- package/dist/apps/routes/Access.d.ts.map +1 -1
- package/dist/apps/routes/Access.js +121 -0
- package/dist/apps/routes/Access.js.map +1 -1
- package/dist/apps/routes/App.d.ts +397 -158
- package/dist/apps/routes/App.d.ts.map +1 -1
- package/dist/apps/routes/App.js.map +1 -1
- package/dist/apps/routes/routes/deposit-addresses.d.ts +169 -61
- package/dist/apps/routes/routes/deposit-addresses.d.ts.map +1 -1
- package/dist/apps/routes/routes/deposit-addresses.js +217 -32
- package/dist/apps/routes/routes/deposit-addresses.js.map +1 -1
- package/dist/apps/routes/routes/quotes.d.ts +90 -0
- package/dist/apps/routes/routes/quotes.d.ts.map +1 -1
- package/dist/apps/routes/routes/quotes.js +101 -27
- package/dist/apps/routes/routes/quotes.js.map +1 -1
- package/dist/apps/routes/routes/transfers.d.ts +187 -135
- package/dist/apps/routes/routes/transfers.d.ts.map +1 -1
- package/dist/apps/routes/routes/transfers.js +182 -37
- package/dist/apps/routes/routes/transfers.js.map +1 -1
- package/dist/cli/ApiKeys.d.ts +120 -0
- package/dist/cli/ApiKeys.d.ts.map +1 -0
- package/dist/cli/ApiKeys.js +138 -0
- package/dist/cli/ApiKeys.js.map +1 -0
- package/dist/cli/Cli.d.ts +92 -34
- package/dist/cli/Cli.d.ts.map +1 -1
- package/dist/cli/Cli.js +188 -11
- package/dist/cli/Cli.js.map +1 -1
- package/dist/cli/Credentials.d.ts +95 -0
- package/dist/cli/Credentials.d.ts.map +1 -0
- package/dist/cli/Credentials.js +110 -0
- package/dist/cli/Credentials.js.map +1 -0
- package/dist/cli/Spec.d.ts +59 -0
- package/dist/cli/Spec.d.ts.map +1 -0
- package/dist/cli/Spec.js +77 -0
- package/dist/cli/Spec.js.map +1 -0
- package/dist/cli/bin.js +5 -1
- package/dist/cli/bin.js.map +1 -1
- package/dist/cli/commands/admin.d.ts +5 -5
- package/dist/cli/commands/admin.d.ts.map +1 -1
- package/dist/cli/commands/auth.d.ts +73 -0
- package/dist/cli/commands/auth.d.ts.map +1 -0
- package/dist/cli/commands/auth.js +204 -0
- package/dist/cli/commands/auth.js.map +1 -0
- package/dist/cli/commands/keys.d.ts +17 -0
- package/dist/cli/commands/keys.d.ts.map +1 -0
- package/dist/cli/commands/keys.js +109 -0
- package/dist/cli/commands/keys.js.map +1 -0
- package/dist/cloudflare.d.ts +27 -2
- package/dist/cloudflare.d.ts.map +1 -1
- package/dist/cloudflare.js +144 -17
- package/dist/cloudflare.js.map +1 -1
- package/dist/db/Db.d.ts +23 -0
- package/dist/db/Db.d.ts.map +1 -1
- package/dist/db/Db.js +418 -1
- package/dist/db/Db.js.map +1 -1
- package/dist/db/Schema.d.ts +44 -1
- package/dist/db/Schema.d.ts.map +1 -1
- package/dist/db/Schema.js +56 -3
- package/dist/db/Schema.js.map +1 -1
- package/dist/db/tables/apiKeyAdmissions.d.ts +28 -2
- package/dist/db/tables/apiKeyAdmissions.d.ts.map +1 -1
- package/dist/db/tables/apiKeyAdmissions.js +65 -9
- package/dist/db/tables/apiKeyAdmissions.js.map +1 -1
- package/dist/db/tables/apiKeyOwnerTombstones.d.ts +2 -0
- package/dist/db/tables/apiKeyOwnerTombstones.d.ts.map +1 -1
- package/dist/db/tables/apiKeyOwnerTombstones.js +2 -2
- package/dist/db/tables/apiKeyOwnerTombstones.js.map +1 -1
- package/dist/db/tables/authAccounts.d.ts.map +1 -1
- package/dist/db/tables/authAccounts.js +1 -0
- package/dist/db/tables/authAccounts.js.map +1 -1
- package/dist/db/tables/earnVaults.d.ts +5 -1
- package/dist/db/tables/earnVaults.d.ts.map +1 -1
- package/dist/db/tables/earnVaults.js +12 -1
- package/dist/db/tables/earnVaults.js.map +1 -1
- package/dist/db/tables/organizations.d.ts +4 -2
- package/dist/db/tables/organizations.d.ts.map +1 -1
- package/dist/db/tables/organizations.js +8 -34
- package/dist/db/tables/organizations.js.map +1 -1
- package/dist/db/tables/requestUsage.d.ts +5 -1
- package/dist/db/tables/requestUsage.d.ts.map +1 -1
- package/dist/db/tables/requestUsage.js +10 -2
- package/dist/db/tables/requestUsage.js.map +1 -1
- package/dist/db/tables/rewardEligibilityAssociations.d.ts +13 -0
- package/dist/db/tables/rewardEligibilityAssociations.d.ts.map +1 -1
- package/dist/db/tables/rewardEligibilityAssociations.js +11 -0
- package/dist/db/tables/rewardEligibilityAssociations.js.map +1 -1
- package/dist/db/tables/rewardRuns.d.ts.map +1 -1
- package/dist/db/tables/rewardRuns.js +4 -0
- package/dist/db/tables/rewardRuns.js.map +1 -1
- package/dist/db/tables/rewardTransactionAttempts.d.ts.map +1 -1
- package/dist/db/tables/rewardTransactionAttempts.js +2 -0
- package/dist/db/tables/rewardTransactionAttempts.js.map +1 -1
- package/dist/db/tables/routesDepositAddresses.d.ts +29 -0
- package/dist/db/tables/routesDepositAddresses.d.ts.map +1 -1
- package/dist/db/tables/routesDepositAddresses.js +84 -31
- package/dist/db/tables/routesDepositAddresses.js.map +1 -1
- package/dist/db/tables/routesDeposits.d.ts.map +1 -1
- package/dist/db/tables/routesDeposits.js +31 -21
- package/dist/db/tables/routesDeposits.js.map +1 -1
- package/dist/db/tables/routesIdempotency.d.ts.map +1 -1
- package/dist/db/tables/routesIdempotency.js +2 -0
- package/dist/db/tables/routesIdempotency.js.map +1 -1
- package/dist/db/tables/routesPolicies.d.ts +58 -0
- package/dist/db/tables/routesPolicies.d.ts.map +1 -0
- package/dist/db/tables/routesPolicies.js +198 -0
- package/dist/db/tables/routesPolicies.js.map +1 -0
- package/dist/db/tables/routesTransferSubsidies.d.ts +22 -0
- package/dist/db/tables/routesTransferSubsidies.d.ts.map +1 -1
- package/dist/db/tables/routesTransferSubsidies.js +41 -0
- package/dist/db/tables/routesTransferSubsidies.js.map +1 -1
- package/dist/db/tables/routesTransferSubsidyNonces.d.ts.map +1 -1
- package/dist/db/tables/routesTransferSubsidyNonces.js +12 -4
- package/dist/db/tables/routesTransferSubsidyNonces.js.map +1 -1
- package/dist/db/tables/routesTransfers.d.ts +26 -1
- package/dist/db/tables/routesTransfers.d.ts.map +1 -1
- package/dist/db/tables/routesTransfers.js +36 -6
- package/dist/db/tables/routesTransfers.js.map +1 -1
- package/dist/db/tables/sponsoredTransactions.d.ts +45 -13
- package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -1
- package/dist/db/tables/sponsoredTransactions.js +144 -33
- package/dist/db/tables/sponsoredTransactions.js.map +1 -1
- package/dist/db/tables/users.d.ts +2 -0
- package/dist/db/tables/users.d.ts.map +1 -1
- package/dist/db/tables/users.js +11 -0
- package/dist/db/tables/users.js.map +1 -1
- package/dist/db/tables/verifiedTokenRequests.d.ts +21 -4
- package/dist/db/tables/verifiedTokenRequests.d.ts.map +1 -1
- package/dist/db/tables/verifiedTokenRequests.js +65 -44
- package/dist/db/tables/verifiedTokenRequests.js.map +1 -1
- package/dist/handlers/internal/multisig.d.ts +1 -1
- package/dist/handlers/internal/multisig.d.ts.map +1 -1
- package/dist/handlers/internal/multisig.js +3 -2
- package/dist/handlers/internal/multisig.js.map +1 -1
- package/dist/handlers/relay.d.ts +2 -0
- package/dist/handlers/relay.d.ts.map +1 -1
- package/dist/handlers/relay.js +34 -15
- package/dist/handlers/relay.js.map +1 -1
- package/dist/internal/Auth.d.ts +73 -6
- package/dist/internal/Auth.d.ts.map +1 -1
- package/dist/internal/Auth.js +212 -85
- package/dist/internal/Auth.js.map +1 -1
- package/dist/internal/BetterAuth.d.ts +22 -0
- package/dist/internal/BetterAuth.d.ts.map +1 -1
- package/dist/internal/BetterAuth.js +17 -14
- package/dist/internal/BetterAuth.js.map +1 -1
- package/dist/internal/Cache.d.ts +5 -0
- package/dist/internal/Cache.d.ts.map +1 -1
- package/dist/internal/Cache.js +3 -1
- package/dist/internal/Cache.js.map +1 -1
- package/dist/internal/CliAuth.d.ts +243 -0
- package/dist/internal/CliAuth.d.ts.map +1 -0
- package/dist/internal/CliAuth.js +249 -0
- package/dist/internal/CliAuth.js.map +1 -0
- package/dist/internal/EarnSharePrices.d.ts +1 -1
- package/dist/internal/EarnSharePrices.d.ts.map +1 -1
- package/dist/internal/EarnSharePrices.js +7 -3
- package/dist/internal/EarnSharePrices.js.map +1 -1
- package/dist/internal/EarnVaults.d.ts +47 -0
- package/dist/internal/EarnVaults.d.ts.map +1 -1
- package/dist/internal/EarnVaults.js +188 -4
- package/dist/internal/EarnVaults.js.map +1 -1
- package/dist/internal/EarnWithdrawals.d.ts +73 -0
- package/dist/internal/EarnWithdrawals.d.ts.map +1 -0
- package/dist/internal/EarnWithdrawals.js +189 -0
- package/dist/internal/EarnWithdrawals.js.map +1 -0
- package/dist/internal/ExecutionContext.d.ts +21 -0
- package/dist/internal/ExecutionContext.d.ts.map +1 -0
- package/dist/internal/ExecutionContext.js +16 -0
- package/dist/internal/ExecutionContext.js.map +1 -0
- package/dist/internal/Log.d.ts +7 -3
- package/dist/internal/Log.d.ts.map +1 -1
- package/dist/internal/Log.js +163 -130
- package/dist/internal/Log.js.map +1 -1
- package/dist/internal/MetricSink.d.ts.map +1 -1
- package/dist/internal/MetricSink.js +26 -1
- package/dist/internal/MetricSink.js.map +1 -1
- package/dist/internal/RateLimit.d.ts +67 -0
- package/dist/internal/RateLimit.d.ts.map +1 -1
- package/dist/internal/RateLimit.js +174 -27
- package/dist/internal/RateLimit.js.map +1 -1
- package/dist/internal/Store.d.ts +6 -0
- package/dist/internal/Store.d.ts.map +1 -1
- package/dist/internal/Store.js +4 -5
- package/dist/internal/Store.js.map +1 -1
- package/dist/internal/Timing.d.ts +6 -0
- package/dist/internal/Timing.d.ts.map +1 -1
- package/dist/internal/Timing.js +21 -1
- package/dist/internal/Timing.js.map +1 -1
- package/dist/internal/Tron.d.ts +124 -0
- package/dist/internal/Tron.d.ts.map +1 -0
- package/dist/internal/Tron.js +202 -0
- package/dist/internal/Tron.js.map +1 -0
- package/dist/internal/Viem.d.ts +4 -0
- package/dist/internal/Viem.d.ts.map +1 -1
- package/dist/internal/Viem.js +25 -6
- package/dist/internal/Viem.js.map +1 -1
- package/dist/internal/rewards/Read.d.ts +2 -2
- package/dist/internal/rewards/Read.d.ts.map +1 -1
- package/dist/internal/rewards/Read.js +11 -2
- package/dist/internal/rewards/Read.js.map +1 -1
- package/dist/internal/routes/Action.d.ts +27 -0
- package/dist/internal/routes/Action.d.ts.map +1 -1
- package/dist/internal/routes/Action.js +49 -1
- package/dist/internal/routes/Action.js.map +1 -1
- package/dist/internal/routes/Chain.d.ts +2 -0
- package/dist/internal/routes/Chain.d.ts.map +1 -1
- package/dist/internal/routes/Chain.js +4 -0
- package/dist/internal/routes/Chain.js.map +1 -1
- package/dist/internal/routes/DepositAddress.d.ts.map +1 -1
- package/dist/internal/routes/DepositAddress.js +5 -0
- package/dist/internal/routes/DepositAddress.js.map +1 -1
- package/dist/internal/routes/Evidence.d.ts.map +1 -1
- package/dist/internal/routes/Evidence.js +6 -2
- package/dist/internal/routes/Evidence.js.map +1 -1
- package/dist/internal/routes/EvmTransferSettlement.d.ts +17 -0
- package/dist/internal/routes/EvmTransferSettlement.d.ts.map +1 -0
- package/dist/internal/routes/EvmTransferSettlement.js +196 -0
- package/dist/internal/routes/EvmTransferSettlement.js.map +1 -0
- package/dist/internal/routes/Policy.d.ts +161 -0
- package/dist/internal/routes/Policy.d.ts.map +1 -0
- package/dist/internal/routes/Policy.js +249 -0
- package/dist/internal/routes/Policy.js.map +1 -0
- package/dist/internal/routes/Provider.d.ts +66 -3
- package/dist/internal/routes/Provider.d.ts.map +1 -1
- package/dist/internal/routes/Provider.js +106 -27
- package/dist/internal/routes/Provider.js.map +1 -1
- package/dist/internal/routes/Quota.d.ts +57 -0
- package/dist/internal/routes/Quota.d.ts.map +1 -0
- package/dist/internal/routes/Quota.js +144 -0
- package/dist/internal/routes/Quota.js.map +1 -0
- package/dist/internal/routes/Reconciliation.d.ts.map +1 -1
- package/dist/internal/routes/Reconciliation.js +163 -47
- package/dist/internal/routes/Reconciliation.js.map +1 -1
- package/dist/internal/routes/SolanaTransferSettlement.d.ts +12 -0
- package/dist/internal/routes/SolanaTransferSettlement.d.ts.map +1 -0
- package/dist/internal/routes/SolanaTransferSettlement.js +220 -0
- package/dist/internal/routes/SolanaTransferSettlement.js.map +1 -0
- package/dist/internal/routes/SolanaTransferSubsidy.d.ts +172 -0
- package/dist/internal/routes/SolanaTransferSubsidy.d.ts.map +1 -0
- package/dist/internal/routes/SolanaTransferSubsidy.js +564 -0
- package/dist/internal/routes/SolanaTransferSubsidy.js.map +1 -0
- package/dist/internal/routes/Subsidy.d.ts +2 -2
- package/dist/internal/routes/Subsidy.d.ts.map +1 -1
- package/dist/internal/routes/Subsidy.js +2 -2
- package/dist/internal/routes/Subsidy.js.map +1 -1
- package/dist/internal/routes/Transfer.d.ts +229 -1
- package/dist/internal/routes/Transfer.d.ts.map +1 -1
- package/dist/internal/routes/Transfer.js +644 -7
- package/dist/internal/routes/Transfer.js.map +1 -1
- package/dist/internal/routes/TransferReconciliation.d.ts +6 -0
- package/dist/internal/routes/TransferReconciliation.d.ts.map +1 -1
- package/dist/internal/routes/TransferReconciliation.js +83 -159
- package/dist/internal/routes/TransferReconciliation.js.map +1 -1
- package/dist/internal/routes/TransferSettlement.d.ts +45 -0
- package/dist/internal/routes/TransferSettlement.d.ts.map +1 -0
- package/dist/internal/routes/TransferSettlement.js +2 -0
- package/dist/internal/routes/TransferSettlement.js.map +1 -0
- package/dist/internal/routes/TransferSubsidy.d.ts +23 -8
- package/dist/internal/routes/TransferSubsidy.d.ts.map +1 -1
- package/dist/internal/routes/TransferSubsidy.js +64 -5
- package/dist/internal/routes/TransferSubsidy.js.map +1 -1
- package/dist/internal/routes/TronTransferSettlement.d.ts +12 -0
- package/dist/internal/routes/TronTransferSettlement.d.ts.map +1 -0
- package/dist/internal/routes/TronTransferSettlement.js +201 -0
- package/dist/internal/routes/TronTransferSettlement.js.map +1 -0
- package/dist/internal/routes/TronTransferSubsidy.d.ts +174 -0
- package/dist/internal/routes/TronTransferSubsidy.d.ts.map +1 -0
- package/dist/internal/routes/TronTransferSubsidy.js +439 -0
- package/dist/internal/routes/TronTransferSubsidy.js.map +1 -0
- package/dist/internal/routes/index.d.ts +3 -0
- package/dist/internal/routes/index.d.ts.map +1 -1
- package/dist/internal/routes/index.js +3 -0
- package/dist/internal/routes/index.js.map +1 -1
- package/dist/internal/routes/providers/across.d.ts +3 -0
- package/dist/internal/routes/providers/across.d.ts.map +1 -1
- package/dist/internal/routes/providers/across.js +11 -0
- package/dist/internal/routes/providers/across.js.map +1 -1
- package/dist/internal/routes/providers/legacyMesh.d.ts +29 -0
- package/dist/internal/routes/providers/legacyMesh.d.ts.map +1 -0
- package/dist/internal/routes/providers/legacyMesh.js +474 -0
- package/dist/internal/routes/providers/legacyMesh.js.map +1 -0
- package/dist/internal/routes/providers/legacyMeshDelivery.d.ts +59 -0
- package/dist/internal/routes/providers/legacyMeshDelivery.d.ts.map +1 -0
- package/dist/internal/routes/providers/legacyMeshDelivery.js +229 -0
- package/dist/internal/routes/providers/legacyMeshDelivery.js.map +1 -0
- package/dist/internal/routes/providers/legacyMeshOutbound.d.ts +19 -0
- package/dist/internal/routes/providers/legacyMeshOutbound.d.ts.map +1 -0
- package/dist/internal/routes/providers/legacyMeshOutbound.js +457 -0
- package/dist/internal/routes/providers/legacyMeshOutbound.js.map +1 -0
- package/dist/internal/routes/providers/legacyMeshOutboundDelivery.d.ts +123 -0
- package/dist/internal/routes/providers/legacyMeshOutboundDelivery.d.ts.map +1 -0
- package/dist/internal/routes/providers/legacyMeshOutboundDelivery.js +333 -0
- package/dist/internal/routes/providers/legacyMeshOutboundDelivery.js.map +1 -0
- package/dist/internal/routes/providers/propAmm.d.ts +70 -0
- package/dist/internal/routes/providers/propAmm.d.ts.map +1 -0
- package/dist/internal/routes/providers/propAmm.js +524 -0
- package/dist/internal/routes/providers/propAmm.js.map +1 -0
- package/dist/internal/routes/providers/relay.d.ts +4 -1
- package/dist/internal/routes/providers/relay.d.ts.map +1 -1
- package/dist/internal/routes/providers/relay.js +353 -42
- package/dist/internal/routes/providers/relay.js.map +1 -1
- package/dist/internal/routes/providers/rhino.d.ts +3 -0
- package/dist/internal/routes/providers/rhino.d.ts.map +1 -1
- package/dist/internal/routes/providers/rhino.js +11 -0
- package/dist/internal/routes/providers/rhino.js.map +1 -1
- package/dist/internal/routes/providers/squid.d.ts +3 -0
- package/dist/internal/routes/providers/squid.d.ts.map +1 -1
- package/dist/internal/routes/providers/squid.js +19 -0
- package/dist/internal/routes/providers/squid.js.map +1 -1
- package/dist/internal/routes/providers/stargate.d.ts.map +1 -1
- package/dist/internal/routes/providers/stargate.js +22 -9
- package/dist/internal/routes/providers/stargate.js.map +1 -1
- package/dist/internal/routes/providers/symbiosis.d.ts.map +1 -1
- package/dist/internal/routes/providers/symbiosis.js +13 -17
- package/dist/internal/routes/providers/symbiosis.js.map +1 -1
- package/dist/providers/index.d.ts +14 -0
- package/dist/providers/index.d.ts.map +1 -1
- package/dist/providers/index.js +6 -0
- package/dist/providers/index.js.map +1 -1
- package/package.json +16 -6
- package/src/ApiKey.ts +7 -3
- package/src/ApiKeys.test.ts +652 -8
- package/src/ApiKeys.ts +191 -11
- package/src/App.test.ts +405 -18
- package/src/App.ts +51 -15
- package/src/Client.test-d.ts +157 -10
- package/src/Client.test.ts +4 -4
- package/src/Scope.ts +1 -2
- package/src/admin/App.ts +11 -5
- package/src/admin/apps/api-keys.test.ts +98 -2
- package/src/admin/apps/api-keys.ts +21 -12
- package/src/admin/apps/earn-vaults.test.ts +76 -7
- package/src/admin/apps/earn-vaults.ts +47 -8
- package/src/admin/apps/organizations.test.ts +147 -31
- package/src/admin/apps/organizations.ts +5 -4
- package/src/admin/apps/route-policies.test.ts +220 -0
- package/src/admin/apps/route-policies.ts +322 -0
- package/src/admin/apps/routes-support.test.ts +107 -2
- package/src/admin/apps/routes-support.ts +43 -3
- package/src/admin/apps/routes.test.ts +150 -0
- package/src/admin/apps/routes.ts +97 -1
- package/src/admin/apps/scopes.test.ts +1 -1
- package/src/admin/apps/verified-tokens.test.ts +32 -0
- package/src/admin/apps/verified-tokens.ts +30 -4
- package/src/admin/index.ts +0 -1
- package/src/admin/ui/plugin.ts +2 -1
- package/src/admin/ui/src/components/Dialog.tsx +1 -1
- package/src/admin/ui/src/components/EarnVaultForm.tsx +45 -0
- package/src/admin/ui/src/components/RoutePicker.tsx +79 -0
- package/src/admin/ui/src/lib/earn-vaults.ts +1 -1
- package/src/admin/ui/src/lib/route-policies.test.ts +452 -0
- package/src/admin/ui/src/lib/route-policies.ts +232 -0
- package/src/admin/ui/src/lib/routes.ts +34 -0
- package/src/admin/ui/src/lib/verified-tokens.ts +24 -9
- package/src/admin/ui/src/routeTree.gen.ts +21 -0
- package/src/admin/ui/src/routes/__root.tsx +1 -1
- package/src/admin/ui/src/routes/api-keys.tsx +21 -2
- package/src/admin/ui/src/routes/early-access.tsx +1 -0
- package/src/admin/ui/src/routes/earn.vaults.tsx +1 -0
- package/src/admin/ui/src/routes/earn.vaults_.$vaultId.edit.tsx +22 -0
- package/src/admin/ui/src/routes/earn.vaults_.new.tsx +10 -0
- package/src/admin/ui/src/routes/organizations.tsx +35 -4
- package/src/admin/ui/src/routes/organizations_.$orgId.tsx +18 -3
- package/src/admin/ui/src/routes/route-policies.tsx +678 -0
- package/src/admin/ui/src/routes/routes.tsx +85 -2
- package/src/admin/ui/src/routes/routes_.support.tsx +6 -22
- package/src/admin/ui/src/routes/subsidies.tsx +1 -0
- package/src/admin/ui/src/routes/verified-tokens.tsx +65 -20
- package/src/admin/ui.generated.ts +99 -104
- package/src/analytics/Analytics.test.ts +4 -50
- package/src/analytics/Analytics.ts +65 -1
- package/src/analytics/tables/requestEvents.test.ts +402 -1
- package/src/analytics/tables/requestEvents.ts +62 -93
- package/src/apps/data/App.test.ts +26 -0
- package/src/apps/data/routes/activities.test.ts +315 -14
- package/src/apps/data/routes/activities.ts +60 -0
- package/src/apps/data/routes/balances.ts +5 -7
- package/src/apps/data/routes/earn.bench.ts +18 -0
- package/src/apps/data/routes/earn.local.test.ts +507 -0
- package/src/apps/data/routes/earn.test.ts +405 -127
- package/src/apps/data/routes/earn.ts +660 -270
- package/src/apps/data/routes/exchanges.bench.ts +3 -1
- package/src/apps/data/routes/exchanges.test.ts +166 -2
- package/src/apps/data/routes/exchanges.ts +308 -101
- package/src/apps/data/routes/fee-amm.test.ts +153 -2
- package/src/apps/data/routes/fee-amm.ts +132 -62
- package/src/apps/data/routes/receipts.test.ts +21 -18
- package/src/apps/data/routes/rpc.test.ts +34 -2
- package/src/apps/data/routes/rpc.ts +47 -10
- package/src/apps/data/routes/tokens.provenance.test.ts +157 -0
- package/src/apps/data/routes/tokens.test.ts +4 -4
- package/src/apps/data/routes/tokens.ts +11 -6
- package/src/apps/data/routes/transactions.test.ts +29 -0
- package/src/apps/data/routes/transactions.ts +33 -22
- package/src/apps/data/routes/webhooks.scan.test.ts +29 -0
- package/src/apps/data/routes/webhooks.test.ts +18 -3
- package/src/apps/data/routes/webhooks.ts +27 -13
- package/src/apps/management/App.ts +4 -0
- package/src/apps/management/Billing.test.ts +253 -27
- package/src/apps/management/Billing.ts +83 -29
- package/src/apps/management/routes/api-keys.test.ts +1 -1
- package/src/apps/management/routes/api-keys.ts +85 -11
- package/src/apps/management/routes/billing.test.ts +68 -4
- package/src/apps/management/routes/cli.test.ts +370 -0
- package/src/apps/management/routes/cli.ts +533 -0
- package/src/apps/management/routes/me.test.ts +14 -4
- package/src/apps/management/routes/orgs.test.ts +108 -4
- package/src/apps/management/routes/orgs.ts +7 -2
- package/src/apps/management/routes/usage.test.ts +2 -2
- package/src/apps/management/routes/verified-token-requests.test.ts +16 -2
- package/src/apps/management/routes/verified-token-requests.ts +7 -10
- package/src/apps/mcp.test.ts +108 -25
- package/src/apps/mcp.ts +15 -3
- package/src/apps/mpp/internal/Policy.test.ts +51 -4
- package/src/apps/mpp/internal/Policy.ts +6 -2
- package/src/apps/mpp/internal/Sponsorship.test.ts +29 -0
- package/src/apps/mpp/internal/Sponsorship.ts +6 -4
- package/src/apps/relay/App.test.ts +85 -1
- package/src/apps/relay/App.ts +28 -4
- package/src/apps/relay/Sponsorships.test.ts +97 -1
- package/src/apps/relay/Sponsorships.ts +77 -66
- package/src/apps/routes/Access.test.ts +319 -0
- package/src/apps/routes/Access.ts +147 -0
- package/src/apps/routes/App.ts +10 -0
- package/src/apps/routes/routes/chains.test.ts +3 -2
- package/src/apps/routes/routes/deposit-addresses.test.ts +978 -34
- package/src/apps/routes/routes/deposit-addresses.ts +243 -32
- package/src/apps/routes/routes/propAmm.local.test.ts +951 -0
- package/src/apps/routes/routes/quotes.test.ts +81 -8
- package/src/apps/routes/routes/quotes.ts +109 -27
- package/src/apps/routes/routes/transfers.test.ts +252 -15
- package/src/apps/routes/routes/transfers.ts +212 -33
- package/src/cli/ApiKeys.test.ts +265 -0
- package/src/cli/ApiKeys.ts +189 -0
- package/src/cli/Cli.test.ts +188 -0
- package/src/cli/Cli.ts +280 -12
- package/src/cli/Credentials.test.ts +122 -0
- package/src/cli/Credentials.ts +144 -0
- package/src/cli/Spec.test.ts +133 -0
- package/src/cli/Spec.ts +132 -0
- package/src/cli/bin.test.ts +49 -0
- package/src/cli/bin.ts +5 -1
- package/src/cli/commands/auth.test.ts +138 -0
- package/src/cli/commands/auth.ts +254 -0
- package/src/cli/commands/keys.test.ts +68 -0
- package/src/cli/commands/keys.ts +125 -0
- package/src/cloudflare.ts +184 -36
- package/src/db/Db.test.ts +877 -0
- package/src/db/Db.ts +507 -1
- package/src/db/Schema.ts +75 -4
- package/src/db/tables/apiKeyAdmissions.test.ts +57 -0
- package/src/db/tables/apiKeyAdmissions.ts +101 -11
- package/src/db/tables/apiKeyOwnerTombstones.ts +2 -2
- package/src/db/tables/authAccounts.test.ts +30 -0
- package/src/db/tables/authAccounts.ts +1 -0
- package/src/db/tables/earnVaults.test.ts +34 -0
- package/src/db/tables/earnVaults.ts +17 -2
- package/src/db/tables/organizations.test.ts +148 -84
- package/src/db/tables/organizations.ts +11 -43
- package/src/db/tables/requestUsage.ts +13 -2
- package/src/db/tables/rewardCampaigns.test.ts +163 -0
- package/src/db/tables/rewardEligibilityAssociations.test.ts +14 -0
- package/src/db/tables/rewardEligibilityAssociations.ts +24 -0
- package/src/db/tables/rewardRuns.ts +4 -0
- package/src/db/tables/rewardTransactionAttempts.ts +2 -0
- package/src/db/tables/routesDepositAddresses.test.ts +263 -0
- package/src/db/tables/routesDepositAddresses.ts +118 -34
- package/src/db/tables/routesDeposits.test.ts +133 -0
- package/src/db/tables/routesDeposits.ts +47 -26
- package/src/db/tables/routesIdempotency.test.ts +80 -0
- package/src/db/tables/routesIdempotency.ts +2 -0
- package/src/db/tables/routesPolicies.test.ts +143 -0
- package/src/db/tables/routesPolicies.ts +251 -0
- package/src/db/tables/routesTransferSubsidies.ts +64 -0
- package/src/db/tables/routesTransferSubsidyNonces.ts +16 -4
- package/src/db/tables/routesTransfers.test.ts +183 -2
- package/src/db/tables/routesTransfers.ts +65 -6
- package/src/db/tables/sponsoredTransactions.test.ts +351 -76
- package/src/db/tables/sponsoredTransactions.ts +182 -38
- package/src/db/tables/users.test.ts +51 -0
- package/src/db/tables/users.ts +12 -0
- package/src/db/tables/verifiedTokenRequests.test.ts +39 -20
- package/src/db/tables/verifiedTokenRequests.ts +80 -44
- package/src/handlers/internal/multisig.store.test.ts +120 -0
- package/src/handlers/internal/multisig.test.ts +389 -0
- package/src/handlers/internal/multisig.ts +13 -10
- package/src/handlers/relay.test.ts +259 -83
- package/src/handlers/relay.ts +36 -17
- package/src/internal/Auth.test.ts +383 -8
- package/src/internal/Auth.ts +272 -99
- package/src/internal/BetterAuth.test.ts +58 -22
- package/src/internal/BetterAuth.ts +23 -17
- package/src/internal/Cache.ts +3 -1
- package/src/internal/CliAuth.test.ts +279 -0
- package/src/internal/CliAuth.ts +319 -0
- package/src/internal/EarnSharePrices.test.ts +25 -0
- package/src/internal/EarnSharePrices.ts +9 -2
- package/src/internal/EarnVaults.test.ts +67 -0
- package/src/internal/EarnVaults.ts +299 -4
- package/src/internal/EarnWithdrawals.test.ts +315 -0
- package/src/internal/EarnWithdrawals.ts +266 -0
- package/src/internal/ExecutionContext.ts +31 -0
- package/src/internal/Log.test.ts +210 -0
- package/src/internal/Log.ts +180 -142
- package/src/internal/MetricSink.test.ts +190 -15
- package/src/internal/MetricSink.ts +27 -1
- package/src/internal/RateLimit.test.ts +144 -8
- package/src/internal/RateLimit.ts +254 -26
- package/src/internal/Store.ts +4 -5
- package/src/internal/Timing.test.ts +32 -0
- package/src/internal/Timing.ts +20 -1
- package/src/internal/Tron.test-d.ts +22 -0
- package/src/internal/Tron.test.ts +94 -0
- package/src/internal/Tron.ts +289 -0
- package/src/internal/Viem.rpc.test.ts +76 -0
- package/src/internal/Viem.ts +29 -6
- package/src/internal/rewards/Read.test.ts +109 -0
- package/src/internal/rewards/Read.ts +13 -4
- package/src/internal/routes/Action.test.ts +70 -0
- package/src/internal/routes/Action.ts +76 -1
- package/src/internal/routes/Chain.test.ts +18 -0
- package/src/internal/routes/Chain.ts +5 -0
- package/src/internal/routes/DepositAddress.ts +5 -0
- package/src/internal/routes/Evidence.test.ts +64 -0
- package/src/internal/routes/Evidence.ts +11 -3
- package/src/internal/routes/EvmTransferSettlement.ts +213 -0
- package/src/internal/routes/Policy.test.ts +316 -0
- package/src/internal/routes/Policy.ts +399 -0
- package/src/internal/routes/Provider.test.ts +103 -2
- package/src/internal/routes/Provider.ts +149 -27
- package/src/internal/routes/Quota.test.ts +149 -0
- package/src/internal/routes/Quota.ts +226 -0
- package/src/internal/routes/Reconciliation.local.test.ts +285 -0
- package/src/internal/routes/Reconciliation.test.ts +371 -13
- package/src/internal/routes/Reconciliation.ts +193 -42
- package/src/internal/routes/SolanaTransferSettlement.ts +232 -0
- package/src/internal/routes/SolanaTransferSubsidy.test.ts +471 -0
- package/src/internal/routes/SolanaTransferSubsidy.ts +760 -0
- package/src/internal/routes/SourceObservation.test.ts +221 -0
- package/src/internal/routes/Subsidy.local.test.ts +46 -0
- package/src/internal/routes/Subsidy.ts +4 -2
- package/src/internal/routes/Transfer.solana.test.ts +389 -0
- package/src/internal/routes/Transfer.test.ts +130 -1
- package/src/internal/routes/Transfer.tron.test.ts +172 -0
- package/src/internal/routes/Transfer.ts +866 -8
- package/src/internal/routes/TransferReconciliation.test.ts +26 -4
- package/src/internal/routes/TransferReconciliation.ts +91 -166
- package/src/internal/routes/TransferSettlement.ts +48 -0
- package/src/internal/routes/TransferSubsidy.local.test.ts +143 -0
- package/src/internal/routes/TransferSubsidy.test.ts +34 -4
- package/src/internal/routes/TransferSubsidy.ts +87 -12
- package/src/internal/routes/TronTransferSettlement.ts +212 -0
- package/src/internal/routes/TronTransferSubsidy.test.ts +109 -0
- package/src/internal/routes/TronTransferSubsidy.ts +619 -0
- package/src/internal/routes/index.test.ts +3 -0
- package/src/internal/routes/index.ts +3 -0
- package/src/internal/routes/providers/across.ts +13 -0
- package/src/internal/routes/providers/index.test.ts +141 -0
- package/src/internal/routes/providers/legacyMesh.test.ts +484 -0
- package/src/internal/routes/providers/legacyMesh.ts +633 -0
- package/src/internal/routes/providers/legacyMeshDelivery.test.ts +306 -0
- package/src/internal/routes/providers/legacyMeshDelivery.ts +320 -0
- package/src/internal/routes/providers/legacyMeshOutbound.test.ts +401 -0
- package/src/internal/routes/providers/legacyMeshOutbound.ts +566 -0
- package/src/internal/routes/providers/legacyMeshOutboundDelivery.ts +473 -0
- package/src/internal/routes/providers/propAmm.ts +640 -0
- package/src/internal/routes/providers/relay.test.ts +1299 -18
- package/src/internal/routes/providers/relay.ts +424 -39
- package/src/internal/routes/providers/rhino.ts +14 -1
- package/src/internal/routes/providers/squid.ts +21 -0
- package/src/internal/routes/providers/stargate.test.ts +105 -1
- package/src/internal/routes/providers/stargate.ts +22 -9
- package/src/internal/routes/providers/symbiosis.ts +13 -47
- package/src/providers/index.test.ts +2 -0
- package/src/providers/index.ts +18 -0
- package/dist/admin/Access.d.ts +0 -39
- package/dist/admin/Access.d.ts.map +0 -1
- package/dist/admin/Access.js +0 -73
- package/dist/admin/Access.js.map +0 -1
- package/src/admin/Access.test.ts +0 -130
- package/src/admin/Access.ts +0 -85
package/src/ApiKeys.test.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
import { sql } from 'kysely'
|
|
2
|
+
|
|
1
3
|
import * as ApiKeys from './ApiKeys.js'
|
|
2
4
|
import * as ApiKeyAdmissions from './db/tables/apiKeyAdmissions.js'
|
|
3
5
|
import * as ApiKeyOwnerTombstones from './db/tables/apiKeyOwnerTombstones.js'
|
|
4
6
|
import * as ApiKeyRevocations from './db/tables/apiKeyRevocations.js'
|
|
5
7
|
import * as Organizations from './db/tables/organizations.js'
|
|
6
8
|
import * as Projects from './db/tables/projects.js'
|
|
9
|
+
import * as RoutesPolicies from './db/tables/routesPolicies.js'
|
|
7
10
|
import * as Store from './internal/Store.js'
|
|
8
11
|
import * as TestApp from '../test/App.js'
|
|
9
12
|
|
|
@@ -214,6 +217,234 @@ describe('markOwnerDeleting', () => {
|
|
|
214
217
|
})
|
|
215
218
|
|
|
216
219
|
describe('mintBounded', () => {
|
|
220
|
+
test.each(['routes:write', '*'])(
|
|
221
|
+
'rejects delegation after the locked source loses %s',
|
|
222
|
+
async (scope) => {
|
|
223
|
+
const db = TestApp.database()
|
|
224
|
+
const state = Store.memory()
|
|
225
|
+
const org = await Organizations.create(db, { name: 'Delegation race' })
|
|
226
|
+
const project = await Projects.create(db, { name: 'Delegation', orgId: org.id })
|
|
227
|
+
const { record: source } = await ApiKeys.mintBounded(db, state, {
|
|
228
|
+
orgId: org.id,
|
|
229
|
+
projectId: project.id,
|
|
230
|
+
scopes: [scope],
|
|
231
|
+
})
|
|
232
|
+
// The handler authorized this snapshot before a restrictive edit committed.
|
|
233
|
+
await ApiKeys.updateBounded(db, state, source.id, { scopes: ['routes:read'] })
|
|
234
|
+
await expect(
|
|
235
|
+
ApiKeys.mintBounded(
|
|
236
|
+
db,
|
|
237
|
+
state,
|
|
238
|
+
{
|
|
239
|
+
environment: source.environment,
|
|
240
|
+
orgId: source.orgId,
|
|
241
|
+
projectId: source.projectId,
|
|
242
|
+
scopes: ['routes:write'],
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
policySourceApiKeyId: source.id,
|
|
246
|
+
sourceScopes: ['routes:write'],
|
|
247
|
+
},
|
|
248
|
+
),
|
|
249
|
+
).rejects.toBeInstanceOf(RoutesPolicies.InvalidScopeError)
|
|
250
|
+
expect((await ApiKeys.listByOrg(state, org.id)).map(({ id }) => id)).toEqual([source.id])
|
|
251
|
+
},
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
test('copies current locked source restrictions instead of the earlier handler snapshot', async () => {
|
|
255
|
+
const db = TestApp.database()
|
|
256
|
+
const state = Store.memory()
|
|
257
|
+
const org = await Organizations.create(db, { name: 'Restriction race' })
|
|
258
|
+
const project = await Projects.create(db, { name: 'Delegation', orgId: org.id })
|
|
259
|
+
const { record: source } = await ApiKeys.mintBounded(db, state, {
|
|
260
|
+
allowedIps: [],
|
|
261
|
+
expiresAt: new Date(Date.now() + 120_000).toISOString(),
|
|
262
|
+
orgId: org.id,
|
|
263
|
+
projectId: project.id,
|
|
264
|
+
rateLimits: { '*': { limit: 10_000, period: 'minute' } },
|
|
265
|
+
scopes: ['routes:write'],
|
|
266
|
+
})
|
|
267
|
+
const updated = await ApiKeys.updateBounded(db, state, source.id, {
|
|
268
|
+
allowedIps: ['203.0.113.7'],
|
|
269
|
+
rateLimits: { '*': { limit: 10, period: 'minute' } },
|
|
270
|
+
})
|
|
271
|
+
const { record } = await ApiKeys.mintBounded(
|
|
272
|
+
db,
|
|
273
|
+
state,
|
|
274
|
+
{
|
|
275
|
+
allowedIps: source.allowedIps,
|
|
276
|
+
environment: source.environment,
|
|
277
|
+
expiresAt: source.expiresAt,
|
|
278
|
+
orgId: source.orgId,
|
|
279
|
+
projectId: source.projectId,
|
|
280
|
+
rateLimits: source.rateLimits,
|
|
281
|
+
scopes: ['routes:write'],
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
policySourceApiKeyId: source.id,
|
|
285
|
+
sourceScopes: ['routes:write'],
|
|
286
|
+
},
|
|
287
|
+
)
|
|
288
|
+
expect(record.allowedIps).toEqual(updated!.allowedIps)
|
|
289
|
+
expect(record.expiresAt).toBe(updated!.expiresAt)
|
|
290
|
+
expect(record.rateLimits).toEqual(updated!.rateLimits)
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
test('releases an unpublished key admission when its KV index disappears before publication', async () => {
|
|
294
|
+
const database = TestApp.databaseFactory()
|
|
295
|
+
const db = database()
|
|
296
|
+
const state = Store.memory()
|
|
297
|
+
const organization = await Organizations.create(db, { name: 'Publication failure' })
|
|
298
|
+
await sql
|
|
299
|
+
.raw(`
|
|
300
|
+
CREATE FUNCTION pause_admission_commit() RETURNS trigger AS $$
|
|
301
|
+
BEGIN
|
|
302
|
+
PERFORM pg_advisory_xact_lock(12813337);
|
|
303
|
+
RETURN NEW;
|
|
304
|
+
END;
|
|
305
|
+
$$ LANGUAGE plpgsql;
|
|
306
|
+
CREATE CONSTRAINT TRIGGER pause_admission_commit
|
|
307
|
+
AFTER INSERT ON api_key_admissions DEFERRABLE INITIALLY DEFERRED
|
|
308
|
+
FOR EACH ROW EXECUTE FUNCTION pause_admission_commit();
|
|
309
|
+
`)
|
|
310
|
+
.execute(db.kysely)
|
|
311
|
+
const locked = Promise.withResolvers<void>()
|
|
312
|
+
const release = Promise.withResolvers<void>()
|
|
313
|
+
const blocker = database().transaction(async (tx) => {
|
|
314
|
+
await sql`SELECT pg_advisory_xact_lock(12813337)`.execute(tx.kysely)
|
|
315
|
+
locked.resolve()
|
|
316
|
+
await release.promise
|
|
317
|
+
})
|
|
318
|
+
await locked.promise
|
|
319
|
+
const minted = ApiKeys.mintBounded(database(), state, {
|
|
320
|
+
orgId: organization.id,
|
|
321
|
+
scopes: ['routes:write'],
|
|
322
|
+
})
|
|
323
|
+
const outcome = Promise.allSettled([minted])
|
|
324
|
+
try {
|
|
325
|
+
await expect
|
|
326
|
+
.poll(async () => {
|
|
327
|
+
const { rows } = await sql<{ waiting: boolean }>`select exists (
|
|
328
|
+
select 1 from pg_locks where locktype = 'advisory' and objid = 12813337 and not granted
|
|
329
|
+
) as waiting`.execute(db.kysely)
|
|
330
|
+
return rows[0]!.waiting
|
|
331
|
+
})
|
|
332
|
+
.toBe(true)
|
|
333
|
+
const [record] = await ApiKeys.listByOrg(state, organization.id)
|
|
334
|
+
expect(record?.scopes).toEqual([])
|
|
335
|
+
await state.delete(`apikey_id:${record!.id}`)
|
|
336
|
+
release.resolve()
|
|
337
|
+
expect(await outcome).toMatchObject([{ status: 'rejected' }])
|
|
338
|
+
expect(await ApiKeyAdmissions.exists(db, record!.id)).toBe(false)
|
|
339
|
+
expect(await ApiKeyRevocations.exists(db, record!.id)).toBe(true)
|
|
340
|
+
expect(await ApiKeyAdmissions.matches(db, record!)).toBe(false)
|
|
341
|
+
} finally {
|
|
342
|
+
release.resolve()
|
|
343
|
+
await blocker
|
|
344
|
+
await outcome
|
|
345
|
+
}
|
|
346
|
+
})
|
|
347
|
+
|
|
348
|
+
test.each(['current', 'stale'])(
|
|
349
|
+
'security: rejects a moved rotation source with %s KV',
|
|
350
|
+
async (kv) => {
|
|
351
|
+
const db = TestApp.database()
|
|
352
|
+
const state = Store.memory()
|
|
353
|
+
const org = await Organizations.create(db, { name: 'Rotation race' })
|
|
354
|
+
const first = await Projects.create(db, { name: 'Original', orgId: org.id })
|
|
355
|
+
const second = await Projects.create(db, { name: 'Reassigned', orgId: org.id })
|
|
356
|
+
const { record: source } = await ApiKeys.mintBounded(db, state, {
|
|
357
|
+
orgId: org.id,
|
|
358
|
+
projectId: first.id,
|
|
359
|
+
scopes: ['routes:write'],
|
|
360
|
+
})
|
|
361
|
+
const scope = {
|
|
362
|
+
apiKeyId: source.id,
|
|
363
|
+
environment: source.environment,
|
|
364
|
+
orgId: source.orgId,
|
|
365
|
+
projectId: source.projectId,
|
|
366
|
+
}
|
|
367
|
+
await RoutesPolicies.save(db, { scope, version: 0, policy: { access: [] } })
|
|
368
|
+
|
|
369
|
+
// The HTTP rotation handler already read and authorized `source`. A
|
|
370
|
+
// reassignment can finish before mintBounded takes its policy-copy lock.
|
|
371
|
+
await ApiKeys.updateBounded(db, state, source.id, { projectId: second.id })
|
|
372
|
+
expect(await RoutesPolicies.get(db, scope)).toMatchObject({ policy: {}, version: 2 })
|
|
373
|
+
expect((await ApiKeys.get(state, source.id))?.projectId).toBe(second.id)
|
|
374
|
+
// KV can lag SQL. Re-create the stale attribution through the KV write
|
|
375
|
+
// boundary; the authoritative admission must still reject the rotation.
|
|
376
|
+
if (kv === 'stale') await ApiKeys.update(state, source.id, { projectId: first.id })
|
|
377
|
+
|
|
378
|
+
// Copying the now-empty old scope would mint a usable key in the former
|
|
379
|
+
// project with unrestricted legacy route access instead of the old denial.
|
|
380
|
+
await expect(
|
|
381
|
+
ApiKeys.mintBounded(
|
|
382
|
+
db,
|
|
383
|
+
state,
|
|
384
|
+
{
|
|
385
|
+
environment: source.environment,
|
|
386
|
+
orgId: source.orgId,
|
|
387
|
+
projectId: source.projectId,
|
|
388
|
+
scopes: source.scopes,
|
|
389
|
+
},
|
|
390
|
+
{ policySourceApiKeyId: source.id },
|
|
391
|
+
).then(({ record }) => ({
|
|
392
|
+
inOriginalProject: record.projectId === first.id,
|
|
393
|
+
scopes: record.scopes,
|
|
394
|
+
})),
|
|
395
|
+
).rejects.toBeInstanceOf(RoutesPolicies.InvalidScopeError)
|
|
396
|
+
},
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
test('leaves a replacement unprivileged when policy copy rolls back at SQL commit', async () => {
|
|
400
|
+
const db = TestApp.database()
|
|
401
|
+
const state = Store.memory()
|
|
402
|
+
const organization = await Organizations.create(db, { name: 'Rotation boundary' })
|
|
403
|
+
const source = await ApiKeys.mintBounded(db, state, {
|
|
404
|
+
environment: 'sandbox',
|
|
405
|
+
orgId: organization.id,
|
|
406
|
+
scopes: ['data:read'],
|
|
407
|
+
})
|
|
408
|
+
const sourceScope = {
|
|
409
|
+
apiKeyId: source.record.id,
|
|
410
|
+
environment: 'sandbox',
|
|
411
|
+
orgId: organization.id,
|
|
412
|
+
} as const
|
|
413
|
+
await RoutesPolicies.save(db, {
|
|
414
|
+
policy: { maxAmount: '17' },
|
|
415
|
+
scope: sourceScope,
|
|
416
|
+
version: 0,
|
|
417
|
+
})
|
|
418
|
+
await installDeferredAdmissionFailure(db, organization.id)
|
|
419
|
+
|
|
420
|
+
await expect(
|
|
421
|
+
ApiKeys.mintBounded(
|
|
422
|
+
db,
|
|
423
|
+
state,
|
|
424
|
+
{
|
|
425
|
+
environment: 'sandbox',
|
|
426
|
+
orgId: organization.id,
|
|
427
|
+
scopes: ['routes:write'],
|
|
428
|
+
},
|
|
429
|
+
{ policySourceApiKeyId: source.record.id },
|
|
430
|
+
),
|
|
431
|
+
).rejects.toThrow('deferred admission failure')
|
|
432
|
+
|
|
433
|
+
const records = await ApiKeys.listByOrg(state, organization.id)
|
|
434
|
+
const replacement = records.find(({ id }) => id !== source.record.id)!
|
|
435
|
+
expect(records.find(({ id }) => id === source.record.id)?.scopes).toEqual(['data:read'])
|
|
436
|
+
expect(replacement.scopes).toEqual([])
|
|
437
|
+
await expect(ApiKeyAdmissions.matches(db, replacement)).resolves.toBe(false)
|
|
438
|
+
await expect(ApiKeyAdmissions.matches(db, source.record)).resolves.toBe(true)
|
|
439
|
+
expect(await RoutesPolicies.get(db, sourceScope)).toMatchObject({
|
|
440
|
+
policy: { maxAmount: '17' },
|
|
441
|
+
version: 1,
|
|
442
|
+
})
|
|
443
|
+
expect(
|
|
444
|
+
await RoutesPolicies.get(db, { ...sourceScope, apiKeyId: replacement.id }),
|
|
445
|
+
).toBeUndefined()
|
|
446
|
+
})
|
|
447
|
+
|
|
217
448
|
test('rejects an authoritative owner tombstone absent from KV', async () => {
|
|
218
449
|
const db = TestApp.database()
|
|
219
450
|
const state = Store.memory()
|
|
@@ -269,8 +500,8 @@ describe('mintBounded', () => {
|
|
|
269
500
|
await state.put(recordKey, raw)
|
|
270
501
|
await state.put(`apikey_org:${revoked.record.orgId}:${revoked.record.id}`, recordKey)
|
|
271
502
|
|
|
272
|
-
const stale =
|
|
273
|
-
expect(
|
|
503
|
+
const stale = revoked.record
|
|
504
|
+
expect(await ApiKeys.resolve(state, revoked.token)).toBeNull()
|
|
274
505
|
await expect(ApiKeyAdmissions.matches(db, stale!)).resolves.toBe(false)
|
|
275
506
|
await ApiKeys.mintBounded(db, state, {
|
|
276
507
|
orgId: revoked.record.orgId,
|
|
@@ -324,8 +555,8 @@ describe('mintBounded', () => {
|
|
|
324
555
|
await state.put(recordKey, raw)
|
|
325
556
|
await state.put(`apikey_org:${target.record.orgId}:${target.record.id}`, recordKey)
|
|
326
557
|
|
|
327
|
-
const stale =
|
|
328
|
-
expect(
|
|
558
|
+
const stale = target.record
|
|
559
|
+
expect(await ApiKeys.resolve(state, target.token)).toBeNull()
|
|
329
560
|
await expect(ApiKeyAdmissions.matches(db, stale!)).resolves.toBe(false)
|
|
330
561
|
})
|
|
331
562
|
|
|
@@ -354,11 +585,12 @@ describe('mintBounded', () => {
|
|
|
354
585
|
await state.delete(`apikey_id:${target.record.id}`)
|
|
355
586
|
|
|
356
587
|
await expect(ApiKeys.revokeBounded(db, state, target.record.id)).resolves.toBe(true)
|
|
357
|
-
const stale =
|
|
358
|
-
expect(
|
|
588
|
+
const stale = target.record
|
|
589
|
+
expect(await ApiKeys.resolve(state, target.token)).toBeNull()
|
|
359
590
|
await expect(ApiKeyAdmissions.matches(db, stale!)).resolves.toBe(false)
|
|
360
591
|
vi.advanceTimersByTime(ApiKeyRevocations.retentionMs + 1)
|
|
361
592
|
await expect(ApiKeyRevocations.prune(db)).resolves.toBe(0)
|
|
593
|
+
expect(await ApiKeys.resolve(state, target.token)).toBeNull()
|
|
362
594
|
await expect(ApiKeyAdmissions.matches(db, stale!)).resolves.toBe(false)
|
|
363
595
|
await expect(ApiKeyAdmissions.exists(db, target.record.id)).resolves.toBe(true)
|
|
364
596
|
await expect(
|
|
@@ -725,6 +957,249 @@ describe('mintBounded', () => {
|
|
|
725
957
|
})
|
|
726
958
|
|
|
727
959
|
describe('updateBounded', () => {
|
|
960
|
+
test('keeps post-commit scope publication serialized with another reassignment', async () => {
|
|
961
|
+
const database = TestApp.databaseFactory()
|
|
962
|
+
const inner = Store.memory()
|
|
963
|
+
const publishing = Promise.withResolvers<void>()
|
|
964
|
+
const release = Promise.withResolvers<void>()
|
|
965
|
+
const state = Store.from({
|
|
966
|
+
...inner,
|
|
967
|
+
async put(key, value, options = {}) {
|
|
968
|
+
if (key.startsWith('apikey:')) {
|
|
969
|
+
const record = JSON.parse(value) as ApiKeys.Record
|
|
970
|
+
if (record.orgId === 'org_first' && record.scopes.includes('*')) {
|
|
971
|
+
publishing.resolve()
|
|
972
|
+
await release.promise
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
await inner.put(key, value, options)
|
|
976
|
+
},
|
|
977
|
+
})
|
|
978
|
+
const { record, token } = await ApiKeys.mintBounded(database(), state, {
|
|
979
|
+
orgId: 'org_source',
|
|
980
|
+
scopes: ['*'],
|
|
981
|
+
})
|
|
982
|
+
const first = ApiKeys.updateBounded(database(), state, record.id, { orgId: 'org_first' })
|
|
983
|
+
const firstResult = Promise.allSettled([first])
|
|
984
|
+
await publishing.promise
|
|
985
|
+
let secondFinished = false
|
|
986
|
+
const second = ApiKeys.updateBounded(database(), state, record.id, {
|
|
987
|
+
orgId: 'org_second',
|
|
988
|
+
scopes: ['routes:read'],
|
|
989
|
+
})
|
|
990
|
+
const secondResult = Promise.allSettled([second]).then((result) => {
|
|
991
|
+
secondFinished = true
|
|
992
|
+
return result
|
|
993
|
+
})
|
|
994
|
+
try {
|
|
995
|
+
// Either the competing move finishes (the bug), or waits for the publishing key lock.
|
|
996
|
+
await expect
|
|
997
|
+
.poll(async () => {
|
|
998
|
+
const { rows } = await sql<{ waiting: boolean }>`select exists (
|
|
999
|
+
select 1 from pg_locks where locktype = 'advisory' and not granted
|
|
1000
|
+
and classid::bigint = ((hashtextextended(${`api-key-admission-id:${record.id}`}, 0) >> 32) & 4294967295)
|
|
1001
|
+
and objid::bigint = (hashtextextended(${`api-key-admission-id:${record.id}`}, 0) & 4294967295)
|
|
1002
|
+
) as waiting`.execute(database().kysely)
|
|
1003
|
+
return secondFinished || rows[0]!.waiting
|
|
1004
|
+
})
|
|
1005
|
+
.toBe(true)
|
|
1006
|
+
} finally {
|
|
1007
|
+
release.resolve()
|
|
1008
|
+
await Promise.all([firstResult, secondResult])
|
|
1009
|
+
}
|
|
1010
|
+
expect(await firstResult).toMatchObject([{ status: 'fulfilled' }])
|
|
1011
|
+
expect(await secondResult).toMatchObject([{ status: 'fulfilled' }])
|
|
1012
|
+
const current = await ApiKeys.resolve(inner, token)
|
|
1013
|
+
expect(current).toMatchObject({ orgId: 'org_second', scopes: ['routes:read'] })
|
|
1014
|
+
expect(await ApiKeyAdmissions.matches(database(), current!)).toBe(true)
|
|
1015
|
+
})
|
|
1016
|
+
|
|
1017
|
+
test.each([
|
|
1018
|
+
{ scopes: undefined, expected: ['*'] },
|
|
1019
|
+
{ scopes: [], expected: [] },
|
|
1020
|
+
{ scopes: ['routes:read'], expected: ['routes:read'] },
|
|
1021
|
+
] as const)(
|
|
1022
|
+
'publishes the latest committed scope intent after another move: %j',
|
|
1023
|
+
async ({ scopes, expected }) => {
|
|
1024
|
+
const db = TestApp.database()
|
|
1025
|
+
const state = Store.memory()
|
|
1026
|
+
const { record, token } = await ApiKeys.mintBounded(db, state, {
|
|
1027
|
+
orgId: 'org_source',
|
|
1028
|
+
scopes: [],
|
|
1029
|
+
})
|
|
1030
|
+
// Seed the durable boundary after a move commits but before its KV publication.
|
|
1031
|
+
await db.transaction(async (tx) => {
|
|
1032
|
+
await ApiKeyAdmissions.lock(tx, record.id)
|
|
1033
|
+
await ApiKeyAdmissions.setPendingScopes(tx, record.id, ['*'])
|
|
1034
|
+
})
|
|
1035
|
+
if (scopes !== undefined) {
|
|
1036
|
+
await ApiKeys.updateBounded(db, state, record.id, { scopes })
|
|
1037
|
+
expect((await ApiKeyAdmissions.get(db, record.id))?.pendingScopes).toBeNull()
|
|
1038
|
+
}
|
|
1039
|
+
await ApiKeys.updateBounded(db, state, record.id, { orgId: 'org_second' })
|
|
1040
|
+
await ApiKeys.updateBounded(db, state, record.id, { orgId: 'org_source' })
|
|
1041
|
+
expect(await ApiKeys.resolve(state, token)).toMatchObject({
|
|
1042
|
+
orgId: 'org_source',
|
|
1043
|
+
scopes: expected,
|
|
1044
|
+
})
|
|
1045
|
+
expect((await ApiKeyAdmissions.get(db, record.id))?.pendingScopes).toBeNull()
|
|
1046
|
+
},
|
|
1047
|
+
)
|
|
1048
|
+
|
|
1049
|
+
test.each(['reassignment', 'rotation'] as const)(
|
|
1050
|
+
'serializes %s with organization deletion without deadlock',
|
|
1051
|
+
async (operation) => {
|
|
1052
|
+
const database = TestApp.databaseFactory()
|
|
1053
|
+
const db = database()
|
|
1054
|
+
const state = Store.memory()
|
|
1055
|
+
const source = await Organizations.create(db, { name: 'Deleting owner' })
|
|
1056
|
+
const target = await Organizations.create(db, { name: 'New owner' })
|
|
1057
|
+
const { record } = await ApiKeys.mintBounded(db, state, {
|
|
1058
|
+
orgId: source.id,
|
|
1059
|
+
scopes: ['routes:read'],
|
|
1060
|
+
})
|
|
1061
|
+
await RoutesPolicies.save(db, {
|
|
1062
|
+
policy: { maxAmount: '0.07' },
|
|
1063
|
+
scope: { apiKeyId: record.id, environment: record.environment, orgId: source.id },
|
|
1064
|
+
version: 0,
|
|
1065
|
+
})
|
|
1066
|
+
const locked = Promise.withResolvers<void>()
|
|
1067
|
+
const release = Promise.withResolvers<void>()
|
|
1068
|
+
const blocker = database().transaction(async (tx) => {
|
|
1069
|
+
await sql`LOCK TABLE sponsored_transactions IN ACCESS EXCLUSIVE MODE`.execute(tx.kysely)
|
|
1070
|
+
locked.resolve()
|
|
1071
|
+
await release.promise
|
|
1072
|
+
})
|
|
1073
|
+
await locked.promise
|
|
1074
|
+
const deletion = Organizations.deleteOrganization(database(), source.id)
|
|
1075
|
+
const deletionResult = Promise.allSettled([deletion])
|
|
1076
|
+
try {
|
|
1077
|
+
// The deletion holds the subsidy lock while its first liability query waits on the table.
|
|
1078
|
+
await expect
|
|
1079
|
+
.poll(async () => {
|
|
1080
|
+
const { rows } = await sql<{ waiting: boolean }>`select exists (
|
|
1081
|
+
select 1 from pg_locks where relation = 'sponsored_transactions'::regclass and not granted
|
|
1082
|
+
) as waiting`.execute(db.kysely)
|
|
1083
|
+
return rows[0]!.waiting
|
|
1084
|
+
})
|
|
1085
|
+
.toBe(true)
|
|
1086
|
+
const mutation =
|
|
1087
|
+
operation === 'reassignment'
|
|
1088
|
+
? ApiKeys.updateBounded(database(), state, record.id, { orgId: target.id })
|
|
1089
|
+
: ApiKeys.mintBounded(
|
|
1090
|
+
database(),
|
|
1091
|
+
state,
|
|
1092
|
+
{
|
|
1093
|
+
orgId: source.id,
|
|
1094
|
+
scopes: ['routes:read'],
|
|
1095
|
+
},
|
|
1096
|
+
{ policySourceApiKeyId: record.id },
|
|
1097
|
+
)
|
|
1098
|
+
const mutationResult = Promise.allSettled([mutation])
|
|
1099
|
+
try {
|
|
1100
|
+
await expect
|
|
1101
|
+
.poll(async () => {
|
|
1102
|
+
const { rows } = await sql<{ waiting: boolean }>`select exists (
|
|
1103
|
+
select 1 from pg_locks where locktype = 'advisory' and not granted
|
|
1104
|
+
and classid::bigint = ((hashtextextended(${`routes-subsidy:${source.id}`}, 0) >> 32) & 4294967295)
|
|
1105
|
+
and objid::bigint = (hashtextextended(${`routes-subsidy:${source.id}`}, 0) & 4294967295)
|
|
1106
|
+
) as waiting`.execute(db.kysely)
|
|
1107
|
+
return rows[0]!.waiting
|
|
1108
|
+
})
|
|
1109
|
+
.toBe(true)
|
|
1110
|
+
} finally {
|
|
1111
|
+
release.resolve()
|
|
1112
|
+
}
|
|
1113
|
+
expect(await deletionResult).toMatchObject([{ status: 'fulfilled', value: true }])
|
|
1114
|
+
expect(await mutationResult).toMatchObject([
|
|
1115
|
+
{ status: 'rejected', reason: { name: 'ApiKeys.OwnerDeletedError' } },
|
|
1116
|
+
])
|
|
1117
|
+
expect(await ApiKeys.get(state, record.id)).toMatchObject({
|
|
1118
|
+
orgId: source.id,
|
|
1119
|
+
scopes: ['routes:read'],
|
|
1120
|
+
})
|
|
1121
|
+
expect(await ApiKeyAdmissions.get(db, record.id)).toBeUndefined()
|
|
1122
|
+
expect(await ApiKeyOwnerTombstones.isDeleted(db, { orgId: source.id })).toBe(true)
|
|
1123
|
+
} finally {
|
|
1124
|
+
release.resolve()
|
|
1125
|
+
await blocker
|
|
1126
|
+
await deletionResult
|
|
1127
|
+
}
|
|
1128
|
+
},
|
|
1129
|
+
)
|
|
1130
|
+
|
|
1131
|
+
test('preserves scopes on repeated reassignment after a former project is deleted', async () => {
|
|
1132
|
+
const db = TestApp.database()
|
|
1133
|
+
const state = Store.memory()
|
|
1134
|
+
const org = await Organizations.create(db, { name: 'Reassignment' })
|
|
1135
|
+
const first = await Projects.create(db, { name: 'First', orgId: org.id })
|
|
1136
|
+
const second = await Projects.create(db, { name: 'Second', orgId: org.id })
|
|
1137
|
+
const third = await Projects.create(db, { name: 'Third', orgId: org.id })
|
|
1138
|
+
const { record, token } = await ApiKeys.mintBounded(db, state, {
|
|
1139
|
+
orgId: org.id,
|
|
1140
|
+
projectId: first.id,
|
|
1141
|
+
scopes: ['routes:read'],
|
|
1142
|
+
})
|
|
1143
|
+
const scope = {
|
|
1144
|
+
apiKeyId: record.id,
|
|
1145
|
+
environment: record.environment,
|
|
1146
|
+
orgId: org.id,
|
|
1147
|
+
projectId: first.id,
|
|
1148
|
+
}
|
|
1149
|
+
await RoutesPolicies.save(db, { policy: { maxAmount: '7' }, scope, version: 0 })
|
|
1150
|
+
await ApiKeys.updateBounded(db, state, record.id, { projectId: second.id })
|
|
1151
|
+
expect(await RoutesPolicies.get(db, scope)).toMatchObject({ policy: {}, version: 2 })
|
|
1152
|
+
await Projects.deleteProject(db, first.id)
|
|
1153
|
+
|
|
1154
|
+
await expect(
|
|
1155
|
+
ApiKeys.updateBounded(db, state, record.id, { projectId: third.id }),
|
|
1156
|
+
).resolves.toMatchObject({ projectId: third.id, scopes: ['routes:read'] })
|
|
1157
|
+
expect(await ApiKeys.resolve(state, token)).toMatchObject({
|
|
1158
|
+
projectId: third.id,
|
|
1159
|
+
scopes: ['routes:read'],
|
|
1160
|
+
})
|
|
1161
|
+
expect(await RoutesPolicies.get(db, scope)).toMatchObject({ policy: {}, version: 3 })
|
|
1162
|
+
expect(await ApiKeyAdmissions.get(db, record.id)).toMatchObject({ projectId: third.id })
|
|
1163
|
+
})
|
|
1164
|
+
|
|
1165
|
+
test('leaves a reassigned key unprivileged when policy clear rolls back at SQL commit', async () => {
|
|
1166
|
+
const db = TestApp.database()
|
|
1167
|
+
const state = Store.memory()
|
|
1168
|
+
const sourceOwner = await Organizations.create(db, { name: 'Source boundary' })
|
|
1169
|
+
const destinationOwner = await Organizations.create(db, { name: 'Destination boundary' })
|
|
1170
|
+
const minted = await ApiKeys.mintBounded(db, state, {
|
|
1171
|
+
orgId: sourceOwner.id,
|
|
1172
|
+
scopes: ['data:read'],
|
|
1173
|
+
})
|
|
1174
|
+
const sourceScope = {
|
|
1175
|
+
apiKeyId: minted.record.id,
|
|
1176
|
+
environment: minted.record.environment,
|
|
1177
|
+
orgId: sourceOwner.id,
|
|
1178
|
+
} as const
|
|
1179
|
+
await RoutesPolicies.save(db, {
|
|
1180
|
+
policy: { maxAmount: '29' },
|
|
1181
|
+
scope: sourceScope,
|
|
1182
|
+
version: 0,
|
|
1183
|
+
})
|
|
1184
|
+
await installDeferredAdmissionFailure(db, destinationOwner.id)
|
|
1185
|
+
|
|
1186
|
+
await expect(
|
|
1187
|
+
ApiKeys.updateBounded(db, state, minted.record.id, { orgId: destinationOwner.id }),
|
|
1188
|
+
).rejects.toThrow('deferred admission failure')
|
|
1189
|
+
|
|
1190
|
+
const moved = (await ApiKeys.get(state, minted.record.id))!
|
|
1191
|
+
expect(moved).toMatchObject({ orgId: destinationOwner.id, scopes: [] })
|
|
1192
|
+
await expect(ApiKeyAdmissions.matches(db, moved)).resolves.toBe(false)
|
|
1193
|
+
expect(await ApiKeyAdmissions.get(db, minted.record.id)).toMatchObject({
|
|
1194
|
+
orgId: sourceOwner.id,
|
|
1195
|
+
projectId: null,
|
|
1196
|
+
})
|
|
1197
|
+
expect(await RoutesPolicies.get(db, sourceScope)).toMatchObject({
|
|
1198
|
+
policy: { maxAmount: '29' },
|
|
1199
|
+
version: 1,
|
|
1200
|
+
})
|
|
1201
|
+
})
|
|
1202
|
+
|
|
728
1203
|
test('rejects an update when the locked key no longer has the expected owner', async () => {
|
|
729
1204
|
const db = TestApp.database()
|
|
730
1205
|
const state = Store.memory()
|
|
@@ -1032,9 +1507,156 @@ describe('revoke', () => {
|
|
|
1032
1507
|
|
|
1033
1508
|
// The id index entry maps id -> record key; revoke uses it directly.
|
|
1034
1509
|
expect(await ApiKeys.revoke(state, record.id)).toBe(true)
|
|
1035
|
-
//
|
|
1510
|
+
// Only the durable revocation marker remains.
|
|
1036
1511
|
const { keys } = await state.list({ prefix: 'apikey' })
|
|
1037
|
-
expect(keys).toMatchInlineSnapshot(`
|
|
1512
|
+
expect(keys.map(({ name }) => name.replace(record.id, '<id>'))).toMatchInlineSnapshot(`
|
|
1513
|
+
[
|
|
1514
|
+
"apikey_revoked:<id>",
|
|
1515
|
+
]
|
|
1516
|
+
`)
|
|
1517
|
+
})
|
|
1518
|
+
})
|
|
1519
|
+
|
|
1520
|
+
describe('reconcileOwnerDeletion', () => {
|
|
1521
|
+
test('removes a fence only when SQL deletion did not commit', async () => {
|
|
1522
|
+
const db = TestApp.database()
|
|
1523
|
+
const state = Store.memory()
|
|
1524
|
+
const owner = { orgId: 'org_retained' }
|
|
1525
|
+
await ApiKeys.markOwnerDeleting(state, owner)
|
|
1526
|
+
|
|
1527
|
+
await ApiKeys.reconcileOwnerDeletion(db, state, owner)
|
|
1528
|
+
|
|
1529
|
+
expect(await ApiKeys.isOwnerDeleted(state, owner)).toBe(false)
|
|
1530
|
+
await ApiKeys.markOwnerDeleting(state, owner)
|
|
1531
|
+
await db.transaction((tx) => ApiKeyOwnerTombstones.markDeleted(tx, owner))
|
|
1532
|
+
await ApiKeys.reconcileOwnerDeletion(db, state, owner)
|
|
1533
|
+
expect(await ApiKeys.isOwnerDeleted(state, owner)).toBe(true)
|
|
1534
|
+
})
|
|
1535
|
+
|
|
1536
|
+
test('waits for a concurrent deletion before reconciling its fence', async () => {
|
|
1537
|
+
const database = TestApp.databaseFactory()
|
|
1538
|
+
const state = Store.memory()
|
|
1539
|
+
const owner = { orgId: 'org_concurrent_deletion' }
|
|
1540
|
+
const locked = Promise.withResolvers<void>()
|
|
1541
|
+
const commit = Promise.withResolvers<void>()
|
|
1542
|
+
const deletion = database().transaction(async (tx) => {
|
|
1543
|
+
await ApiKeyOwnerTombstones.markDeleted(tx, owner)
|
|
1544
|
+
await ApiKeys.markOwnerDeleting(state, owner)
|
|
1545
|
+
locked.resolve()
|
|
1546
|
+
await commit.promise
|
|
1547
|
+
})
|
|
1548
|
+
await locked.promise
|
|
1549
|
+
|
|
1550
|
+
const reconciliation = ApiKeys.reconcileOwnerDeletion(database(), state, owner)
|
|
1551
|
+
try {
|
|
1552
|
+
await vi.waitFor(async () => {
|
|
1553
|
+
const result = await sql<{ waiting: boolean }>`
|
|
1554
|
+
SELECT EXISTS (
|
|
1555
|
+
SELECT 1 FROM pg_locks WHERE locktype = 'advisory' AND NOT granted
|
|
1556
|
+
AND objid::bigint = (hashtextextended(${'organization:' + owner.orgId}, 0) & 4294967295)
|
|
1557
|
+
) AS waiting
|
|
1558
|
+
`.execute(database().kysely)
|
|
1559
|
+
expect(result.rows[0]?.waiting).toBe(true)
|
|
1560
|
+
})
|
|
1561
|
+
} finally {
|
|
1562
|
+
commit.resolve()
|
|
1563
|
+
}
|
|
1564
|
+
await Promise.all([deletion, reconciliation])
|
|
1565
|
+
|
|
1566
|
+
expect(await ApiKeys.isOwnerDeleted(state, owner)).toBe(true)
|
|
1567
|
+
})
|
|
1568
|
+
})
|
|
1569
|
+
|
|
1570
|
+
describe('isFenced', () => {
|
|
1571
|
+
test('reports revocation and organization or project deletion in one read', async () => {
|
|
1572
|
+
const state = Store.memory()
|
|
1573
|
+
const key = { id: 'key_fenced', orgId: 'org_fenced', projectId: 'prj_fenced' }
|
|
1574
|
+
const sibling = { id: 'key_sibling', orgId: 'org_fenced', projectId: 'prj_sibling' }
|
|
1575
|
+
|
|
1576
|
+
expect(await ApiKeys.isFenced(state, key)).toBe(false)
|
|
1577
|
+
|
|
1578
|
+
await ApiKeys.markOwnerDeleting(state, { orgId: key.orgId, projectId: key.projectId })
|
|
1579
|
+
expect([await ApiKeys.isFenced(state, key), await ApiKeys.isFenced(state, sibling)]).toEqual([
|
|
1580
|
+
true,
|
|
1581
|
+
false,
|
|
1582
|
+
])
|
|
1583
|
+
|
|
1584
|
+
await ApiKeys.markOwnerDeleting(state, { orgId: key.orgId })
|
|
1585
|
+
expect(await ApiKeys.isFenced(state, sibling)).toBe(true)
|
|
1586
|
+
})
|
|
1587
|
+
|
|
1588
|
+
test('reports a revoked key without an owner fence', async () => {
|
|
1589
|
+
const state = Store.memory()
|
|
1590
|
+
const key = { id: 'key_revoked_only', orgId: 'org_live' }
|
|
1591
|
+
|
|
1592
|
+
await ApiKeys.revoke(state, key.id)
|
|
1593
|
+
|
|
1594
|
+
expect(await ApiKeys.isFenced(state, key)).toBe(true)
|
|
1595
|
+
expect(await ApiKeys.isFenced(state, { ...key, id: 'key_other' })).toBe(false)
|
|
1596
|
+
})
|
|
1597
|
+
})
|
|
1598
|
+
|
|
1599
|
+
describe('revokeBounded', () => {
|
|
1600
|
+
test.each(['missing', 'stale'])('fences a key with a %s ID index', async (index) => {
|
|
1601
|
+
const db = TestApp.database()
|
|
1602
|
+
const state = Store.memory()
|
|
1603
|
+
const { record, token } = await ApiKeys.mintBounded(db, state, {
|
|
1604
|
+
orgId: 'org_revocation',
|
|
1605
|
+
scopes: ['data:read'],
|
|
1606
|
+
})
|
|
1607
|
+
const recordKey = (await state.get(`apikey_id:${record.id}`))!
|
|
1608
|
+
if (index === 'missing') await state.delete(`apikey_id:${record.id}`)
|
|
1609
|
+
else await state.put(`apikey_id:${record.id}`, 'apikey:missing')
|
|
1610
|
+
|
|
1611
|
+
await expect(ApiKeys.revokeBounded(db, state, record.id)).resolves.toBe(true)
|
|
1612
|
+
|
|
1613
|
+
expect(await state.get(recordKey)).not.toBeNull()
|
|
1614
|
+
expect(await ApiKeys.resolve(state, token)).toBeNull()
|
|
1615
|
+
})
|
|
1616
|
+
|
|
1617
|
+
test('rejects a failed KV fence write and permits retry', async () => {
|
|
1618
|
+
const db = TestApp.database()
|
|
1619
|
+
const backing = Store.memory()
|
|
1620
|
+
const { record, token } = await ApiKeys.mintBounded(db, backing, {
|
|
1621
|
+
orgId: 'org_revocation',
|
|
1622
|
+
scopes: ['data:read'],
|
|
1623
|
+
})
|
|
1624
|
+
await backing.delete(`apikey_id:${record.id}`)
|
|
1625
|
+
const state = Store.from({
|
|
1626
|
+
...backing,
|
|
1627
|
+
async put(key, value, options) {
|
|
1628
|
+
if (key === `apikey_revoked:${record.id}`) throw new Error('KV write failed')
|
|
1629
|
+
await backing.put(key, value, options)
|
|
1630
|
+
},
|
|
1631
|
+
})
|
|
1632
|
+
|
|
1633
|
+
await expect(ApiKeys.revokeBounded(db, state, record.id)).rejects.toThrow('KV write failed')
|
|
1634
|
+
await expect(ApiKeyAdmissions.exists(db, record.id)).resolves.toBe(true)
|
|
1635
|
+
expect(await ApiKeys.resolve(backing, token)).not.toBeNull()
|
|
1636
|
+
|
|
1637
|
+
await expect(ApiKeys.revokeBounded(db, backing, record.id)).resolves.toBe(true)
|
|
1638
|
+
expect(await ApiKeys.resolve(backing, token)).toBeNull()
|
|
1639
|
+
})
|
|
1640
|
+
|
|
1641
|
+
test('keeps the KV fence when record cleanup fails', async () => {
|
|
1642
|
+
const db = TestApp.database()
|
|
1643
|
+
const backing = Store.memory()
|
|
1644
|
+
const { record, token } = await ApiKeys.mintBounded(db, backing, {
|
|
1645
|
+
orgId: 'org_revocation',
|
|
1646
|
+
scopes: ['data:read'],
|
|
1647
|
+
})
|
|
1648
|
+
const recordKey = (await backing.get(`apikey_id:${record.id}`))!
|
|
1649
|
+
const state = Store.from({
|
|
1650
|
+
...backing,
|
|
1651
|
+
async delete(key) {
|
|
1652
|
+
if (key === recordKey) throw new Error('KV delete failed')
|
|
1653
|
+
await backing.delete(key)
|
|
1654
|
+
},
|
|
1655
|
+
})
|
|
1656
|
+
|
|
1657
|
+
await expect(ApiKeys.revokeBounded(db, state, record.id)).rejects.toThrow('KV delete failed')
|
|
1658
|
+
expect(await backing.get(recordKey)).not.toBeNull()
|
|
1659
|
+
expect(await ApiKeys.resolve(backing, token)).toBeNull()
|
|
1038
1660
|
})
|
|
1039
1661
|
})
|
|
1040
1662
|
|
|
@@ -1435,3 +2057,25 @@ describe('backfill', () => {
|
|
|
1435
2057
|
expect(await ApiKeys.revoke(state, record.id)).toBe(true)
|
|
1436
2058
|
})
|
|
1437
2059
|
})
|
|
2060
|
+
|
|
2061
|
+
async function installDeferredAdmissionFailure(
|
|
2062
|
+
db: ReturnType<typeof TestApp.database>,
|
|
2063
|
+
orgId: string,
|
|
2064
|
+
) {
|
|
2065
|
+
await sql
|
|
2066
|
+
.raw(`
|
|
2067
|
+
CREATE FUNCTION fail_admission_at_commit() RETURNS trigger AS $$
|
|
2068
|
+
BEGIN
|
|
2069
|
+
IF NEW.org_id = '${orgId}' THEN
|
|
2070
|
+
RAISE EXCEPTION 'deferred admission failure';
|
|
2071
|
+
END IF;
|
|
2072
|
+
RETURN NEW;
|
|
2073
|
+
END;
|
|
2074
|
+
$$ LANGUAGE plpgsql;
|
|
2075
|
+
CREATE CONSTRAINT TRIGGER fail_admission_at_commit
|
|
2076
|
+
AFTER INSERT OR UPDATE ON api_key_admissions
|
|
2077
|
+
DEFERRABLE INITIALLY DEFERRED
|
|
2078
|
+
FOR EACH ROW EXECUTE FUNCTION fail_admission_at_commit();
|
|
2079
|
+
`)
|
|
2080
|
+
.execute(db.kysely)
|
|
2081
|
+
}
|