tapimo 0.4.1 → 0.4.4
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 +1 -1
- package/dist/ApiKey.d.ts +1 -9
- package/dist/ApiKey.d.ts.map +1 -1
- package/dist/ApiKey.js +2 -3
- package/dist/ApiKey.js.map +1 -1
- package/dist/ApiKeys.d.ts +53 -27
- package/dist/ApiKeys.d.ts.map +1 -1
- package/dist/ApiKeys.js +32 -17
- package/dist/ApiKeys.js.map +1 -1
- package/dist/App.d.ts +1990 -1442
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js +23 -9
- package/dist/App.js.map +1 -1
- package/dist/Assets.d.ts +10 -0
- package/dist/Assets.d.ts.map +1 -1
- package/dist/Assets.js +4 -0
- package/dist/Assets.js.map +1 -1
- package/dist/Auth.d.ts +2 -1
- package/dist/Auth.d.ts.map +1 -1
- package/dist/Auth.js +2 -1
- package/dist/Auth.js.map +1 -1
- package/dist/Scope.d.ts +40 -15
- package/dist/Scope.d.ts.map +1 -1
- package/dist/Scope.js +44 -13
- package/dist/Scope.js.map +1 -1
- package/dist/admin/App.d.ts +560 -9
- package/dist/admin/App.d.ts.map +1 -1
- package/dist/admin/App.js +38 -6
- package/dist/admin/App.js.map +1 -1
- package/dist/admin/apps/api-keys.d.ts +10 -24
- package/dist/admin/apps/api-keys.d.ts.map +1 -1
- package/dist/admin/apps/api-keys.js +10 -3
- package/dist/admin/apps/api-keys.js.map +1 -1
- package/dist/admin/apps/organizations.d.ts +833 -0
- package/dist/admin/apps/organizations.d.ts.map +1 -0
- package/dist/admin/apps/organizations.js +733 -0
- package/dist/admin/apps/organizations.js.map +1 -0
- package/dist/admin/apps/scopes.d.ts +8 -0
- package/dist/admin/apps/scopes.d.ts.map +1 -1
- package/dist/admin/apps/verified-tokens.d.ts +57 -57
- package/dist/admin/ui.generated.d.ts +1 -1
- package/dist/admin/ui.generated.d.ts.map +1 -1
- package/dist/admin/ui.generated.js +53 -13
- package/dist/admin/ui.generated.js.map +1 -1
- package/dist/analytics/Analytics.d.ts +10 -0
- package/dist/analytics/Analytics.d.ts.map +1 -1
- package/dist/analytics/Analytics.js +8 -0
- package/dist/analytics/Analytics.js.map +1 -1
- package/dist/analytics/tables/requestEvents.d.ts +9 -0
- package/dist/analytics/tables/requestEvents.d.ts.map +1 -1
- package/dist/analytics/tables/requestEvents.js +14 -0
- package/dist/analytics/tables/requestEvents.js.map +1 -1
- package/dist/apps/data/App.d.ts +1304 -1304
- package/dist/apps/data/FundingRouteProviders.d.ts +4 -22
- package/dist/apps/data/FundingRouteProviders.d.ts.map +1 -1
- package/dist/apps/data/FundingRouteProviders.js +10 -120
- package/dist/apps/data/FundingRouteProviders.js.map +1 -1
- package/dist/apps/data/routes/activities.d.ts +38 -38
- package/dist/apps/data/routes/addresses.d.ts +24 -24
- package/dist/apps/data/routes/blocks.d.ts +39 -39
- package/dist/apps/data/routes/fee-amm.d.ts +20 -20
- package/dist/apps/data/routes/funding.js +7 -7
- package/dist/apps/data/routes/funding.js.map +1 -1
- package/dist/apps/data/routes/indexer.d.ts +5 -5
- package/dist/apps/data/routes/tokenlist.d.ts +12 -12
- package/dist/apps/data/routes/verified-tokens.d.ts +36 -36
- package/dist/apps/management/App.d.ts +808 -267
- package/dist/apps/management/App.d.ts.map +1 -1
- package/dist/apps/management/App.js +2 -2
- package/dist/apps/management/Billing.d.ts +3 -0
- package/dist/apps/management/Billing.d.ts.map +1 -1
- package/dist/apps/management/Billing.js +7 -2
- package/dist/apps/management/Billing.js.map +1 -1
- package/dist/apps/management/routes/api-keys.d.ts +116 -100
- package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
- package/dist/apps/management/routes/api-keys.js +41 -30
- package/dist/apps/management/routes/api-keys.js.map +1 -1
- package/dist/apps/management/routes/billing.d.ts +182 -50
- package/dist/apps/management/routes/billing.d.ts.map +1 -1
- package/dist/apps/management/routes/billing.js +14 -14
- package/dist/apps/management/routes/billing.js.map +1 -1
- package/dist/apps/management/routes/invitations.d.ts +93 -20
- package/dist/apps/management/routes/invitations.d.ts.map +1 -1
- package/dist/apps/management/routes/invitations.js +12 -15
- package/dist/apps/management/routes/invitations.js.map +1 -1
- package/dist/apps/management/routes/members.d.ts +89 -18
- package/dist/apps/management/routes/members.d.ts.map +1 -1
- package/dist/apps/management/routes/members.js +10 -10
- package/dist/apps/management/routes/members.js.map +1 -1
- package/dist/apps/management/routes/orgs.d.ts +100 -40
- package/dist/apps/management/routes/orgs.d.ts.map +1 -1
- package/dist/apps/management/routes/orgs.js +19 -19
- package/dist/apps/management/routes/orgs.js.map +1 -1
- package/dist/apps/management/routes/projects.d.ts +122 -33
- package/dist/apps/management/routes/projects.d.ts.map +1 -1
- package/dist/apps/management/routes/projects.js +12 -13
- package/dist/apps/management/routes/projects.js.map +1 -1
- package/dist/apps/management/routes/scopes.d.ts +4 -18
- package/dist/apps/management/routes/scopes.d.ts.map +1 -1
- package/dist/apps/management/routes/scopes.js +6 -7
- package/dist/apps/management/routes/scopes.js.map +1 -1
- package/dist/apps/management/routes/usage.d.ts +63 -19
- package/dist/apps/management/routes/usage.d.ts.map +1 -1
- package/dist/apps/management/routes/usage.js +9 -10
- package/dist/apps/management/routes/usage.js.map +1 -1
- package/dist/apps/mcp.d.ts.map +1 -1
- package/dist/apps/mcp.js +38 -4
- package/dist/apps/mcp.js.map +1 -1
- package/dist/apps/relay/App.d.ts +26 -4
- package/dist/apps/relay/App.d.ts.map +1 -1
- package/dist/apps/relay/App.js +17 -14
- package/dist/apps/relay/App.js.map +1 -1
- package/dist/cloudflare.d.ts +2 -0
- package/dist/cloudflare.d.ts.map +1 -1
- package/dist/cloudflare.js +4 -0
- package/dist/cloudflare.js.map +1 -1
- package/dist/db/Db.d.ts +5 -3
- package/dist/db/Db.d.ts.map +1 -1
- package/dist/db/Db.js +52 -0
- package/dist/db/Db.js.map +1 -1
- package/dist/db/Schema.d.ts +13 -0
- package/dist/db/Schema.d.ts.map +1 -1
- package/dist/db/Schema.js +16 -1
- package/dist/db/Schema.js.map +1 -1
- package/dist/db/tables/adminAuditLogs.d.ts +27 -0
- package/dist/db/tables/adminAuditLogs.d.ts.map +1 -0
- package/dist/db/tables/adminAuditLogs.js +19 -0
- package/dist/db/tables/adminAuditLogs.js.map +1 -0
- package/dist/db/tables/invitations.d.ts +1 -1
- package/dist/db/tables/invitations.d.ts.map +1 -1
- package/dist/db/tables/memberships.d.ts +32 -4
- package/dist/db/tables/memberships.d.ts.map +1 -1
- package/dist/db/tables/memberships.js +31 -0
- package/dist/db/tables/memberships.js.map +1 -1
- package/dist/db/tables/organizations.d.ts +43 -1
- package/dist/db/tables/organizations.d.ts.map +1 -1
- package/dist/db/tables/organizations.js +103 -0
- package/dist/db/tables/organizations.js.map +1 -1
- package/dist/db/tables/projects.d.ts +25 -0
- package/dist/db/tables/projects.d.ts.map +1 -1
- package/dist/db/tables/projects.js +22 -0
- package/dist/db/tables/projects.js.map +1 -1
- package/dist/db/tables/webhookDeliveries.d.ts +26 -0
- package/dist/db/tables/webhookDeliveries.d.ts.map +1 -1
- package/dist/db/tables/webhookDeliveries.js +37 -0
- package/dist/db/tables/webhookDeliveries.js.map +1 -1
- package/dist/internal/Auth.d.ts +46 -40
- package/dist/internal/Auth.d.ts.map +1 -1
- package/dist/internal/Auth.js +118 -69
- package/dist/internal/Auth.js.map +1 -1
- package/dist/internal/Cursor.d.ts +1 -1
- package/dist/internal/Cursor.d.ts.map +1 -1
- package/dist/internal/Cursor.js +4 -0
- package/dist/internal/Cursor.js.map +1 -1
- package/dist/internal/EdgeCache.d.ts.map +1 -1
- package/dist/internal/EdgeCache.js +2 -0
- package/dist/internal/EdgeCache.js.map +1 -1
- package/dist/internal/Errors.d.ts +6 -0
- package/dist/internal/Errors.d.ts.map +1 -0
- package/dist/internal/Errors.js +16 -0
- package/dist/internal/Errors.js.map +1 -0
- package/dist/internal/FundingRoutes.d.ts +15 -43
- package/dist/internal/FundingRoutes.d.ts.map +1 -1
- package/dist/internal/FundingRoutes.js +8 -17
- package/dist/internal/FundingRoutes.js.map +1 -1
- package/dist/internal/Log.d.ts +16 -1
- package/dist/internal/Log.d.ts.map +1 -1
- package/dist/internal/Log.js +5 -3
- package/dist/internal/Log.js.map +1 -1
- package/dist/internal/Store.d.ts +61 -16
- package/dist/internal/Store.d.ts.map +1 -1
- package/dist/internal/Store.js +72 -17
- package/dist/internal/Store.js.map +1 -1
- package/dist/internal/Viem.d.ts +1 -1
- package/dist/internal/index.d.ts +27 -0
- package/dist/internal/index.d.ts.map +1 -0
- package/dist/internal/index.js +27 -0
- package/dist/internal/index.js.map +1 -0
- package/package.json +8 -3
- package/src/ApiKey.ts +2 -3
- package/src/ApiKeys.ts +95 -17
- package/src/App.test.ts +104 -6
- package/src/App.ts +29 -9
- package/src/Assets.ts +8 -0
- package/src/Auth.test-d.ts +36 -0
- package/src/Auth.ts +20 -1
- package/src/Client.test-d.ts +17 -17
- package/src/Scope.test.ts +34 -0
- package/src/Scope.ts +53 -17
- package/src/admin/App.test.ts +33 -3
- package/src/admin/App.ts +49 -14
- package/src/admin/apps/api-keys.ts +10 -3
- package/src/admin/apps/organizations.test.ts +455 -0
- package/src/admin/apps/organizations.ts +953 -0
- package/src/admin/apps/scopes.test.ts +10 -0
- package/src/admin/ui/plugin.ts +1 -1
- package/src/admin/ui/src/components/MobileProfile.tsx +2 -0
- package/src/admin/ui/src/components/ThemeToggle.tsx +20 -0
- package/src/admin/ui/src/lib/api-keys.ts +36 -19
- package/src/admin/ui/src/lib/config.ts +2 -3
- package/src/admin/ui/src/lib/organizations.test.ts +89 -0
- package/src/admin/ui/src/lib/organizations.ts +262 -0
- package/src/admin/ui/src/lib/query-client.ts +3 -1
- package/src/admin/ui/src/lib/theme.ts +57 -0
- package/src/admin/ui/src/routeTree.gen.ts +47 -4
- package/src/admin/ui/src/routes/__root.tsx +18 -3
- package/src/admin/ui/src/routes/api-keys.tsx +52 -7
- package/src/admin/ui/src/routes/index.tsx +1 -1
- package/src/admin/ui/src/routes/organizations.tsx +158 -0
- package/src/admin/ui/src/routes/organizations_.$orgId.tsx +710 -0
- package/src/admin/ui/src/start.ts +6 -0
- package/src/admin/ui.generated.ts +63 -13
- package/src/analytics/Analytics.ts +25 -0
- package/src/analytics/tables/requestEvents.ts +33 -0
- package/src/apps/composition.test.ts +82 -2
- package/src/apps/data/FundingRouteProviders.test.ts +37 -93
- package/src/apps/data/FundingRouteProviders.ts +14 -161
- package/src/apps/data/routes/funding.test.ts +7 -42
- package/src/apps/data/routes/funding.ts +8 -8
- package/src/apps/data/routes/indexer.test.ts +2 -2
- package/src/apps/data/routes/webhooks.test.ts +2 -2
- package/src/apps/management/App.ts +2 -2
- package/src/apps/management/Billing.ts +10 -2
- package/src/apps/management/routes/api-keys.test.ts +137 -1
- package/src/apps/management/routes/api-keys.ts +63 -38
- package/src/apps/management/routes/billing.ts +14 -14
- package/src/apps/management/routes/invitations.test.ts +102 -1
- package/src/apps/management/routes/invitations.ts +13 -15
- package/src/apps/management/routes/members.ts +10 -10
- package/src/apps/management/routes/orgs.test.ts +78 -16
- package/src/apps/management/routes/orgs.ts +21 -20
- package/src/apps/management/routes/projects.ts +12 -13
- package/src/apps/management/routes/scopes.test.ts +16 -0
- package/src/apps/management/routes/scopes.ts +6 -7
- package/src/apps/management/routes/usage.ts +9 -14
- package/src/apps/mcp.test.ts +72 -7
- package/src/apps/mcp.ts +46 -4
- package/src/apps/relay/App.test.ts +23 -18
- package/src/apps/relay/App.ts +35 -14
- package/src/cloudflare.ts +5 -0
- package/src/db/Db.ts +59 -3
- package/src/db/Schema.ts +20 -1
- package/src/db/tables/adminAuditLogs.ts +47 -0
- package/src/db/tables/invitations.ts +1 -1
- package/src/db/tables/memberships.ts +59 -3
- package/src/db/tables/organizations.ts +148 -1
- package/src/db/tables/projects.ts +47 -0
- package/src/db/tables/webhookDeliveries.ts +83 -0
- package/src/internal/Auth.test.ts +571 -23
- package/src/internal/Auth.ts +166 -94
- package/src/internal/Cursor.test.ts +10 -0
- package/src/internal/Cursor.ts +5 -1
- package/src/internal/EdgeCache.test.ts +11 -0
- package/src/internal/EdgeCache.ts +2 -0
- package/src/internal/FundingRoutes.test.ts +8 -8
- package/src/internal/FundingRoutes.ts +25 -29
- package/src/internal/Log.test.ts +41 -3
- package/src/internal/Log.ts +23 -4
- package/src/internal/Store.test-d.ts +15 -0
- package/src/internal/Store.test.ts +153 -2
- package/src/internal/Store.ts +157 -41
- package/src/internal/index.test.ts +34 -0
- package/src/internal/index.ts +26 -0
|
@@ -41,7 +41,7 @@ export namespace schema {
|
|
|
41
41
|
.optional(z.record(z.string(), z.string()))
|
|
42
42
|
.check(
|
|
43
43
|
z.describe('Provider-specific chain ids Tempo uses when querying route providers.'),
|
|
44
|
-
z.meta({ examples: [{
|
|
44
|
+
z.meta({ examples: [{ bridge: 'base', relay: '8453' }] }),
|
|
45
45
|
),
|
|
46
46
|
}),
|
|
47
47
|
'A normalized chain reference for funding routes.',
|
|
@@ -122,7 +122,7 @@ export namespace schema {
|
|
|
122
122
|
/** Provider summary embedded in a route candidate. */
|
|
123
123
|
export const ProviderRef = Schema.describe(
|
|
124
124
|
z.object({
|
|
125
|
-
id: z.string().check(z.describe('Stable provider id.'), z.meta({ examples: ['
|
|
125
|
+
id: z.string().check(z.describe('Stable provider id.'), z.meta({ examples: ['relay'] })),
|
|
126
126
|
kind: z
|
|
127
127
|
.string()
|
|
128
128
|
.check(
|
|
@@ -131,7 +131,7 @@ export namespace schema {
|
|
|
131
131
|
),
|
|
132
132
|
name: z
|
|
133
133
|
.string()
|
|
134
|
-
.check(z.describe('Human-readable provider name.'), z.meta({ examples: ['
|
|
134
|
+
.check(z.describe('Human-readable provider name.'), z.meta({ examples: ['Relay'] })),
|
|
135
135
|
requiresApiKey: z
|
|
136
136
|
.boolean()
|
|
137
137
|
.check(
|
|
@@ -283,7 +283,7 @@ export namespace schema {
|
|
|
283
283
|
.string()
|
|
284
284
|
.check(
|
|
285
285
|
z.describe('Stable deterministic route candidate id.'),
|
|
286
|
-
z.meta({ examples: ['base-usdc-tempo-usdce-
|
|
286
|
+
z.meta({ examples: ['base-usdc-tempo-usdce-relay'] }),
|
|
287
287
|
),
|
|
288
288
|
provider: ProviderRef,
|
|
289
289
|
quality: Quality,
|
|
@@ -369,9 +369,9 @@ export namespace schema {
|
|
|
369
369
|
.optional(z.string())
|
|
370
370
|
.check(
|
|
371
371
|
z.describe(
|
|
372
|
-
'Only return candidates from this provider id, such as `
|
|
372
|
+
'Only return candidates from this provider id, such as `relay`, `bridge`, or `bungee`. Planned providers can return an empty list.',
|
|
373
373
|
),
|
|
374
|
-
z.meta({ examples: ['
|
|
374
|
+
z.meta({ examples: ['relay', 'bridge'] }),
|
|
375
375
|
),
|
|
376
376
|
sourceAmount: z
|
|
377
377
|
.string()
|
|
@@ -463,12 +463,12 @@ export namespace schema {
|
|
|
463
463
|
const fundingRouteResponseExamples = {
|
|
464
464
|
baseUsdc: {
|
|
465
465
|
description:
|
|
466
|
-
'`GET /v1/funding/routes?sourceChain=base&sourceToken=USDC&sourceAmount=1000000&provider=
|
|
466
|
+
'`GET /v1/funding/routes?sourceChain=base&sourceToken=USDC&sourceAmount=1000000&provider=relay`',
|
|
467
467
|
summary: 'Base USDC',
|
|
468
468
|
value: {
|
|
469
469
|
data: FundingRoutes.listStaticRoutes({
|
|
470
470
|
limit: 10,
|
|
471
|
-
provider: '
|
|
471
|
+
provider: 'relay',
|
|
472
472
|
sourceAmount: '1000000',
|
|
473
473
|
sourceAmountUnits: 'baseUnits',
|
|
474
474
|
sourceChain: 'base',
|
|
@@ -131,14 +131,14 @@ describe('GET /indexer/query', () => {
|
|
|
131
131
|
const app = TestApp.create({
|
|
132
132
|
rateLimit: { store: rateLimitStore },
|
|
133
133
|
mpp: {
|
|
134
|
-
|
|
134
|
+
secretKey: 'secret_test_key_0123456789abcdef',
|
|
135
|
+
session: {
|
|
135
136
|
chainId: Tempo.chain.id,
|
|
136
137
|
currency: Tempo.currency,
|
|
137
138
|
decimals: 6,
|
|
138
139
|
getClient: () => Tempo.client,
|
|
139
140
|
recipient: Tempo.accounts[2].address,
|
|
140
141
|
},
|
|
141
|
-
secretKey: 'secret_test_key_0123456789abcdef',
|
|
142
142
|
},
|
|
143
143
|
})
|
|
144
144
|
await exhaustQuota(RateLimit.memory({ store: rateLimitStore }), 'public:anonymous', {
|
|
@@ -1100,14 +1100,14 @@ describe('MPP access (disabled for now — API-key-only)', () => {
|
|
|
1100
1100
|
const app = TestApp.create({
|
|
1101
1101
|
auth: { keys: [] },
|
|
1102
1102
|
mpp: {
|
|
1103
|
-
|
|
1103
|
+
secretKey: 'secret_test_key_0123456789abcdef',
|
|
1104
|
+
session: {
|
|
1104
1105
|
chainId: Tempo.chain.id,
|
|
1105
1106
|
currency: Tempo.currency,
|
|
1106
1107
|
decimals: 6,
|
|
1107
1108
|
getClient: () => Tempo.client,
|
|
1108
1109
|
recipient: Tempo.accounts[2].address,
|
|
1109
1110
|
},
|
|
1110
|
-
secretKey: 'secret_test_key_0123456789abcdef',
|
|
1111
1111
|
},
|
|
1112
1112
|
})
|
|
1113
1113
|
|
|
@@ -87,8 +87,8 @@ export type Environment = {
|
|
|
87
87
|
* invitations, and API-key management. Every route reads its dependencies from
|
|
88
88
|
* request context set by {@link App.create}.
|
|
89
89
|
*
|
|
90
|
-
* The session-auth surface
|
|
91
|
-
*
|
|
90
|
+
* The session-auth surface is owned by {@link App.create}; management routes
|
|
91
|
+
* reference it alongside scoped API-key access.
|
|
92
92
|
*
|
|
93
93
|
* ```ts
|
|
94
94
|
* App.create(options).route('/', management())
|
|
@@ -7,6 +7,7 @@ import type * as BillingSettings from '../../db/tables/billingSettings.js'
|
|
|
7
7
|
import * as Db from '../../db/Db.js'
|
|
8
8
|
import * as Fees from '../../internal/Fees.js'
|
|
9
9
|
import * as RequestUsage from '../../db/tables/requestUsage.js'
|
|
10
|
+
import type * as Scope from '../../Scope.js'
|
|
10
11
|
import * as SponsoredTransactions from '../../db/tables/sponsoredTransactions.js'
|
|
11
12
|
import * as StripeCustomers from '../../db/tables/stripeCustomers.js'
|
|
12
13
|
import * as Viem from '../../internal/Viem.js'
|
|
@@ -641,7 +642,7 @@ export async function listActive(
|
|
|
641
642
|
export function createKeyBillingSyncer(
|
|
642
643
|
options: createKeyBillingSyncer.Options,
|
|
643
644
|
): createKeyBillingSyncer.Syncer {
|
|
644
|
-
const { db, environments = ['sandbox'], kv } = options
|
|
645
|
+
const { db, environments = ['sandbox'], kv, scopeCatalog } = options
|
|
645
646
|
return {
|
|
646
647
|
async tick() {
|
|
647
648
|
// Resolve per tick so Workers factories build a fresh Hyperdrive-pooled
|
|
@@ -655,7 +656,12 @@ export function createKeyBillingSyncer(
|
|
|
655
656
|
checked += 1
|
|
656
657
|
// `setBillingActive` writes only drifted records, so a converged
|
|
657
658
|
// fleet costs list reads and no writes.
|
|
658
|
-
updated += await ApiKeys.setBillingActive(kv, {
|
|
659
|
+
updated += await ApiKeys.setBillingActive(kv, {
|
|
660
|
+
active,
|
|
661
|
+
environment,
|
|
662
|
+
orgId,
|
|
663
|
+
scopeCatalog,
|
|
664
|
+
})
|
|
659
665
|
}
|
|
660
666
|
}
|
|
661
667
|
return { checked, updated }
|
|
@@ -672,6 +678,8 @@ export declare namespace createKeyBillingSyncer {
|
|
|
672
678
|
environments?: readonly StripeCustomers.Record['environment'][] | undefined
|
|
673
679
|
/** KV state store holding API-key records. */
|
|
674
680
|
kv: Store.State
|
|
681
|
+
/** Scope catalog accepted while reconciling API-key records. */
|
|
682
|
+
scopeCatalog?: Scope.Catalog | undefined
|
|
675
683
|
}
|
|
676
684
|
|
|
677
685
|
/** A scheduled snapshot-reconcile job. */
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'
|
|
2
2
|
|
|
3
3
|
import * as ApiKeys from '../../../ApiKeys.js'
|
|
4
|
+
import * as Auth from '../../../internal/Auth.js'
|
|
5
|
+
import * as OpenApi from '../../../internal/OpenApi.js'
|
|
6
|
+
import * as Scope from '../../../Scope.js'
|
|
4
7
|
import * as TestApp from '../../../../test/App.js'
|
|
8
|
+
import * as Memberships from '../../../db/tables/memberships.js'
|
|
5
9
|
import * as Keys from './api-keys.js'
|
|
10
|
+
import * as Me from './me.js'
|
|
6
11
|
import * as Orgs from './orgs.js'
|
|
7
12
|
import * as Projects from './projects.js'
|
|
8
13
|
|
|
@@ -33,10 +38,18 @@ function superAdminJson(method: 'PATCH' | 'POST', body: unknown) {
|
|
|
33
38
|
// Resolve-cache disabled so revocation assertions observe the store directly.
|
|
34
39
|
type State = ReturnType<typeof TestApp.kvStore>
|
|
35
40
|
|
|
36
|
-
function createApp(
|
|
41
|
+
function createApp(
|
|
42
|
+
options: {
|
|
43
|
+
db?: ReturnType<typeof TestApp.database>
|
|
44
|
+
kv?: { store: State }
|
|
45
|
+
scopes?: readonly Scope.Entry[]
|
|
46
|
+
} = {},
|
|
47
|
+
) {
|
|
37
48
|
return TestApp.create({
|
|
38
49
|
auth: { cache: false, superAdmin: { secret } },
|
|
50
|
+
db: options.db ?? TestApp.database(),
|
|
39
51
|
...(options.kv ? { kv: options.kv } : {}),
|
|
52
|
+
...(options.scopes ? { scopes: options.scopes } : {}),
|
|
40
53
|
session: { wallet: { origin } },
|
|
41
54
|
})
|
|
42
55
|
}
|
|
@@ -70,6 +83,77 @@ async function fixture(app: ReturnType<typeof createApp>, cookie: string) {
|
|
|
70
83
|
}
|
|
71
84
|
|
|
72
85
|
describe('keys', () => {
|
|
86
|
+
test('requires an owner to issue management write keys', async () => {
|
|
87
|
+
const db = TestApp.database()
|
|
88
|
+
const app = createApp({ db })
|
|
89
|
+
const owner = await session(app)
|
|
90
|
+
const { org, project } = await fixture(app, owner)
|
|
91
|
+
const admin = await session(app)
|
|
92
|
+
const user = await TestApp.json(
|
|
93
|
+
await app.request('/v1/me', { headers: { cookie: admin } }),
|
|
94
|
+
Me.schema.User,
|
|
95
|
+
)
|
|
96
|
+
await Memberships.create(db, {
|
|
97
|
+
orgId: org.id,
|
|
98
|
+
role: 'admin',
|
|
99
|
+
userId: user.id,
|
|
100
|
+
})
|
|
101
|
+
const base = `/v1/orgs/${org.id}/projects/${project.id}/api-keys`
|
|
102
|
+
|
|
103
|
+
const read = await app.request(base, json('POST', admin, { scopes: ['management:read'] }))
|
|
104
|
+
expect(read.status).toBe(200)
|
|
105
|
+
|
|
106
|
+
const denied = await app.request(base, json('POST', admin, { scopes: ['management:write'] }))
|
|
107
|
+
expect(denied.status).toBe(403)
|
|
108
|
+
expect(await denied.json()).toMatchObject({ error: { code: 'scope_not_issuable' } })
|
|
109
|
+
|
|
110
|
+
const write = await app.request(base, json('POST', owner, { scopes: ['management:write'] }))
|
|
111
|
+
expect(write.status).toBe(200)
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
test('lets management keys manage org credentials without delegating issuance', async () => {
|
|
115
|
+
const app = createApp()
|
|
116
|
+
const cookie = await session(app)
|
|
117
|
+
const { org, project } = await fixture(app, cookie)
|
|
118
|
+
const other = await TestApp.json(
|
|
119
|
+
await app.request(`/v1/orgs/${org.id}/projects`, json('POST', cookie, { name: 'Payments' })),
|
|
120
|
+
Projects.schema.Project,
|
|
121
|
+
)
|
|
122
|
+
const base = `/v1/orgs/${org.id}/projects/${project.id}/api-keys`
|
|
123
|
+
const siblingBase = `/v1/orgs/${org.id}/projects/${other.id}/api-keys`
|
|
124
|
+
const manager = await TestApp.json(
|
|
125
|
+
await app.request(
|
|
126
|
+
base,
|
|
127
|
+
superAdminJson('POST', {
|
|
128
|
+
scopes: ['management:read', 'management:write'],
|
|
129
|
+
}),
|
|
130
|
+
),
|
|
131
|
+
Keys.schema.createApiKey.Response,
|
|
132
|
+
)
|
|
133
|
+
const target = await TestApp.json(
|
|
134
|
+
await app.request(siblingBase, superAdminJson('POST', { scopes: ['data:read'] })),
|
|
135
|
+
Keys.schema.createApiKey.Response,
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
const listed = await app.request(siblingBase, {
|
|
139
|
+
headers: { 'tempo-api-key': manager.token },
|
|
140
|
+
})
|
|
141
|
+
expect(listed.status).toBe(200)
|
|
142
|
+
|
|
143
|
+
const delegated = await app.request(base, {
|
|
144
|
+
body: JSON.stringify({ scopes: ['management:read'] }),
|
|
145
|
+
headers: { 'content-type': 'application/json', 'tempo-api-key': manager.token },
|
|
146
|
+
method: 'POST',
|
|
147
|
+
})
|
|
148
|
+
expect(delegated.status).toBe(403)
|
|
149
|
+
|
|
150
|
+
const revoked = await app.request(`${siblingBase}/${target.id}`, {
|
|
151
|
+
headers: { 'tempo-api-key': manager.token },
|
|
152
|
+
method: 'DELETE',
|
|
153
|
+
})
|
|
154
|
+
expect(revoked.status).toBe(200)
|
|
155
|
+
})
|
|
156
|
+
|
|
73
157
|
test('mints, lists, authenticates, and revokes a project key', async () => {
|
|
74
158
|
const app = createApp()
|
|
75
159
|
const cookie = await session(app)
|
|
@@ -187,6 +271,58 @@ describe('keys', () => {
|
|
|
187
271
|
expect(created.createdBy).not.toBe('super_admin')
|
|
188
272
|
})
|
|
189
273
|
|
|
274
|
+
test('mints and authenticates host scopes', async () => {
|
|
275
|
+
const app = createApp({
|
|
276
|
+
scopes: [
|
|
277
|
+
{
|
|
278
|
+
description: 'Read project accounts.',
|
|
279
|
+
scope: 'accounts:read',
|
|
280
|
+
selfServe: true,
|
|
281
|
+
},
|
|
282
|
+
],
|
|
283
|
+
}).get(
|
|
284
|
+
'/custom/accounts',
|
|
285
|
+
Auth.policy({ apiKey: { scopes: ['accounts:read'] } }),
|
|
286
|
+
OpenApi.describeRoute({
|
|
287
|
+
operationId: 'getCustomAccounts',
|
|
288
|
+
responses: { 200: { description: 'Custom accounts response.' } },
|
|
289
|
+
summary: 'Get custom accounts',
|
|
290
|
+
}),
|
|
291
|
+
(c) => c.json({ ok: true }, 200),
|
|
292
|
+
)
|
|
293
|
+
const cookie = await session(app)
|
|
294
|
+
const { org, project } = await fixture(app, cookie)
|
|
295
|
+
|
|
296
|
+
const created = await TestApp.json(
|
|
297
|
+
await app.request(
|
|
298
|
+
`/v1/orgs/${org.id}/projects/${project.id}/api-keys`,
|
|
299
|
+
json('POST', cookie, { scopes: ['accounts:read'] }),
|
|
300
|
+
),
|
|
301
|
+
Keys.schema.createApiKey.Response,
|
|
302
|
+
)
|
|
303
|
+
const response = await app.request('/custom/accounts', {
|
|
304
|
+
headers: { 'tempo-api-key': created.token },
|
|
305
|
+
})
|
|
306
|
+
|
|
307
|
+
expect(created.scopes).toEqual(['accounts:read'])
|
|
308
|
+
expect(response.status).toBe(200)
|
|
309
|
+
expect(await response.json()).toEqual({ ok: true })
|
|
310
|
+
})
|
|
311
|
+
|
|
312
|
+
test('rejects unregistered host scopes', async () => {
|
|
313
|
+
const app = createApp()
|
|
314
|
+
const cookie = await session(app)
|
|
315
|
+
const { org, project } = await fixture(app, cookie)
|
|
316
|
+
|
|
317
|
+
const response = await app.request(
|
|
318
|
+
`/v1/orgs/${org.id}/projects/${project.id}/api-keys`,
|
|
319
|
+
superAdminJson('POST', { scopes: ['accounts:read'] }),
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
expect(response.status).toBe(400)
|
|
323
|
+
expect(await response.json()).toMatchObject({ error: { code: 'body_invalid' } })
|
|
324
|
+
})
|
|
325
|
+
|
|
190
326
|
test('rejects session mints of non-self-serve scopes (webhooks)', async () => {
|
|
191
327
|
const app = createApp()
|
|
192
328
|
const cookie = await session(app)
|
|
@@ -53,11 +53,11 @@ export namespace schema {
|
|
|
53
53
|
z.meta({ examples: ['org_1a2b3c4d5e6f7g8h9j0k1m2n'] }),
|
|
54
54
|
),
|
|
55
55
|
projectId: z.optional(z.string()).check(
|
|
56
|
-
z.describe('
|
|
56
|
+
z.describe('Attributed project id (`prj_…`).'), // prettier-ignore
|
|
57
57
|
z.meta({ examples: ['prj_1a2b3c4d5e6f7g8h9j0k1m2n'] }),
|
|
58
58
|
),
|
|
59
59
|
scopes: z
|
|
60
|
-
.array(z.
|
|
60
|
+
.array(z.string().check(z.minLength(1)))
|
|
61
61
|
.check(
|
|
62
62
|
z.describe(
|
|
63
63
|
"Granted scopes. '*' (grant-all) appears only on keys minted by the super admin.",
|
|
@@ -138,7 +138,15 @@ export namespace schema {
|
|
|
138
138
|
.optional(z.string().check(z.minLength(1), z.maxLength(100)))
|
|
139
139
|
.check(z.describe('Human-readable key name.'), z.meta({ examples: ['CI'] })),
|
|
140
140
|
scopes: z
|
|
141
|
-
._default(
|
|
141
|
+
._default(
|
|
142
|
+
z.array(
|
|
143
|
+
z.string().check(
|
|
144
|
+
z.minLength(1),
|
|
145
|
+
z.refine((scope) => scope !== Scope.wildcard),
|
|
146
|
+
),
|
|
147
|
+
),
|
|
148
|
+
[],
|
|
149
|
+
)
|
|
142
150
|
.check(z.describe('Scopes to grant.'), z.meta({ examples: [['data:read']] })),
|
|
143
151
|
})
|
|
144
152
|
.check(z.describe('Fields for minting an API key.'))
|
|
@@ -210,11 +218,7 @@ export namespace schema {
|
|
|
210
218
|
}
|
|
211
219
|
|
|
212
220
|
/**
|
|
213
|
-
* Mounts the
|
|
214
|
-
* (project-scoped mint/list/revoke) plus the org-wide read
|
|
215
|
-
* `/orgs/:orgId/api-keys`, over the KV state store. Access follows the
|
|
216
|
-
* org/project scope (owning session or super admin); the token appears once,
|
|
217
|
-
* at mint.
|
|
221
|
+
* Mounts project-attributed API key mint, list, and revoke routes plus the org-wide list. Access follows the caller's organization ownership.
|
|
218
222
|
*/
|
|
219
223
|
export function apiKeys(options: apiKeys.Options = {}) {
|
|
220
224
|
const hidden = !options.enabled
|
|
@@ -233,7 +237,7 @@ export function apiKeys(options: apiKeys.Options = {}) {
|
|
|
233
237
|
message: 'Check the request body and try again.',
|
|
234
238
|
}),
|
|
235
239
|
OpenApi.describeRoute({
|
|
236
|
-
description: 'Mint a project-
|
|
240
|
+
description: 'Mint a project-attributed API key. The token appears once and is never shown again.', // prettier-ignore
|
|
237
241
|
hide: hidden,
|
|
238
242
|
operationId: 'createApiKey',
|
|
239
243
|
responses: OpenApi.responses({
|
|
@@ -273,20 +277,19 @@ export function apiKeys(options: apiKeys.Options = {}) {
|
|
|
273
277
|
|
|
274
278
|
const body = c.req.valid('json')
|
|
275
279
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
if (denied.length > 0)
|
|
283
|
-
return Response.error(c, {
|
|
284
|
-
code: 'scope_not_issuable',
|
|
285
|
-
message: 'The requested scopes are not issuable.',
|
|
286
|
-
status: 403,
|
|
287
|
-
})
|
|
288
|
-
}
|
|
280
|
+
if (!body.scopes.every((scope) => Scope.includes(c.get('scopeCatalog'), scope)))
|
|
281
|
+
return Response.error(c, {
|
|
282
|
+
code: 'body_invalid',
|
|
283
|
+
message: 'Check the request body and try again.',
|
|
284
|
+
status: 400,
|
|
285
|
+
})
|
|
289
286
|
|
|
287
|
+
if (!canIssue(c, body.scopes))
|
|
288
|
+
return Response.error(c, {
|
|
289
|
+
code: 'scope_not_issuable',
|
|
290
|
+
message: 'The requested scopes are not issuable.',
|
|
291
|
+
status: 403,
|
|
292
|
+
})
|
|
290
293
|
try {
|
|
291
294
|
const orgId = Auth.org(c).id
|
|
292
295
|
// Snapshot billing onto the record so the auth path throttles a
|
|
@@ -296,15 +299,19 @@ export function apiKeys(options: apiKeys.Options = {}) {
|
|
|
296
299
|
body.environment === 'sandbox'
|
|
297
300
|
? await Billing.active(Db.get(c.get('db')), orgId, 'sandbox')
|
|
298
301
|
: undefined
|
|
299
|
-
const { record, token } = await ApiKeys.mint(
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
302
|
+
const { record, token } = await ApiKeys.mint(
|
|
303
|
+
kv.store,
|
|
304
|
+
{
|
|
305
|
+
createdBy,
|
|
306
|
+
environment: body.environment,
|
|
307
|
+
...(billingActive === undefined ? {} : { billingActive }),
|
|
308
|
+
...(body.name === undefined ? {} : { name: body.name }),
|
|
309
|
+
orgId,
|
|
310
|
+
projectId: Auth.project(c).id,
|
|
311
|
+
scopes: body.scopes,
|
|
312
|
+
},
|
|
313
|
+
{ scopeCatalog: c.get('scopeCatalog') },
|
|
314
|
+
)
|
|
308
315
|
return c.json(
|
|
309
316
|
Response.validated(schema.createApiKey.Response, { ...serializeKey(record), token }),
|
|
310
317
|
200,
|
|
@@ -316,7 +323,7 @@ export function apiKeys(options: apiKeys.Options = {}) {
|
|
|
316
323
|
)
|
|
317
324
|
.get(
|
|
318
325
|
'/v1/orgs/:orgId{org_[A-Za-z0-9_-]+}/projects/:projectId{prj_[A-Za-z0-9_-]+}/api-keys',
|
|
319
|
-
Auth.policy({ session: true }),
|
|
326
|
+
Auth.policy({ apiKey: { scopes: ['management:read'] }, session: true }),
|
|
320
327
|
Auth.ensureOrg(),
|
|
321
328
|
Auth.ensureProject(),
|
|
322
329
|
OpenApi.validate('param', schema.Params, {
|
|
@@ -344,7 +351,7 @@ export function apiKeys(options: apiKeys.Options = {}) {
|
|
|
344
351
|
tags: ['API Keys'],
|
|
345
352
|
}),
|
|
346
353
|
async (c) => {
|
|
347
|
-
if (Auth.narrowAccess) return Auth.
|
|
354
|
+
if (Auth.narrowAccess) return Auth.accessError(c)
|
|
348
355
|
if (Auth.narrowScope) return Auth.ensureProjectError(c)
|
|
349
356
|
if (OpenApi.narrowValidation)
|
|
350
357
|
return OpenApi.validationError(c, {
|
|
@@ -363,6 +370,7 @@ export function apiKeys(options: apiKeys.Options = {}) {
|
|
|
363
370
|
const records = await ApiKeys.listByOrg(kv.store, Auth.org(c).id, {
|
|
364
371
|
environment: c.req.valid('query').environment,
|
|
365
372
|
projectId: Auth.project(c).id,
|
|
373
|
+
scopeCatalog: c.get('scopeCatalog'),
|
|
366
374
|
})
|
|
367
375
|
return c.json(
|
|
368
376
|
Response.validated(schema.listApiKeys.Response, {
|
|
@@ -377,7 +385,7 @@ export function apiKeys(options: apiKeys.Options = {}) {
|
|
|
377
385
|
)
|
|
378
386
|
.get(
|
|
379
387
|
'/v1/orgs/:orgId{org_[A-Za-z0-9_-]+}/api-keys',
|
|
380
|
-
Auth.policy({ session: true }),
|
|
388
|
+
Auth.policy({ apiKey: { scopes: ['management:read'] }, session: true }),
|
|
381
389
|
Auth.ensureOrg(),
|
|
382
390
|
OpenApi.validate('param', schema.OrgParams, {
|
|
383
391
|
code: 'param_invalid',
|
|
@@ -405,7 +413,7 @@ export function apiKeys(options: apiKeys.Options = {}) {
|
|
|
405
413
|
tags: ['API Keys'],
|
|
406
414
|
}),
|
|
407
415
|
async (c) => {
|
|
408
|
-
if (Auth.narrowAccess) return Auth.
|
|
416
|
+
if (Auth.narrowAccess) return Auth.accessError(c)
|
|
409
417
|
if (Auth.narrowScope) return Auth.ensureOrgError(c)
|
|
410
418
|
if (OpenApi.narrowValidation)
|
|
411
419
|
return OpenApi.validationError(c, {
|
|
@@ -423,6 +431,7 @@ export function apiKeys(options: apiKeys.Options = {}) {
|
|
|
423
431
|
try {
|
|
424
432
|
const records = await ApiKeys.listByOrg(kv.store, Auth.org(c).id, {
|
|
425
433
|
environment: c.req.valid('query').environment,
|
|
434
|
+
scopeCatalog: c.get('scopeCatalog'),
|
|
426
435
|
})
|
|
427
436
|
return c.json(
|
|
428
437
|
Response.validated(schema.listOrgApiKeys.Response, {
|
|
@@ -437,7 +446,7 @@ export function apiKeys(options: apiKeys.Options = {}) {
|
|
|
437
446
|
)
|
|
438
447
|
.delete(
|
|
439
448
|
'/v1/orgs/:orgId{org_[A-Za-z0-9_-]+}/projects/:projectId{prj_[A-Za-z0-9_-]+}/api-keys/:keyId{key_[A-Za-z0-9_-]+}',
|
|
440
|
-
Auth.policy({ session: true }),
|
|
449
|
+
Auth.policy({ apiKey: { scopes: ['management:write'] }, session: true }),
|
|
441
450
|
Auth.ensureOrg({ role: 'admin' }),
|
|
442
451
|
Auth.ensureProject(),
|
|
443
452
|
OpenApi.validate('param', schema.KeyParams, {
|
|
@@ -459,7 +468,7 @@ export function apiKeys(options: apiKeys.Options = {}) {
|
|
|
459
468
|
tags: ['API Keys'],
|
|
460
469
|
}),
|
|
461
470
|
async (c) => {
|
|
462
|
-
if (Auth.narrowAccess) return Auth.
|
|
471
|
+
if (Auth.narrowAccess) return Auth.accessError(c)
|
|
463
472
|
if (Auth.narrowScope) return Auth.ensureProjectError(c)
|
|
464
473
|
if (OpenApi.narrowValidation)
|
|
465
474
|
return OpenApi.validationError(c, {
|
|
@@ -473,7 +482,9 @@ export function apiKeys(options: apiKeys.Options = {}) {
|
|
|
473
482
|
try {
|
|
474
483
|
// Bind the key to the scoped project before revoking: a key id from
|
|
475
484
|
// another org or project must be indistinguishable from a missing one.
|
|
476
|
-
const record = await ApiKeys.get(kv.store, keyId
|
|
485
|
+
const record = await ApiKeys.get(kv.store, keyId, {
|
|
486
|
+
scopeCatalog: c.get('scopeCatalog'),
|
|
487
|
+
})
|
|
477
488
|
if (!record || record.orgId !== Auth.org(c).id || record.projectId !== Auth.project(c).id)
|
|
478
489
|
return keyNotFound(c)
|
|
479
490
|
const revoked = await ApiKeys.revoke(kv.store, keyId)
|
|
@@ -518,6 +529,20 @@ function mintedBy(c: Context<App.Environment>) {
|
|
|
518
529
|
return undefined
|
|
519
530
|
}
|
|
520
531
|
|
|
532
|
+
/** Checks whether the caller may grant every requested scope. */
|
|
533
|
+
function canIssue(c: Context<App.Environment>, scopes: readonly Scope.Id[]) {
|
|
534
|
+
const principal = Auth.getPrincipal(c)
|
|
535
|
+
if (principal?.type === 'super_admin') return true
|
|
536
|
+
if (principal?.type === 'session') {
|
|
537
|
+
const selfServe = Scope.selfServeFrom(c.get('scopeCatalog'))
|
|
538
|
+
return (
|
|
539
|
+
scopes.every((scope) => selfServe.includes(scope)) &&
|
|
540
|
+
(!scopes.includes('management:write') || Auth.membership(c)?.role === 'owner')
|
|
541
|
+
)
|
|
542
|
+
}
|
|
543
|
+
return false
|
|
544
|
+
}
|
|
545
|
+
|
|
521
546
|
function keyNotFound(c: Context<App.Environment>) {
|
|
522
547
|
return Response.error(c, {
|
|
523
548
|
code: 'api_key_not_found',
|
|
@@ -234,14 +234,13 @@ export namespace schema {
|
|
|
234
234
|
}
|
|
235
235
|
|
|
236
236
|
/**
|
|
237
|
-
* Mounts
|
|
238
|
-
* a Stripe billing source read as `none`. Session lane; reads for members.
|
|
237
|
+
* Mounts billing routes. Reads require membership or scoped API key access; organizations without Stripe state return `none`.
|
|
239
238
|
*/
|
|
240
239
|
export function billing() {
|
|
241
240
|
return new Hono<Environment>()
|
|
242
241
|
.get(
|
|
243
242
|
'/v1/orgs/:orgId{org_[A-Za-z0-9_-]+}/billing',
|
|
244
|
-
Auth.policy({ session: true }),
|
|
243
|
+
Auth.policy({ apiKey: { scopes: ['management:read'] }, session: true }),
|
|
245
244
|
Auth.ensureOrg(),
|
|
246
245
|
OpenApi.validate('param', schema.Params, {
|
|
247
246
|
code: 'param_invalid',
|
|
@@ -264,7 +263,7 @@ export function billing() {
|
|
|
264
263
|
tags: ['Billing'],
|
|
265
264
|
}),
|
|
266
265
|
async (c) => {
|
|
267
|
-
if (Auth.narrowAccess) return Auth.
|
|
266
|
+
if (Auth.narrowAccess) return Auth.accessError(c)
|
|
268
267
|
if (Auth.narrowScope) return Auth.ensureOrgError(c)
|
|
269
268
|
if (OpenApi.narrowValidation)
|
|
270
269
|
return OpenApi.validationError(c, {
|
|
@@ -294,7 +293,7 @@ export function billing() {
|
|
|
294
293
|
)
|
|
295
294
|
.patch(
|
|
296
295
|
'/v1/orgs/:orgId{org_[A-Za-z0-9_-]+}/billing',
|
|
297
|
-
Auth.policy({ session: true }),
|
|
296
|
+
Auth.policy({ apiKey: { scopes: ['management:write'] }, session: true }),
|
|
298
297
|
Auth.ensureOrg({ role: 'owner' }),
|
|
299
298
|
OpenApi.validate('param', schema.Params, {
|
|
300
299
|
code: 'param_invalid',
|
|
@@ -325,7 +324,7 @@ export function billing() {
|
|
|
325
324
|
tags: ['Billing'],
|
|
326
325
|
}),
|
|
327
326
|
async (c) => {
|
|
328
|
-
if (Auth.narrowAccess) return Auth.
|
|
327
|
+
if (Auth.narrowAccess) return Auth.accessError(c)
|
|
329
328
|
if (Auth.narrowScope) return Auth.ensureOrgError(c)
|
|
330
329
|
if (OpenApi.narrowValidation)
|
|
331
330
|
return OpenApi.validationError(c, {
|
|
@@ -381,7 +380,7 @@ export function billing() {
|
|
|
381
380
|
)
|
|
382
381
|
.post(
|
|
383
382
|
'/v1/orgs/:orgId{org_[A-Za-z0-9_-]+}/billing/stripe/checkout',
|
|
384
|
-
Auth.policy({ session: true }),
|
|
383
|
+
Auth.policy({ apiKey: { scopes: ['management:write'] }, session: true }),
|
|
385
384
|
Auth.ensureOrg({ role: 'owner' }),
|
|
386
385
|
OpenApi.validate('param', schema.Params, {
|
|
387
386
|
code: 'param_invalid',
|
|
@@ -408,7 +407,7 @@ export function billing() {
|
|
|
408
407
|
tags: ['Billing'],
|
|
409
408
|
}),
|
|
410
409
|
async (c) => {
|
|
411
|
-
if (Auth.narrowAccess) return Auth.
|
|
410
|
+
if (Auth.narrowAccess) return Auth.accessError(c)
|
|
412
411
|
if (Auth.narrowScope) return Auth.ensureOrgError(c)
|
|
413
412
|
if (OpenApi.narrowValidation)
|
|
414
413
|
return OpenApi.validationError(c, {
|
|
@@ -466,7 +465,7 @@ export function billing() {
|
|
|
466
465
|
)
|
|
467
466
|
.post(
|
|
468
467
|
'/v1/orgs/:orgId{org_[A-Za-z0-9_-]+}/billing/stripe/manage',
|
|
469
|
-
Auth.policy({ session: true }),
|
|
468
|
+
Auth.policy({ apiKey: { scopes: ['management:write'] }, session: true }),
|
|
470
469
|
Auth.ensureOrg({ role: 'owner' }),
|
|
471
470
|
OpenApi.validate('param', schema.Params, {
|
|
472
471
|
code: 'param_invalid',
|
|
@@ -493,7 +492,7 @@ export function billing() {
|
|
|
493
492
|
tags: ['Billing'],
|
|
494
493
|
}),
|
|
495
494
|
async (c) => {
|
|
496
|
-
if (Auth.narrowAccess) return Auth.
|
|
495
|
+
if (Auth.narrowAccess) return Auth.accessError(c)
|
|
497
496
|
if (Auth.narrowScope) return Auth.ensureOrgError(c)
|
|
498
497
|
if (OpenApi.narrowValidation)
|
|
499
498
|
return OpenApi.validationError(c, {
|
|
@@ -529,7 +528,7 @@ export function billing() {
|
|
|
529
528
|
)
|
|
530
529
|
.get(
|
|
531
530
|
'/v1/orgs/:orgId{org_[A-Za-z0-9_-]+}/billing/payment-methods',
|
|
532
|
-
Auth.policy({ session: true }),
|
|
531
|
+
Auth.policy({ apiKey: { scopes: ['management:read'] }, session: true }),
|
|
533
532
|
Auth.ensureOrg(),
|
|
534
533
|
OpenApi.validate('param', schema.Params, {
|
|
535
534
|
code: 'param_invalid',
|
|
@@ -555,7 +554,7 @@ export function billing() {
|
|
|
555
554
|
tags: ['Billing'],
|
|
556
555
|
}),
|
|
557
556
|
async (c) => {
|
|
558
|
-
if (Auth.narrowAccess) return Auth.
|
|
557
|
+
if (Auth.narrowAccess) return Auth.accessError(c)
|
|
559
558
|
if (Auth.narrowScope) return Auth.ensureOrgError(c)
|
|
560
559
|
if (OpenApi.narrowValidation)
|
|
561
560
|
return OpenApi.validationError(c, {
|
|
@@ -615,7 +614,7 @@ export function billing() {
|
|
|
615
614
|
)
|
|
616
615
|
.delete(
|
|
617
616
|
'/v1/orgs/:orgId{org_[A-Za-z0-9_-]+}/billing/payment-methods/:methodId{pm_[A-Za-z0-9_-]+}',
|
|
618
|
-
Auth.policy({ session: true }),
|
|
617
|
+
Auth.policy({ apiKey: { scopes: ['management:write'] }, session: true }),
|
|
619
618
|
Auth.ensureOrg({ role: 'owner' }),
|
|
620
619
|
OpenApi.validate('param', schema.MethodParams, {
|
|
621
620
|
code: 'param_invalid',
|
|
@@ -642,7 +641,7 @@ export function billing() {
|
|
|
642
641
|
tags: ['Billing'],
|
|
643
642
|
}),
|
|
644
643
|
async (c) => {
|
|
645
|
-
if (Auth.narrowAccess) return Auth.
|
|
644
|
+
if (Auth.narrowAccess) return Auth.accessError(c)
|
|
646
645
|
if (Auth.narrowScope) return Auth.ensureOrgError(c)
|
|
647
646
|
if (OpenApi.narrowValidation)
|
|
648
647
|
return OpenApi.validationError(c, {
|
|
@@ -797,6 +796,7 @@ async function syncKeyBilling(
|
|
|
797
796
|
active: options.status === 'active',
|
|
798
797
|
environment: 'sandbox',
|
|
799
798
|
orgId: options.orgId,
|
|
799
|
+
scopeCatalog: c.get('scopeCatalog'),
|
|
800
800
|
})
|
|
801
801
|
}
|
|
802
802
|
|