tapimo 0.4.1 → 0.4.3
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 +2451 -1533
- 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/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 +1354 -984
- package/dist/apps/data/App.d.ts.map +1 -1
- package/dist/apps/data/FundingRouteProviders.d.ts +94 -5
- package/dist/apps/data/FundingRouteProviders.d.ts.map +1 -1
- package/dist/apps/data/FundingRouteProviders.js +196 -5
- 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/exchanges.d.ts +453 -453
- package/dist/apps/data/routes/fee-amm.d.ts +101 -101
- package/dist/apps/data/routes/funding.d.ts +730 -138
- package/dist/apps/data/routes/funding.d.ts.map +1 -1
- package/dist/apps/data/routes/funding.js +234 -0
- 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/transactions.d.ts +281 -281
- package/dist/apps/data/routes/transfers.d.ts +58 -58
- package/dist/apps/data/routes/verified-tokens.d.ts +81 -81
- package/dist/apps/management/App.d.ts +1156 -615
- 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 +106 -90
- 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 +278 -146
- 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/me.d.ts +6 -6
- 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 +130 -70
- 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 +137 -48
- 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 +35 -3
- 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/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 +13 -18
- package/dist/internal/Auth.d.ts.map +1 -1
- package/dist/internal/Auth.js +78 -58
- 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/FundingRoutes.d.ts +170 -0
- package/dist/internal/FundingRoutes.d.ts.map +1 -1
- package/dist/internal/FundingRoutes.js +68 -6
- 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/package.json +2 -2
- package/src/ApiKey.ts +2 -3
- package/src/ApiKeys.ts +95 -17
- package/src/App.test.ts +44 -2
- 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 +64 -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 +204 -0
- package/src/apps/data/FundingRouteProviders.ts +294 -9
- package/src/apps/data/routes/funding.test.ts +90 -23
- package/src/apps/data/routes/funding.ts +315 -0
- 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 +25 -1
- package/src/apps/mcp.ts +43 -3
- package/src/apps/relay/App.test.ts +23 -18
- package/src/apps/relay/App.ts +35 -14
- 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 +174 -7
- package/src/internal/Auth.ts +90 -62
- 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 +26 -0
- package/src/internal/FundingRoutes.ts +187 -18
- package/src/internal/Log.test.ts +41 -3
- package/src/internal/Log.ts +23 -4
|
@@ -81,6 +81,53 @@ export function listByOrg(db: Db.Db, orgId: string): Promise<Record[]> {
|
|
|
81
81
|
.execute()
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
/**
|
|
85
|
+
* Lists a bounded page of an organization's projects, newest first.
|
|
86
|
+
*
|
|
87
|
+
* @param db - The database.
|
|
88
|
+
* @param orgId - The organization id.
|
|
89
|
+
* @param options - Keyset and page-size options.
|
|
90
|
+
* @returns At most `limit + 1` rows for next-cursor derivation.
|
|
91
|
+
*/
|
|
92
|
+
export function listByOrgPage(
|
|
93
|
+
db: Db.Db,
|
|
94
|
+
orgId: string,
|
|
95
|
+
options: listByOrgPage.Options,
|
|
96
|
+
): Promise<Record[]> {
|
|
97
|
+
const cursor = options.cursor
|
|
98
|
+
let query = db.kysely.selectFrom('projects').selectAll().where('orgId', '=', orgId)
|
|
99
|
+
if (cursor)
|
|
100
|
+
query = query.where((eb) =>
|
|
101
|
+
eb.or([
|
|
102
|
+
eb('createdAt', '<', cursor.createdAt),
|
|
103
|
+
eb.and([eb('createdAt', '=', cursor.createdAt), eb('id', '<', cursor.id)]),
|
|
104
|
+
]),
|
|
105
|
+
)
|
|
106
|
+
return query
|
|
107
|
+
.orderBy('createdAt', 'desc')
|
|
108
|
+
.orderBy('id', 'desc')
|
|
109
|
+
.limit(options.limit + 1)
|
|
110
|
+
.execute()
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export declare namespace listByOrgPage {
|
|
114
|
+
/** Cursor fields for the last project on the previous page. */
|
|
115
|
+
type Cursor = {
|
|
116
|
+
/** Project creation time. */
|
|
117
|
+
createdAt: string
|
|
118
|
+
/** Project id, used as a deterministic tie-breaker. */
|
|
119
|
+
id: string
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Options for {@link listByOrgPage}. */
|
|
123
|
+
type Options = {
|
|
124
|
+
/** Last project returned by the previous page. */
|
|
125
|
+
cursor?: Cursor | undefined
|
|
126
|
+
/** Requested page size. */
|
|
127
|
+
limit: number
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
84
131
|
/**
|
|
85
132
|
* Renames a project, bumping `updatedAt`.
|
|
86
133
|
*
|
|
@@ -10,6 +10,12 @@ export type Table = db_Schema.WebhookDelivery
|
|
|
10
10
|
/** A stored webhook-delivery row. */
|
|
11
11
|
export type Record = Selectable<Table>
|
|
12
12
|
|
|
13
|
+
/** A failed delivery joined to its organization-owned subscription. */
|
|
14
|
+
export type OrganizationFailure = Pick<
|
|
15
|
+
Record,
|
|
16
|
+
'attempt' | 'createdAt' | 'error' | 'id' | 'responseStatus' | 'subscriptionId'
|
|
17
|
+
>
|
|
18
|
+
|
|
13
19
|
/** Maps a stored row to the domain delivery (null columns → absent fields). */
|
|
14
20
|
export function toDelivery(row: Record): Webhooks.Delivery {
|
|
15
21
|
return {
|
|
@@ -85,6 +91,83 @@ export async function list(
|
|
|
85
91
|
return (await query.execute()).map(toDelivery)
|
|
86
92
|
}
|
|
87
93
|
|
|
94
|
+
/** Lists recent failed webhook deliveries across an organization. */
|
|
95
|
+
export function listFailuresByOrg(
|
|
96
|
+
db: Db.Db,
|
|
97
|
+
orgId: string,
|
|
98
|
+
options: listFailuresByOrg.Options,
|
|
99
|
+
): Promise<OrganizationFailure[]> {
|
|
100
|
+
return db.kysely
|
|
101
|
+
.selectFrom('webhook_deliveries')
|
|
102
|
+
.innerJoin(
|
|
103
|
+
'webhook_subscriptions',
|
|
104
|
+
'webhook_subscriptions.id',
|
|
105
|
+
'webhook_deliveries.subscriptionId',
|
|
106
|
+
)
|
|
107
|
+
.select([
|
|
108
|
+
'webhook_deliveries.attempt',
|
|
109
|
+
'webhook_deliveries.createdAt',
|
|
110
|
+
'webhook_deliveries.error',
|
|
111
|
+
'webhook_deliveries.id',
|
|
112
|
+
'webhook_deliveries.responseStatus',
|
|
113
|
+
'webhook_deliveries.subscriptionId',
|
|
114
|
+
])
|
|
115
|
+
.where('webhook_subscriptions.ownerId', '=', orgId)
|
|
116
|
+
.where('webhook_subscriptions.ownerType', '=', 'api_key')
|
|
117
|
+
.where('webhook_deliveries.createdAt', '>=', options.from)
|
|
118
|
+
.where('webhook_deliveries.expiresAt', '>', options.now)
|
|
119
|
+
.where('webhook_deliveries.status', '=', 'failed')
|
|
120
|
+
.orderBy('webhook_deliveries.createdAt', 'desc')
|
|
121
|
+
.orderBy('webhook_deliveries.id', 'desc')
|
|
122
|
+
.limit(options.limit)
|
|
123
|
+
.execute()
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export declare namespace listFailuresByOrg {
|
|
127
|
+
/** Bounds for the organization failure scan. */
|
|
128
|
+
type Options = {
|
|
129
|
+
/** Window start (ISO 8601), inclusive. */
|
|
130
|
+
from: string
|
|
131
|
+
/** Maximum failures to return. */
|
|
132
|
+
limit: number
|
|
133
|
+
/** ISO timestamp retention is evaluated against. */
|
|
134
|
+
now: string
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Counts failed webhook deliveries across an organization in a time window. */
|
|
139
|
+
export async function countFailuresByOrg(
|
|
140
|
+
db: Db.Db,
|
|
141
|
+
orgId: string,
|
|
142
|
+
options: countFailuresByOrg.Options,
|
|
143
|
+
): Promise<number> {
|
|
144
|
+
const row = await db.kysely
|
|
145
|
+
.selectFrom('webhook_deliveries')
|
|
146
|
+
.innerJoin(
|
|
147
|
+
'webhook_subscriptions',
|
|
148
|
+
'webhook_subscriptions.id',
|
|
149
|
+
'webhook_deliveries.subscriptionId',
|
|
150
|
+
)
|
|
151
|
+
.select((eb) => eb.fn.countAll().as('count'))
|
|
152
|
+
.where('webhook_subscriptions.ownerId', '=', orgId)
|
|
153
|
+
.where('webhook_subscriptions.ownerType', '=', 'api_key')
|
|
154
|
+
.where('webhook_deliveries.createdAt', '>=', options.from)
|
|
155
|
+
.where('webhook_deliveries.expiresAt', '>', options.now)
|
|
156
|
+
.where('webhook_deliveries.status', '=', 'failed')
|
|
157
|
+
.executeTakeFirstOrThrow()
|
|
158
|
+
return Number(row.count)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export declare namespace countFailuresByOrg {
|
|
162
|
+
/** Bounds for the organization failure count. */
|
|
163
|
+
type Options = {
|
|
164
|
+
/** Window start (ISO 8601), inclusive. */
|
|
165
|
+
from: string
|
|
166
|
+
/** ISO timestamp retention is evaluated against. */
|
|
167
|
+
now: string
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
88
171
|
export declare namespace list {
|
|
89
172
|
/** Options for {@link list}. */
|
|
90
173
|
type Options = {
|
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
import { Hono } from 'hono'
|
|
1
|
+
import { Hono, type Context } from 'hono'
|
|
2
2
|
import { requestId } from 'hono/request-id'
|
|
3
3
|
import { Mppx, tempo } from 'mppx/hono'
|
|
4
4
|
import { RateLimit } from 'tapimo'
|
|
5
5
|
import * as ApiKey from '../ApiKey.js'
|
|
6
|
+
import type * as Db from '../db/Db.js'
|
|
7
|
+
import * as Memberships from '../db/tables/memberships.js'
|
|
8
|
+
import * as Organizations from '../db/tables/organizations.js'
|
|
9
|
+
import * as Projects from '../db/tables/projects.js'
|
|
10
|
+
import * as Users from '../db/tables/users.js'
|
|
6
11
|
import * as Auth from './Auth.js'
|
|
7
12
|
import * as Log from './Log.js'
|
|
8
13
|
import * as Scope from '../Scope.js'
|
|
@@ -75,12 +80,13 @@ describe('require', () => {
|
|
|
75
80
|
`)
|
|
76
81
|
})
|
|
77
82
|
|
|
78
|
-
test('accepts API key from
|
|
83
|
+
test('accepts API key from key query param', async () => {
|
|
79
84
|
const app = createApp()
|
|
80
85
|
const response = await app.request(`/protected?key=${token}`)
|
|
81
86
|
const body = await response.json()
|
|
82
87
|
|
|
83
88
|
expect(response.status).toMatchInlineSnapshot(`200`)
|
|
89
|
+
expect(response.headers.get('cache-control')).toMatchInlineSnapshot(`"private"`)
|
|
84
90
|
expect(body).toMatchInlineSnapshot(`
|
|
85
91
|
{
|
|
86
92
|
"id": "key_test",
|
|
@@ -90,11 +96,30 @@ describe('require', () => {
|
|
|
90
96
|
`)
|
|
91
97
|
})
|
|
92
98
|
|
|
93
|
-
test('
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
99
|
+
test('keeps query-authenticated responses private after downstream caching', async () => {
|
|
100
|
+
const auth = {
|
|
101
|
+
apiKey: { resolve: TestApp.resolver({ keys: [{ ...key, token }] }) },
|
|
102
|
+
rateLimit: RateLimit.memory(),
|
|
103
|
+
}
|
|
104
|
+
const app = new Hono<Auth.Environment>()
|
|
105
|
+
app.use('*', async (c, next) => {
|
|
106
|
+
c.set('auth', auth)
|
|
107
|
+
await next()
|
|
108
|
+
})
|
|
109
|
+
app.use('/cached', Auth.require({ apiKey: { scopes: ['data:read'] } }))
|
|
110
|
+
app.get('/cached', (c) => {
|
|
111
|
+
c.header('Cache-Control', 'public, max-age=86400')
|
|
112
|
+
return c.json({ ok: true })
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
const response = await app.request(`/cached?key=${token}`)
|
|
116
|
+
|
|
117
|
+
expect(response.status).toMatchInlineSnapshot(`200`)
|
|
118
|
+
expect(response.headers.get('cache-control')).toMatchInlineSnapshot(`"private"`)
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
test('strips the key query param before route validation', async () => {
|
|
122
|
+
// Auth owns `key`, so strict route schemas must not see it.
|
|
98
123
|
const auth = {
|
|
99
124
|
apiKey: { resolve: TestApp.resolver({ keys: [{ ...key, token }] }) },
|
|
100
125
|
rateLimit: RateLimit.memory(),
|
|
@@ -1298,6 +1323,87 @@ describe('require', () => {
|
|
|
1298
1323
|
})
|
|
1299
1324
|
})
|
|
1300
1325
|
|
|
1326
|
+
describe('ensureProject', () => {
|
|
1327
|
+
test('authorizes a flat project route against the API key organization', async () => {
|
|
1328
|
+
const db = TestApp.database()
|
|
1329
|
+
const organization = await Organizations.create(db, { id: key.orgId, name: 'Test org' })
|
|
1330
|
+
const project = await Projects.create(db, { name: 'Test project', orgId: organization.id })
|
|
1331
|
+
const response = await createProjectGuardApp(db, apiKeyPrincipal(organization.id)).request(
|
|
1332
|
+
`/v1/projects/${project.id}/resource`,
|
|
1333
|
+
)
|
|
1334
|
+
|
|
1335
|
+
expect(response.status).toMatchInlineSnapshot(`200`)
|
|
1336
|
+
expect(await response.json()).toEqual({
|
|
1337
|
+
membership: null,
|
|
1338
|
+
orgId: organization.id,
|
|
1339
|
+
projectId: project.id,
|
|
1340
|
+
})
|
|
1341
|
+
})
|
|
1342
|
+
|
|
1343
|
+
test('hides a flat project from an API key in another organization', async () => {
|
|
1344
|
+
const db = TestApp.database()
|
|
1345
|
+
const organization = await Organizations.create(db, { id: key.orgId, name: 'Test org' })
|
|
1346
|
+
const project = await Projects.create(db, { name: 'Test project', orgId: organization.id })
|
|
1347
|
+
const response = await createProjectGuardApp(db, apiKeyPrincipal('org_foreign')).request(
|
|
1348
|
+
`/v1/projects/${project.id}/resource`,
|
|
1349
|
+
)
|
|
1350
|
+
|
|
1351
|
+
expect(response.status).toMatchInlineSnapshot(`404`)
|
|
1352
|
+
expect(await response.json()).toMatchInlineSnapshot(`
|
|
1353
|
+
{
|
|
1354
|
+
"error": {
|
|
1355
|
+
"code": "project_not_found",
|
|
1356
|
+
"message": "Project not found",
|
|
1357
|
+
},
|
|
1358
|
+
}
|
|
1359
|
+
`)
|
|
1360
|
+
})
|
|
1361
|
+
|
|
1362
|
+
test('resolves session membership for a flat project route', async () => {
|
|
1363
|
+
const db = TestApp.database()
|
|
1364
|
+
const user = await Users.upsertByAddress(db, {
|
|
1365
|
+
address: '0x0000000000000000000000000000000000000001',
|
|
1366
|
+
})
|
|
1367
|
+
const organization = await Organizations.create(db, { name: 'Test org' })
|
|
1368
|
+
await Memberships.create(db, { orgId: organization.id, role: 'member', userId: user.id })
|
|
1369
|
+
const project = await Projects.create(db, { name: 'Test project', orgId: organization.id })
|
|
1370
|
+
const response = await createProjectGuardApp(db, sessionPrincipal(user.id)).request(
|
|
1371
|
+
`/v1/projects/${project.id}/resource`,
|
|
1372
|
+
)
|
|
1373
|
+
const body = (await response.json()) as Record<string, unknown>
|
|
1374
|
+
|
|
1375
|
+
expect(response.status).toMatchInlineSnapshot(`200`)
|
|
1376
|
+
expect(body).toEqual({
|
|
1377
|
+
membership: 'member',
|
|
1378
|
+
orgId: organization.id,
|
|
1379
|
+
projectId: project.id,
|
|
1380
|
+
})
|
|
1381
|
+
})
|
|
1382
|
+
|
|
1383
|
+
test('preserves nested organization role authorization', async () => {
|
|
1384
|
+
const db = TestApp.database()
|
|
1385
|
+
const user = await Users.upsertByAddress(db, {
|
|
1386
|
+
address: '0x0000000000000000000000000000000000000001',
|
|
1387
|
+
})
|
|
1388
|
+
const organization = await Organizations.create(db, { name: 'Test org' })
|
|
1389
|
+
await Memberships.create(db, { orgId: organization.id, role: 'member', userId: user.id })
|
|
1390
|
+
const project = await Projects.create(db, { name: 'Test project', orgId: organization.id })
|
|
1391
|
+
const response = await createNestedProjectGuardApp(db, sessionPrincipal(user.id)).request(
|
|
1392
|
+
`/v1/orgs/${organization.id}/projects/${project.id}/resource`,
|
|
1393
|
+
)
|
|
1394
|
+
|
|
1395
|
+
expect(response.status).toMatchInlineSnapshot(`403`)
|
|
1396
|
+
expect(await response.json()).toMatchInlineSnapshot(`
|
|
1397
|
+
{
|
|
1398
|
+
"error": {
|
|
1399
|
+
"code": "forbidden",
|
|
1400
|
+
"message": "Requires the admin role",
|
|
1401
|
+
},
|
|
1402
|
+
}
|
|
1403
|
+
`)
|
|
1404
|
+
})
|
|
1405
|
+
})
|
|
1406
|
+
|
|
1301
1407
|
function createScopedApp(options: {
|
|
1302
1408
|
auth?: Auth.Context | undefined
|
|
1303
1409
|
routes: readonly { path: string; policy: Auth.require.Policy }[]
|
|
@@ -1322,6 +1428,67 @@ function createScopedApp(options: {
|
|
|
1322
1428
|
return app
|
|
1323
1429
|
}
|
|
1324
1430
|
|
|
1431
|
+
type GuardEnvironment = {
|
|
1432
|
+
Variables: Auth.Variables & { db: Db.Source }
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
function createProjectGuardApp(db: Db.Source, principal: Auth.Principal) {
|
|
1436
|
+
const app = new Hono<GuardEnvironment>()
|
|
1437
|
+
app.use('*', async (c, next) => {
|
|
1438
|
+
c.set('db', db)
|
|
1439
|
+
c.set('principal', principal)
|
|
1440
|
+
await next()
|
|
1441
|
+
})
|
|
1442
|
+
app.get('/v1/projects/:projectId/resource', Auth.ensureProject(), projectGuardHandler)
|
|
1443
|
+
return app
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
function createNestedProjectGuardApp(db: Db.Source, principal: Auth.Principal) {
|
|
1447
|
+
const app = new Hono<GuardEnvironment>()
|
|
1448
|
+
app.use('*', async (c, next) => {
|
|
1449
|
+
c.set('db', db)
|
|
1450
|
+
c.set('principal', principal)
|
|
1451
|
+
await next()
|
|
1452
|
+
})
|
|
1453
|
+
app.get(
|
|
1454
|
+
'/v1/orgs/:orgId/projects/:projectId/resource',
|
|
1455
|
+
Auth.ensureOrg({ role: 'admin' }),
|
|
1456
|
+
Auth.ensureProject(),
|
|
1457
|
+
projectGuardHandler,
|
|
1458
|
+
)
|
|
1459
|
+
return app
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
function projectGuardHandler(c: Context<GuardEnvironment>) {
|
|
1463
|
+
return c.json({
|
|
1464
|
+
membership: Auth.membership(c)?.role ?? null,
|
|
1465
|
+
orgId: Auth.org(c).id,
|
|
1466
|
+
projectId: Auth.project(c).id,
|
|
1467
|
+
})
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
function apiKeyPrincipal(orgId: string): Auth.Principal {
|
|
1471
|
+
const apiKey = { ...key, orgId }
|
|
1472
|
+
return {
|
|
1473
|
+
apiKey,
|
|
1474
|
+
environment: apiKey.environment,
|
|
1475
|
+
id: apiKey.id,
|
|
1476
|
+
orgId,
|
|
1477
|
+
type: 'api_key',
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
function sessionPrincipal(id: string): Auth.Principal {
|
|
1482
|
+
return {
|
|
1483
|
+
id,
|
|
1484
|
+
identity: {
|
|
1485
|
+
provider: 'wallet',
|
|
1486
|
+
subject: '0x0000000000000000000000000000000000000001',
|
|
1487
|
+
},
|
|
1488
|
+
type: 'session',
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1325
1492
|
type Options = {
|
|
1326
1493
|
auth?: Auth.Context | undefined
|
|
1327
1494
|
logger?: Log.Emit | undefined
|