tapimo 0.4.4 → 0.5.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/dist/App.d.ts +7403 -6127
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js +4 -0
- package/dist/App.js.map +1 -1
- package/dist/Scope.d.ts +18 -1
- package/dist/Scope.d.ts.map +1 -1
- package/dist/Scope.js +24 -3
- package/dist/Scope.js.map +1 -1
- package/dist/admin/apps/verified-tokens.d.ts +115 -115
- package/dist/apps/data/App.d.ts +1838 -1534
- package/dist/apps/data/App.d.ts.map +1 -1
- package/dist/apps/data/App.js +135 -44
- package/dist/apps/data/App.js.map +1 -1
- package/dist/apps/data/{FundingRouteProviders.d.ts → FundingQuoteProviders.d.ts} +7 -7
- package/dist/apps/data/{FundingRouteProviders.d.ts.map → FundingQuoteProviders.d.ts.map} +1 -1
- package/dist/apps/data/{FundingRouteProviders.js → FundingQuoteProviders.js} +36 -91
- package/dist/apps/data/FundingQuoteProviders.js.map +1 -0
- package/dist/apps/data/index.d.ts +1 -1
- package/dist/apps/data/index.js +1 -1
- package/dist/apps/data/routes/activities.d.ts +242 -242
- package/dist/apps/data/routes/addresses.d.ts +227 -227
- package/dist/apps/data/routes/balances.d.ts +12 -12
- package/dist/apps/data/routes/balances.d.ts.map +1 -1
- package/dist/apps/data/routes/balances.js +67 -149
- package/dist/apps/data/routes/balances.js.map +1 -1
- package/dist/apps/data/routes/blocks.d.ts +125 -126
- package/dist/apps/data/routes/blocks.d.ts.map +1 -1
- package/dist/apps/data/routes/blocks.js +3 -5
- package/dist/apps/data/routes/blocks.js.map +1 -1
- package/dist/apps/data/routes/coingecko.d.ts +55 -54
- package/dist/apps/data/routes/coingecko.d.ts.map +1 -1
- package/dist/apps/data/routes/coingecko.js +3 -8
- package/dist/apps/data/routes/coingecko.js.map +1 -1
- package/dist/apps/data/routes/funding.d.ts +207 -388
- package/dist/apps/data/routes/funding.d.ts.map +1 -1
- package/dist/apps/data/routes/funding.js +143 -221
- package/dist/apps/data/routes/funding.js.map +1 -1
- package/dist/apps/data/routes/rpc.d.ts.map +1 -1
- package/dist/apps/data/routes/rpc.js +86 -16
- package/dist/apps/data/routes/rpc.js.map +1 -1
- package/dist/apps/data/routes/tokenlist.d.ts +33 -33
- package/dist/apps/data/routes/transactions.d.ts +281 -281
- package/dist/apps/data/routes/transfers.d.ts +58 -58
- package/dist/apps/data/routes/verified-tokens.d.ts +86 -86
- package/dist/apps/data/routes/webhooks.d.ts +340 -0
- package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
- package/dist/apps/data/routes/webhooks.js +13 -1
- package/dist/apps/data/routes/webhooks.js.map +1 -1
- package/dist/apps/index.d.ts +1 -1
- package/dist/apps/index.js +1 -1
- package/dist/apps/management/App.d.ts +1090 -108
- package/dist/apps/management/App.d.ts.map +1 -1
- package/dist/apps/management/App.js +4 -0
- package/dist/apps/management/App.js.map +1 -1
- package/dist/apps/management/routes/api-keys.d.ts +118 -118
- package/dist/apps/management/routes/api-keys.js +2 -2
- package/dist/apps/management/routes/api-keys.js.map +1 -1
- package/dist/apps/management/routes/billing.d.ts +207 -207
- package/dist/apps/management/routes/invite-links.d.ts +1100 -0
- package/dist/apps/management/routes/invite-links.d.ts.map +1 -0
- package/dist/apps/management/routes/invite-links.js +411 -0
- package/dist/apps/management/routes/invite-links.js.map +1 -0
- package/dist/apps/management/routes/me.d.ts +17 -17
- package/dist/apps/management/routes/members.d.ts +71 -71
- package/dist/apps/management/routes/orgs.d.ts +12 -12
- package/dist/apps/management/routes/projects.d.ts +70 -70
- package/dist/apps/management/routes/scopes.d.ts +11 -11
- package/dist/apps/management/routes/scopes.d.ts.map +1 -1
- package/dist/apps/management/routes/scopes.js +2 -2
- package/dist/apps/management/routes/scopes.js.map +1 -1
- package/dist/apps/management/routes/usage.d.ts +75 -75
- package/dist/apps/relay/App.d.ts.map +1 -1
- package/dist/apps/relay/App.js +42 -4
- package/dist/apps/relay/App.js.map +1 -1
- package/dist/db/Db.d.ts +3 -0
- package/dist/db/Db.d.ts.map +1 -1
- package/dist/db/Db.js +57 -0
- package/dist/db/Db.js.map +1 -1
- package/dist/db/Schema.d.ts +31 -0
- package/dist/db/Schema.d.ts.map +1 -1
- package/dist/db/Schema.js +31 -0
- package/dist/db/Schema.js.map +1 -1
- package/dist/db/tables/inviteLinks.d.ts +42 -0
- package/dist/db/tables/inviteLinks.d.ts.map +1 -0
- package/dist/db/tables/inviteLinks.js +148 -0
- package/dist/db/tables/inviteLinks.js.map +1 -0
- package/dist/db/tables/organizations.d.ts.map +1 -1
- package/dist/db/tables/organizations.js +2 -0
- package/dist/db/tables/organizations.js.map +1 -1
- package/dist/handlers/relay.d.ts.map +1 -1
- package/dist/handlers/relay.js +13 -3
- package/dist/handlers/relay.js.map +1 -1
- package/dist/internal/Auth.d.ts +9 -1
- package/dist/internal/Auth.d.ts.map +1 -1
- package/dist/internal/Auth.js +130 -10
- package/dist/internal/Auth.js.map +1 -1
- package/dist/internal/EdgeCache.d.ts +3 -1
- package/dist/internal/EdgeCache.d.ts.map +1 -1
- package/dist/internal/EdgeCache.js +17 -2
- package/dist/internal/EdgeCache.js.map +1 -1
- package/dist/internal/{FundingRoutes.d.ts → FundingQuotes.d.ts} +185 -428
- package/dist/internal/FundingQuotes.d.ts.map +1 -0
- package/dist/internal/{FundingRoutes.js → FundingQuotes.js} +93 -218
- package/dist/internal/FundingQuotes.js.map +1 -0
- package/dist/internal/Viem.d.ts +3 -1
- package/dist/internal/Viem.d.ts.map +1 -1
- package/dist/internal/Viem.js +11 -2
- package/dist/internal/Viem.js.map +1 -1
- package/dist/internal/index.d.ts +1 -1
- package/dist/internal/index.js +1 -1
- package/package.json +1 -1
- package/src/ApiKeys.test.ts +23 -0
- package/src/App.test-d.ts +2 -0
- package/src/App.ts +6 -0
- package/src/Client.test-d.ts +20 -11
- package/src/Scope.test.ts +1 -0
- package/src/Scope.ts +30 -4
- package/src/apps/data/App.test.ts +354 -0
- package/src/apps/data/App.ts +159 -52
- package/src/apps/data/FundingQuoteProviders.test.ts +151 -0
- package/src/apps/data/{FundingRouteProviders.ts → FundingQuoteProviders.ts} +40 -101
- package/src/apps/data/index.ts +1 -1
- package/src/apps/data/routes/balances.test.ts +64 -18
- package/src/apps/data/routes/balances.ts +76 -177
- package/src/apps/data/routes/blocks.ts +3 -5
- package/src/apps/data/routes/coingecko.ts +3 -8
- package/src/apps/data/routes/funding.test.ts +151 -206
- package/src/apps/data/routes/funding.ts +156 -291
- package/src/apps/data/routes/indexer.test.ts +97 -1
- package/src/apps/data/routes/rpc.test.ts +219 -0
- package/src/apps/data/routes/rpc.ts +92 -17
- package/src/apps/data/routes/webhooks.test.ts +21 -0
- package/src/apps/data/routes/webhooks.ts +13 -1
- package/src/apps/index.ts +1 -1
- package/src/apps/management/App.ts +4 -0
- package/src/apps/management/routes/api-keys.test.ts +47 -3
- package/src/apps/management/routes/api-keys.ts +2 -2
- package/src/apps/management/routes/invite-links.test.ts +385 -0
- package/src/apps/management/routes/invite-links.ts +570 -0
- package/src/apps/management/routes/scopes.test.ts +3 -2
- package/src/apps/management/routes/scopes.ts +4 -2
- package/src/apps/relay/App.test.ts +324 -4
- package/src/apps/relay/App.ts +40 -4
- package/src/db/Db.ts +65 -0
- package/src/db/Schema.ts +39 -0
- package/src/db/tables/inviteLinks.ts +187 -0
- package/src/db/tables/organizations.ts +2 -0
- package/src/handlers/relay.ts +16 -4
- package/src/internal/Auth.test.ts +132 -22
- package/src/internal/Auth.ts +171 -14
- package/src/internal/EdgeCache.test.ts +24 -3
- package/src/internal/EdgeCache.ts +18 -2
- package/src/internal/FundingQuotes.test.ts +174 -0
- package/src/internal/{FundingRoutes.ts → FundingQuotes.ts} +153 -312
- package/src/internal/Viem.ts +14 -5
- package/src/internal/index.test.ts +1 -1
- package/src/internal/index.ts +1 -1
- package/dist/apps/data/FundingRouteProviders.js.map +0 -1
- package/dist/internal/FundingRoutes.d.ts.map +0 -1
- package/dist/internal/FundingRoutes.js.map +0 -1
- package/src/apps/data/FundingRouteProviders.test.ts +0 -225
- package/src/internal/FundingRoutes.test.ts +0 -213
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import type { Selectable } from 'kysely'
|
|
2
|
+
|
|
3
|
+
import * as Id from '../../internal/Id.js'
|
|
4
|
+
import type * as Db from '../Db.js'
|
|
5
|
+
import type * as db_Schema from '../Schema.js'
|
|
6
|
+
|
|
7
|
+
/** Columns of the `invite_links` table. */
|
|
8
|
+
export type Table = db_Schema.InviteLink
|
|
9
|
+
/** Columns of the `invite_link_redemptions` table. */
|
|
10
|
+
export type RedemptionTable = db_Schema.InviteLinkRedemption
|
|
11
|
+
/** A stored invite link. */
|
|
12
|
+
export type Record = Selectable<Table>
|
|
13
|
+
/** A stored redemption. */
|
|
14
|
+
export type Redemption = Selectable<RedemptionTable>
|
|
15
|
+
/** Derived link availability. */
|
|
16
|
+
export type Status = 'active' | 'deleted' | 'disabled' | 'exhausted' | 'expired'
|
|
17
|
+
|
|
18
|
+
/** Derives current availability from persisted controls and count. */
|
|
19
|
+
export function status(record: Record, now = new Date().toISOString()): Status {
|
|
20
|
+
if (record.deletedAt) return 'deleted'
|
|
21
|
+
if (!record.enabled) return 'disabled'
|
|
22
|
+
if (record.expiresAt && record.expiresAt <= now) return 'expired'
|
|
23
|
+
if (record.maxUses !== null && record.useCount >= record.maxUses) return 'exhausted'
|
|
24
|
+
return 'active'
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Creates a reusable invite link. */
|
|
28
|
+
export function create(
|
|
29
|
+
db: Db.Db,
|
|
30
|
+
input: {
|
|
31
|
+
createdBy: string
|
|
32
|
+
expiresAt: string | null
|
|
33
|
+
maxUses: number | null
|
|
34
|
+
name?: string | undefined
|
|
35
|
+
orgId: string
|
|
36
|
+
role: 'member'
|
|
37
|
+
},
|
|
38
|
+
): Promise<Record> {
|
|
39
|
+
const now = new Date().toISOString()
|
|
40
|
+
return db.kysely
|
|
41
|
+
.insertInto('invite_links')
|
|
42
|
+
.values({
|
|
43
|
+
createdAt: now,
|
|
44
|
+
createdBy: input.createdBy,
|
|
45
|
+
deletedAt: null,
|
|
46
|
+
enabled: true,
|
|
47
|
+
expiresAt: input.expiresAt,
|
|
48
|
+
id: Id.generate('iln'),
|
|
49
|
+
lastUsedAt: null,
|
|
50
|
+
maxUses: input.maxUses,
|
|
51
|
+
name: input.name ?? 'Untitled link',
|
|
52
|
+
orgId: input.orgId,
|
|
53
|
+
role: input.role,
|
|
54
|
+
token: Id.generate('lnk'),
|
|
55
|
+
updatedAt: now,
|
|
56
|
+
useCount: 0,
|
|
57
|
+
})
|
|
58
|
+
.returningAll()
|
|
59
|
+
.executeTakeFirstOrThrow()
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Lists non-deleted links in an organization. */
|
|
63
|
+
export function list(db: Db.Db, orgId: string): Promise<Record[]> {
|
|
64
|
+
return db.kysely
|
|
65
|
+
.selectFrom('invite_links')
|
|
66
|
+
.selectAll()
|
|
67
|
+
.where('orgId', '=', orgId)
|
|
68
|
+
.where('deletedAt', 'is', null)
|
|
69
|
+
.orderBy('createdAt', 'desc')
|
|
70
|
+
.execute()
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Gets an invite link by its management id, including deleted links. */
|
|
74
|
+
export function get(db: Db.Db, id: string): Promise<Record | undefined> {
|
|
75
|
+
return db.kysely.selectFrom('invite_links').selectAll().where('id', '=', id).executeTakeFirst()
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Gets an invite link by its bearer token, including deleted links. */
|
|
79
|
+
export function getByToken(db: Db.Db, token: string): Promise<Record | undefined> {
|
|
80
|
+
return db.kysely
|
|
81
|
+
.selectFrom('invite_links')
|
|
82
|
+
.selectAll()
|
|
83
|
+
.where('token', '=', token)
|
|
84
|
+
.executeTakeFirst()
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Updates an org-scoped non-deleted link. */
|
|
88
|
+
export function update(
|
|
89
|
+
db: Db.Db,
|
|
90
|
+
orgId: string,
|
|
91
|
+
id: string,
|
|
92
|
+
enabled: boolean,
|
|
93
|
+
): Promise<Record | undefined> {
|
|
94
|
+
return db.kysely
|
|
95
|
+
.updateTable('invite_links')
|
|
96
|
+
.set({ enabled, updatedAt: new Date().toISOString() })
|
|
97
|
+
.where('id', '=', id)
|
|
98
|
+
.where('orgId', '=', orgId)
|
|
99
|
+
.where('deletedAt', 'is', null)
|
|
100
|
+
.returningAll()
|
|
101
|
+
.executeTakeFirst()
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Soft deletes an org-scoped link. */
|
|
105
|
+
export function remove(db: Db.Db, orgId: string, id: string): Promise<Record | undefined> {
|
|
106
|
+
const now = new Date().toISOString()
|
|
107
|
+
return db.kysely
|
|
108
|
+
.updateTable('invite_links')
|
|
109
|
+
.set({ deletedAt: now, updatedAt: now })
|
|
110
|
+
.where('id', '=', id)
|
|
111
|
+
.where('orgId', '=', orgId)
|
|
112
|
+
.where('deletedAt', 'is', null)
|
|
113
|
+
.returningAll()
|
|
114
|
+
.executeTakeFirst()
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Lists redemption audit records, newest first. */
|
|
118
|
+
export function listRedemptions(
|
|
119
|
+
db: Db.Db,
|
|
120
|
+
orgId: string,
|
|
121
|
+
inviteLinkId: string,
|
|
122
|
+
): Promise<Redemption[]> {
|
|
123
|
+
return db.kysely
|
|
124
|
+
.selectFrom('invite_link_redemptions')
|
|
125
|
+
.selectAll()
|
|
126
|
+
.where('orgId', '=', orgId)
|
|
127
|
+
.where('inviteLinkId', '=', inviteLinkId)
|
|
128
|
+
.orderBy('createdAt', 'desc')
|
|
129
|
+
.execute()
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Atomically joins through an active link, consuming only a newly inserted membership. */
|
|
133
|
+
export function accept(
|
|
134
|
+
db: Db.Db,
|
|
135
|
+
token: string,
|
|
136
|
+
userId: string,
|
|
137
|
+
email: string,
|
|
138
|
+
): Promise<{ link: Record; consumed: boolean } | undefined> {
|
|
139
|
+
return db.kysely.transaction().execute(async (trx) => {
|
|
140
|
+
const now = new Date().toISOString()
|
|
141
|
+
const link = await trx
|
|
142
|
+
.selectFrom('invite_links')
|
|
143
|
+
.selectAll()
|
|
144
|
+
.where('token', '=', token)
|
|
145
|
+
.forUpdate()
|
|
146
|
+
.executeTakeFirst()
|
|
147
|
+
if (!link) return undefined
|
|
148
|
+
// Existing membership is already the desired end state, so retries remain
|
|
149
|
+
// successful after the link becomes unavailable and never consume capacity.
|
|
150
|
+
const existing = await trx
|
|
151
|
+
.selectFrom('memberships')
|
|
152
|
+
.select('userId')
|
|
153
|
+
.where('orgId', '=', link.orgId)
|
|
154
|
+
.where('userId', '=', userId)
|
|
155
|
+
.executeTakeFirst()
|
|
156
|
+
if (existing) return { consumed: false, link }
|
|
157
|
+
if (status(link, now) !== 'active') return undefined
|
|
158
|
+
const inserted = await trx
|
|
159
|
+
.insertInto('memberships')
|
|
160
|
+
.values({ createdAt: now, orgId: link.orgId, role: 'member', updatedAt: now, userId })
|
|
161
|
+
.onConflict((oc) => oc.columns(['orgId', 'userId']).doNothing())
|
|
162
|
+
.returning('userId')
|
|
163
|
+
.executeTakeFirst()
|
|
164
|
+
if (!inserted) return { consumed: false, link }
|
|
165
|
+
await trx
|
|
166
|
+
.updateTable('invite_links')
|
|
167
|
+
.set({ lastUsedAt: now, updatedAt: now, useCount: link.useCount + 1 })
|
|
168
|
+
.where('id', '=', link.id)
|
|
169
|
+
.execute()
|
|
170
|
+
await trx
|
|
171
|
+
.insertInto('invite_link_redemptions')
|
|
172
|
+
.values({
|
|
173
|
+
createdAt: now,
|
|
174
|
+
email: email.toLowerCase(),
|
|
175
|
+
id: Id.generate('ilr'),
|
|
176
|
+
inviteLinkId: link.id,
|
|
177
|
+
inviteLinkName: link.name,
|
|
178
|
+
orgId: link.orgId,
|
|
179
|
+
userId,
|
|
180
|
+
})
|
|
181
|
+
.execute()
|
|
182
|
+
return {
|
|
183
|
+
consumed: true,
|
|
184
|
+
link: { ...link, lastUsedAt: now, updatedAt: now, useCount: link.useCount + 1 },
|
|
185
|
+
}
|
|
186
|
+
})
|
|
187
|
+
}
|
|
@@ -112,6 +112,8 @@ export function deleteOrganization(db: Db.Db, id: string): Promise<boolean> {
|
|
|
112
112
|
.execute()
|
|
113
113
|
await trx.deleteFrom('billing_settings').where('orgId', '=', id).execute()
|
|
114
114
|
await trx.deleteFrom('invitations').where('orgId', '=', id).execute()
|
|
115
|
+
await trx.deleteFrom('invite_link_redemptions').where('orgId', '=', id).execute()
|
|
116
|
+
await trx.deleteFrom('invite_links').where('orgId', '=', id).execute()
|
|
115
117
|
await trx.deleteFrom('memberships').where('orgId', '=', id).execute()
|
|
116
118
|
await trx.deleteFrom('projects').where('orgId', '=', id).execute()
|
|
117
119
|
await trx.deleteFrom('request_usage_buckets').where('orgId', '=', id).execute()
|
package/src/handlers/relay.ts
CHANGED
|
@@ -116,15 +116,27 @@ export function relay(options: relay.Options = {}): Handler {
|
|
|
116
116
|
) {
|
|
117
117
|
await onRequest?.(request)
|
|
118
118
|
|
|
119
|
-
// Resolve chainId from: 1) explicit option (URL path), 2) first param object, 3) default chain.
|
|
120
119
|
const params = 'params' in request && Array.isArray(request.params) ? request.params : []
|
|
121
120
|
const first =
|
|
122
121
|
typeof params[0] === 'object' && params[0]
|
|
123
122
|
? (params[0] as Record<string, unknown>)
|
|
124
123
|
: undefined
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
124
|
+
const chainId_body = Utils.resolveChainId(first?.['chainId'])
|
|
125
|
+
const chainId_path = options?.chainId
|
|
126
|
+
if (chainId_body !== undefined && chainId_path !== undefined && chainId_body !== chainId_path)
|
|
127
|
+
return Utils.rpcErrorJson(
|
|
128
|
+
request,
|
|
129
|
+
new RpcResponse.InvalidParamsError({ message: 'Conflicting chain ids.' }),
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
let chainId: number | undefined
|
|
133
|
+
let client: Client
|
|
134
|
+
try {
|
|
135
|
+
chainId = chainId_path ?? chainId_body ?? getClient().chain?.id
|
|
136
|
+
client = getClient(chainId)
|
|
137
|
+
} catch (error) {
|
|
138
|
+
return Utils.rpcErrorJson(request, error)
|
|
139
|
+
}
|
|
128
140
|
|
|
129
141
|
switch (request.method) {
|
|
130
142
|
case 'eth_fillTransaction': {
|
|
@@ -1010,6 +1010,38 @@ describe('require', () => {
|
|
|
1010
1010
|
})
|
|
1011
1011
|
})
|
|
1012
1012
|
|
|
1013
|
+
test('session challenge uses the explicit request chain', async () => {
|
|
1014
|
+
const mpp = createMppServer({
|
|
1015
|
+
chainId: Viem.chainId.mainnet,
|
|
1016
|
+
getClient: ({ chainId = Viem.chainId.mainnet }) => Viem.getClient({ chainId }),
|
|
1017
|
+
})
|
|
1018
|
+
const app = createApp({
|
|
1019
|
+
auth: {
|
|
1020
|
+
apiKey: { resolve: TestApp.resolver({ keys: [{ ...key, token }] }) },
|
|
1021
|
+
mpp,
|
|
1022
|
+
rateLimit: overQuota(),
|
|
1023
|
+
},
|
|
1024
|
+
policy: {
|
|
1025
|
+
mpp: { session: { amount: '1' } },
|
|
1026
|
+
public: { rateLimit: { perMinute: 1 } },
|
|
1027
|
+
},
|
|
1028
|
+
})
|
|
1029
|
+
|
|
1030
|
+
const defaultResponse = await app.request('/protected')
|
|
1031
|
+
const testnetResponse = await app.request('/protected?chainId=testnet')
|
|
1032
|
+
|
|
1033
|
+
expect(Challenge.fromHeaders(defaultResponse.headers).request).toMatchObject({
|
|
1034
|
+
methodDetails: {
|
|
1035
|
+
chainId: Viem.chainId.mainnet,
|
|
1036
|
+
},
|
|
1037
|
+
})
|
|
1038
|
+
expect(Challenge.fromHeaders(testnetResponse.headers).request).toMatchObject({
|
|
1039
|
+
methodDetails: {
|
|
1040
|
+
chainId: Viem.chainId.testnet,
|
|
1041
|
+
},
|
|
1042
|
+
})
|
|
1043
|
+
})
|
|
1044
|
+
|
|
1013
1045
|
test('route session overrides propagate to the challenge', async () => {
|
|
1014
1046
|
const mpp = createMppServer()
|
|
1015
1047
|
const app = createApp({
|
|
@@ -1657,29 +1689,74 @@ describe('require', () => {
|
|
|
1657
1689
|
})
|
|
1658
1690
|
|
|
1659
1691
|
describe('ensureProject', () => {
|
|
1660
|
-
test('authorizes
|
|
1692
|
+
test('authorizes an organization-attributed API key', async () => {
|
|
1661
1693
|
const db = TestApp.database()
|
|
1662
1694
|
const organization = await Organizations.create(db, { id: key.orgId, name: 'Test org' })
|
|
1663
1695
|
const project = await Projects.create(db, { name: 'Test project', orgId: organization.id })
|
|
1664
|
-
const response = await createProjectGuardApp(
|
|
1665
|
-
|
|
1666
|
-
|
|
1696
|
+
const response = await createProjectGuardApp(
|
|
1697
|
+
db,
|
|
1698
|
+
apiKeyPrincipal({ orgId: organization.id }),
|
|
1699
|
+
).request(`/v1/projects/${project.id}/resource`)
|
|
1700
|
+
const { orgId, projectId, ...body } = await response.json()
|
|
1667
1701
|
|
|
1668
1702
|
expect(response.status).toMatchInlineSnapshot(`200`)
|
|
1669
|
-
expect(
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1703
|
+
expect(orgId).toBe(organization.id)
|
|
1704
|
+
expect(projectId).toBe(project.id)
|
|
1705
|
+
expect(body).toMatchInlineSnapshot(`
|
|
1706
|
+
{
|
|
1707
|
+
"membership": null,
|
|
1708
|
+
}
|
|
1709
|
+
`)
|
|
1710
|
+
})
|
|
1711
|
+
|
|
1712
|
+
test('authorizes a project-attributed API key for its project', async () => {
|
|
1713
|
+
const db = TestApp.database()
|
|
1714
|
+
const organization = await Organizations.create(db, { id: key.orgId, name: 'Test org' })
|
|
1715
|
+
const project = await Projects.create(db, { name: 'Test project', orgId: organization.id })
|
|
1716
|
+
const response = await createProjectGuardApp(
|
|
1717
|
+
db,
|
|
1718
|
+
apiKeyPrincipal({ orgId: organization.id, projectId: project.id }),
|
|
1719
|
+
).request(`/v1/projects/${project.id}/resource`)
|
|
1720
|
+
const { orgId, projectId, ...body } = await response.json()
|
|
1721
|
+
|
|
1722
|
+
expect(response.status).toMatchInlineSnapshot(`200`)
|
|
1723
|
+
expect(orgId).toBe(organization.id)
|
|
1724
|
+
expect(projectId).toBe(project.id)
|
|
1725
|
+
expect(body).toMatchInlineSnapshot(`
|
|
1726
|
+
{
|
|
1727
|
+
"membership": null,
|
|
1728
|
+
}
|
|
1729
|
+
`)
|
|
1730
|
+
})
|
|
1731
|
+
|
|
1732
|
+
test('hides another project from a project-attributed API key', async () => {
|
|
1733
|
+
const db = TestApp.database()
|
|
1734
|
+
const organization = await Organizations.create(db, { id: key.orgId, name: 'Test org' })
|
|
1735
|
+
const project = await Projects.create(db, { name: 'Test project', orgId: organization.id })
|
|
1736
|
+
const response = await createProjectGuardApp(
|
|
1737
|
+
db,
|
|
1738
|
+
apiKeyPrincipal({ orgId: organization.id, projectId: 'prj_other' }),
|
|
1739
|
+
).request(`/v1/projects/${project.id}/resource`)
|
|
1740
|
+
|
|
1741
|
+
expect(response.status).toMatchInlineSnapshot(`404`)
|
|
1742
|
+
expect(await response.json()).toMatchInlineSnapshot(`
|
|
1743
|
+
{
|
|
1744
|
+
"error": {
|
|
1745
|
+
"code": "project_not_found",
|
|
1746
|
+
"message": "Project not found",
|
|
1747
|
+
},
|
|
1748
|
+
}
|
|
1749
|
+
`)
|
|
1674
1750
|
})
|
|
1675
1751
|
|
|
1676
1752
|
test('hides a flat project from an API key in another organization', async () => {
|
|
1677
1753
|
const db = TestApp.database()
|
|
1678
1754
|
const organization = await Organizations.create(db, { id: key.orgId, name: 'Test org' })
|
|
1679
1755
|
const project = await Projects.create(db, { name: 'Test project', orgId: organization.id })
|
|
1680
|
-
const response = await createProjectGuardApp(
|
|
1681
|
-
|
|
1682
|
-
|
|
1756
|
+
const response = await createProjectGuardApp(
|
|
1757
|
+
db,
|
|
1758
|
+
apiKeyPrincipal({ orgId: 'org_foreign' }),
|
|
1759
|
+
).request(`/v1/projects/${project.id}/resource`)
|
|
1683
1760
|
|
|
1684
1761
|
expect(response.status).toMatchInlineSnapshot(`404`)
|
|
1685
1762
|
expect(await response.json()).toMatchInlineSnapshot(`
|
|
@@ -1703,14 +1780,16 @@ describe('ensureProject', () => {
|
|
|
1703
1780
|
const response = await createProjectGuardApp(db, sessionPrincipal(user.id)).request(
|
|
1704
1781
|
`/v1/projects/${project.id}/resource`,
|
|
1705
1782
|
)
|
|
1706
|
-
const body =
|
|
1783
|
+
const { orgId, projectId, ...body } = await response.json()
|
|
1707
1784
|
|
|
1708
1785
|
expect(response.status).toMatchInlineSnapshot(`200`)
|
|
1709
|
-
expect(
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1786
|
+
expect(orgId).toBe(organization.id)
|
|
1787
|
+
expect(projectId).toBe(project.id)
|
|
1788
|
+
expect(body).toMatchInlineSnapshot(`
|
|
1789
|
+
{
|
|
1790
|
+
"membership": "member",
|
|
1791
|
+
}
|
|
1792
|
+
`)
|
|
1714
1793
|
})
|
|
1715
1794
|
|
|
1716
1795
|
test('preserves nested organization role authorization', async () => {
|
|
@@ -1735,6 +1814,26 @@ describe('ensureProject', () => {
|
|
|
1735
1814
|
}
|
|
1736
1815
|
`)
|
|
1737
1816
|
})
|
|
1817
|
+
|
|
1818
|
+
test('hides another nested project from a project-attributed API key', async () => {
|
|
1819
|
+
const db = TestApp.database()
|
|
1820
|
+
const organization = await Organizations.create(db, { id: key.orgId, name: 'Test org' })
|
|
1821
|
+
const project = await Projects.create(db, { name: 'Test project', orgId: organization.id })
|
|
1822
|
+
const response = await createNestedProjectGuardApp(
|
|
1823
|
+
db,
|
|
1824
|
+
apiKeyPrincipal({ orgId: organization.id, projectId: 'prj_other' }),
|
|
1825
|
+
).request(`/v1/orgs/${organization.id}/projects/${project.id}/resource`)
|
|
1826
|
+
|
|
1827
|
+
expect(response.status).toMatchInlineSnapshot(`404`)
|
|
1828
|
+
expect(await response.json()).toMatchInlineSnapshot(`
|
|
1829
|
+
{
|
|
1830
|
+
"error": {
|
|
1831
|
+
"code": "project_not_found",
|
|
1832
|
+
"message": "Project not found",
|
|
1833
|
+
},
|
|
1834
|
+
}
|
|
1835
|
+
`)
|
|
1836
|
+
})
|
|
1738
1837
|
})
|
|
1739
1838
|
|
|
1740
1839
|
function createScopedApp(options: {
|
|
@@ -1800,13 +1899,21 @@ function projectGuardHandler(c: Context<GuardEnvironment>) {
|
|
|
1800
1899
|
})
|
|
1801
1900
|
}
|
|
1802
1901
|
|
|
1803
|
-
function apiKeyPrincipal(
|
|
1804
|
-
|
|
1902
|
+
function apiKeyPrincipal(options: {
|
|
1903
|
+
orgId: string
|
|
1904
|
+
projectId?: string | undefined
|
|
1905
|
+
}): Auth.Principal {
|
|
1906
|
+
const apiKey = {
|
|
1907
|
+
...key,
|
|
1908
|
+
orgId: options.orgId,
|
|
1909
|
+
...(options.projectId === undefined ? {} : { projectId: options.projectId }),
|
|
1910
|
+
}
|
|
1805
1911
|
return {
|
|
1806
1912
|
apiKey,
|
|
1807
1913
|
environment: apiKey.environment,
|
|
1808
1914
|
id: apiKey.id,
|
|
1809
|
-
orgId,
|
|
1915
|
+
orgId: options.orgId,
|
|
1916
|
+
...(options.projectId === undefined ? {} : { projectId: options.projectId }),
|
|
1810
1917
|
type: 'api_key',
|
|
1811
1918
|
}
|
|
1812
1919
|
}
|
|
@@ -1869,7 +1976,10 @@ function createMppServer(options: createMppServer.Options = {}) {
|
|
|
1869
1976
|
}
|
|
1870
1977
|
|
|
1871
1978
|
declare namespace createMppServer {
|
|
1872
|
-
type Options = Pick<
|
|
1979
|
+
type Options = Pick<
|
|
1980
|
+
NonNullable<Parameters<typeof tempo.session>[0]>,
|
|
1981
|
+
'chainId' | 'getClient' | 'store'
|
|
1982
|
+
>
|
|
1873
1983
|
}
|
|
1874
1984
|
|
|
1875
1985
|
function createMppHandler(
|