tapimo 0.5.2 → 0.6.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 +13987 -5755
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js.map +1 -1
- package/dist/admin/App.d.ts +910 -412
- package/dist/admin/App.d.ts.map +1 -1
- package/dist/admin/App.js +4 -0
- package/dist/admin/App.js.map +1 -1
- package/dist/admin/apps/early-access.d.ts +148 -0
- package/dist/admin/apps/early-access.d.ts.map +1 -0
- package/dist/admin/apps/early-access.js +133 -0
- package/dist/admin/apps/early-access.js.map +1 -0
- package/dist/admin/apps/verified-tokens.d.ts +643 -118
- package/dist/admin/apps/verified-tokens.d.ts.map +1 -1
- package/dist/admin/apps/verified-tokens.js +214 -1
- package/dist/admin/apps/verified-tokens.js.map +1 -1
- package/dist/admin/ui.generated.d.ts +1 -1
- package/dist/admin/ui.generated.d.ts.map +1 -1
- package/dist/admin/ui.generated.js +45 -33
- package/dist/admin/ui.generated.js.map +1 -1
- package/dist/apps/data/App.d.ts +3733 -2428
- package/dist/apps/data/App.d.ts.map +1 -1
- package/dist/apps/data/App.js +11 -6
- package/dist/apps/data/App.js.map +1 -1
- package/dist/apps/data/FundingQuoteProviders.d.ts +34 -1
- package/dist/apps/data/FundingQuoteProviders.d.ts.map +1 -1
- package/dist/apps/data/FundingQuoteProviders.js +193 -2
- package/dist/apps/data/FundingQuoteProviders.js.map +1 -1
- package/dist/apps/data/FxOracle.d.ts +71 -0
- package/dist/apps/data/FxOracle.d.ts.map +1 -0
- package/dist/apps/data/FxOracle.js +97 -0
- package/dist/apps/data/FxOracle.js.map +1 -0
- package/dist/apps/data/index.d.ts +1 -0
- package/dist/apps/data/index.d.ts.map +1 -1
- package/dist/apps/data/index.js +1 -0
- package/dist/apps/data/index.js.map +1 -1
- package/dist/apps/data/routes/activities.d.ts +3575 -1358
- package/dist/apps/data/routes/activities.d.ts.map +1 -1
- package/dist/apps/data/routes/activities.js +230 -88
- package/dist/apps/data/routes/activities.js.map +1 -1
- package/dist/apps/data/routes/addresses.d.ts +963 -342
- package/dist/apps/data/routes/addresses.d.ts.map +1 -1
- package/dist/apps/data/routes/addresses.js +11 -9
- package/dist/apps/data/routes/addresses.js.map +1 -1
- package/dist/apps/data/routes/balances.d.ts +108 -57
- package/dist/apps/data/routes/balances.d.ts.map +1 -1
- package/dist/apps/data/routes/balances.js +88 -45
- package/dist/apps/data/routes/balances.js.map +1 -1
- package/dist/apps/data/routes/coingecko.d.ts +169 -169
- package/dist/apps/data/routes/exchanges.d.ts +952 -201
- package/dist/apps/data/routes/exchanges.d.ts.map +1 -1
- package/dist/apps/data/routes/exchanges.js +518 -76
- package/dist/apps/data/routes/exchanges.js.map +1 -1
- package/dist/apps/data/routes/fee-amm.d.ts +24 -24
- package/dist/apps/data/routes/funding.d.ts +8 -2
- package/dist/apps/data/routes/funding.d.ts.map +1 -1
- package/dist/apps/data/routes/funding.js +32 -6
- 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/receipts.d.ts +392 -315
- package/dist/apps/data/routes/receipts.d.ts.map +1 -1
- package/dist/apps/data/routes/receipts.js +152 -77
- package/dist/apps/data/routes/receipts.js.map +1 -1
- package/dist/apps/data/routes/tokens.d.ts +166 -173
- package/dist/apps/data/routes/tokens.d.ts.map +1 -1
- package/dist/apps/data/routes/tokens.js +14 -12
- package/dist/apps/data/routes/tokens.js.map +1 -1
- package/dist/apps/data/routes/transactions.d.ts +277 -536
- package/dist/apps/data/routes/transactions.d.ts.map +1 -1
- package/dist/apps/data/routes/transactions.js +96 -77
- package/dist/apps/data/routes/transactions.js.map +1 -1
- package/dist/apps/data/routes/transfers.d.ts +82 -17
- package/dist/apps/data/routes/transfers.d.ts.map +1 -1
- package/dist/apps/data/routes/transfers.js +70 -13
- package/dist/apps/data/routes/transfers.js.map +1 -1
- package/dist/apps/data/routes/valuation.d.ts +307 -0
- package/dist/apps/data/routes/valuation.d.ts.map +1 -0
- package/dist/apps/data/routes/valuation.js +292 -0
- package/dist/apps/data/routes/valuation.js.map +1 -0
- package/dist/apps/data/routes/verified-tokens.d.ts.map +1 -1
- package/dist/apps/data/routes/verified-tokens.js +7 -5
- package/dist/apps/data/routes/verified-tokens.js.map +1 -1
- package/dist/apps/data/routes/webhooks.d.ts +459 -479
- package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
- package/dist/apps/data/routes/webhooks.js +124 -25
- package/dist/apps/data/routes/webhooks.js.map +1 -1
- package/dist/apps/index.d.ts +1 -1
- package/dist/apps/index.d.ts.map +1 -1
- package/dist/apps/index.js +1 -1
- package/dist/apps/index.js.map +1 -1
- package/dist/apps/management/App.d.ts +8606 -1681
- package/dist/apps/management/App.d.ts.map +1 -1
- package/dist/apps/management/App.js +12 -1
- package/dist/apps/management/App.js.map +1 -1
- package/dist/apps/management/routes/api-keys.d.ts +23 -0
- package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
- package/dist/apps/management/routes/api-keys.js +5 -3
- package/dist/apps/management/routes/api-keys.js.map +1 -1
- package/dist/apps/management/routes/billing.d.ts +88 -48
- package/dist/apps/management/routes/billing.d.ts.map +1 -1
- package/dist/apps/management/routes/billing.js +10 -2
- package/dist/apps/management/routes/billing.js.map +1 -1
- package/dist/apps/management/routes/faucet.d.ts +218 -0
- package/dist/apps/management/routes/faucet.d.ts.map +1 -0
- package/dist/apps/management/routes/faucet.js +139 -0
- package/dist/apps/management/routes/faucet.js.map +1 -0
- package/dist/apps/management/routes/invitations.d.ts +121 -121
- package/dist/apps/management/routes/invitations.d.ts.map +1 -1
- package/dist/apps/management/routes/invitations.js +3 -0
- package/dist/apps/management/routes/invitations.js.map +1 -1
- package/dist/apps/management/routes/invite-links.d.ts +197 -168
- package/dist/apps/management/routes/invite-links.d.ts.map +1 -1
- package/dist/apps/management/routes/invite-links.js +26 -1
- package/dist/apps/management/routes/invite-links.js.map +1 -1
- package/dist/apps/management/routes/me.d.ts +2 -0
- package/dist/apps/management/routes/me.d.ts.map +1 -1
- package/dist/apps/management/routes/me.js +4 -0
- package/dist/apps/management/routes/me.js.map +1 -1
- package/dist/apps/management/routes/members.d.ts +49 -49
- package/dist/apps/management/routes/orgs.d.ts +91 -91
- package/dist/apps/management/routes/projects.d.ts +115 -115
- package/dist/apps/management/routes/usage.d.ts +35 -32
- package/dist/apps/management/routes/usage.d.ts.map +1 -1
- package/dist/apps/management/routes/usage.js +4 -0
- package/dist/apps/management/routes/usage.js.map +1 -1
- package/dist/apps/management/routes/verified-token-requests.d.ts +697 -0
- package/dist/apps/management/routes/verified-token-requests.d.ts.map +1 -0
- package/dist/apps/management/routes/verified-token-requests.js +271 -0
- package/dist/apps/management/routes/verified-token-requests.js.map +1 -0
- package/dist/apps/management/routes/webhooks.d.ts +6977 -0
- package/dist/apps/management/routes/webhooks.d.ts.map +1 -0
- package/dist/apps/management/routes/webhooks.js +573 -0
- package/dist/apps/management/routes/webhooks.js.map +1 -0
- package/dist/apps/mcp.d.ts +9 -2
- package/dist/apps/mcp.d.ts.map +1 -1
- package/dist/apps/mcp.js +9 -4
- package/dist/apps/mcp.js.map +1 -1
- package/dist/cli/Cli.d.ts.map +1 -1
- package/dist/cli/Cli.js +7 -2
- package/dist/cli/Cli.js.map +1 -1
- package/dist/db/Db.d.ts +4 -0
- package/dist/db/Db.d.ts.map +1 -1
- package/dist/db/Db.js +83 -0
- package/dist/db/Db.js.map +1 -1
- package/dist/db/Schema.d.ts +41 -0
- package/dist/db/Schema.d.ts.map +1 -1
- package/dist/db/Schema.js +45 -0
- package/dist/db/Schema.js.map +1 -1
- package/dist/db/tables/earlyAccess.d.ts +50 -0
- package/dist/db/tables/earlyAccess.d.ts.map +1 -0
- package/dist/db/tables/earlyAccess.js +63 -0
- package/dist/db/tables/earlyAccess.js.map +1 -0
- package/dist/db/tables/invitations.d.ts +12 -1
- package/dist/db/tables/invitations.d.ts.map +1 -1
- package/dist/db/tables/invitations.js +23 -0
- package/dist/db/tables/invitations.js.map +1 -1
- package/dist/db/tables/inviteLinks.d.ts +10 -2
- package/dist/db/tables/inviteLinks.d.ts.map +1 -1
- package/dist/db/tables/inviteLinks.js +10 -1
- package/dist/db/tables/inviteLinks.js.map +1 -1
- package/dist/db/tables/sponsoredTransactions.d.ts +2 -0
- package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -1
- package/dist/db/tables/sponsoredTransactions.js +2 -0
- package/dist/db/tables/sponsoredTransactions.js.map +1 -1
- package/dist/db/tables/verifiedTokenRequests.d.ts +94 -0
- package/dist/db/tables/verifiedTokenRequests.d.ts.map +1 -0
- package/dist/db/tables/verifiedTokenRequests.js +120 -0
- package/dist/db/tables/verifiedTokenRequests.js.map +1 -0
- package/dist/db/tables/webhookSubscriptions.d.ts +14 -0
- package/dist/db/tables/webhookSubscriptions.d.ts.map +1 -1
- package/dist/db/tables/webhookSubscriptions.js +16 -0
- package/dist/db/tables/webhookSubscriptions.js.map +1 -1
- package/dist/internal/Auth.d.ts +43 -2
- package/dist/internal/Auth.d.ts.map +1 -1
- package/dist/internal/Auth.js +60 -45
- package/dist/internal/Auth.js.map +1 -1
- package/dist/internal/FundingQuotes.d.ts +69 -15
- package/dist/internal/FundingQuotes.d.ts.map +1 -1
- package/dist/internal/FundingQuotes.js +74 -25
- package/dist/internal/FundingQuotes.js.map +1 -1
- package/dist/internal/Log.d.ts +19 -1
- package/dist/internal/Log.d.ts.map +1 -1
- package/dist/internal/Log.js +2 -0
- package/dist/internal/Log.js.map +1 -1
- package/dist/internal/MetricSink.d.ts.map +1 -1
- package/dist/internal/MetricSink.js +9 -0
- package/dist/internal/MetricSink.js.map +1 -1
- package/dist/internal/OpenApi.d.ts +2 -2
- package/dist/internal/OpenApi.d.ts.map +1 -1
- package/dist/internal/OpenApi.js +3 -0
- package/dist/internal/OpenApi.js.map +1 -1
- package/dist/internal/Schema.d.ts +14 -5
- package/dist/internal/Schema.d.ts.map +1 -1
- package/dist/internal/Schema.js +23 -5
- package/dist/internal/Schema.js.map +1 -1
- package/dist/internal/Value.d.ts +18 -0
- package/dist/internal/Value.d.ts.map +1 -1
- package/dist/internal/Value.js +11 -1
- package/dist/internal/Value.js.map +1 -1
- package/dist/internal/VerifiedTokens.d.ts.map +1 -1
- package/dist/internal/VerifiedTokens.js +10 -1
- package/dist/internal/VerifiedTokens.js.map +1 -1
- package/dist/internal/Viem.d.ts +1 -1
- package/dist/internal/Webhooks.d.ts +24 -3
- package/dist/internal/Webhooks.d.ts.map +1 -1
- package/dist/internal/Webhooks.js +34 -8
- package/dist/internal/Webhooks.js.map +1 -1
- package/package.json +2 -2
- package/src/App.ts +9 -1
- package/src/Client.test-d.ts +638 -6
- package/src/admin/App.ts +4 -0
- package/src/admin/apps/early-access.test.ts +125 -0
- package/src/admin/apps/early-access.ts +175 -0
- package/src/admin/apps/verified-tokens.test.ts +201 -0
- package/src/admin/apps/verified-tokens.ts +532 -286
- package/src/admin/ui/plugin.ts +2 -1
- package/src/admin/ui/src/lib/early-access.ts +63 -0
- package/src/admin/ui/src/lib/verified-tokens.ts +79 -0
- package/src/admin/ui/src/routeTree.gen.ts +33 -2
- package/src/admin/ui/src/routes/__root.tsx +2 -1
- package/src/admin/ui/src/routes/early-access.tsx +157 -0
- package/src/admin/ui/src/routes/verified-tokens.tsx +230 -19
- package/src/admin/ui.generated.ts +48 -33
- package/src/apps/data/App.ts +22 -14
- package/src/apps/data/FundingQuoteProviders.test.ts +298 -0
- package/src/apps/data/FundingQuoteProviders.ts +257 -4
- package/src/apps/data/FxOracle.test.ts +118 -0
- package/src/apps/data/FxOracle.ts +153 -0
- package/src/apps/data/index.ts +1 -0
- package/src/apps/data/routes/activities.test.ts +173 -3
- package/src/apps/data/routes/activities.ts +403 -132
- package/src/apps/data/routes/addresses.ts +19 -9
- package/src/apps/data/routes/balances.test.ts +144 -11
- package/src/apps/data/routes/balances.ts +140 -51
- package/src/apps/data/routes/exchanges.test.ts +540 -9
- package/src/apps/data/routes/exchanges.ts +690 -103
- package/src/apps/data/routes/funding.test.ts +84 -0
- package/src/apps/data/routes/funding.ts +38 -6
- package/src/apps/data/routes/receipts.test.ts +125 -23
- package/src/apps/data/routes/receipts.ts +205 -100
- package/src/apps/data/routes/tokens.test.ts +43 -0
- package/src/apps/data/routes/tokens.ts +27 -12
- package/src/apps/data/routes/transactions.test.ts +72 -19
- package/src/apps/data/routes/transactions.ts +162 -92
- package/src/apps/data/routes/transfers.test.ts +146 -4
- package/src/apps/data/routes/transfers.ts +91 -13
- package/src/apps/data/routes/valuation.test.ts +422 -0
- package/src/apps/data/routes/valuation.ts +423 -0
- package/src/apps/data/routes/verified-tokens.ts +14 -2
- package/src/apps/data/routes/webhooks.test.ts +56 -8
- package/src/apps/data/routes/webhooks.ts +144 -27
- package/src/apps/index.ts +1 -1
- package/src/apps/management/App.ts +13 -0
- package/src/apps/management/routes/api-keys.test.ts +87 -0
- package/src/apps/management/routes/api-keys.ts +4 -3
- package/src/apps/management/routes/billing.test.ts +112 -1
- package/src/apps/management/routes/billing.ts +7 -2
- package/src/apps/management/routes/faucet.test.ts +79 -0
- package/src/apps/management/routes/faucet.ts +185 -0
- package/src/apps/management/routes/invitations.test.ts +172 -0
- package/src/apps/management/routes/invitations.ts +3 -0
- package/src/apps/management/routes/invite-links.test.ts +117 -0
- package/src/apps/management/routes/invite-links.ts +43 -1
- package/src/apps/management/routes/me.test.ts +140 -88
- package/src/apps/management/routes/me.ts +6 -0
- package/src/apps/management/routes/usage.test.ts +6 -0
- package/src/apps/management/routes/usage.ts +7 -0
- package/src/apps/management/routes/verified-token-requests.test.ts +377 -0
- package/src/apps/management/routes/verified-token-requests.ts +397 -0
- package/src/apps/management/routes/webhooks.test.ts +426 -0
- package/src/apps/management/routes/webhooks.ts +672 -0
- package/src/apps/mcp.test.ts +13 -0
- package/src/apps/mcp.ts +18 -5
- package/src/cli/Cli.test.ts +36 -0
- package/src/cli/Cli.ts +7 -2
- package/src/db/Db.test.ts +19 -0
- package/src/db/Db.ts +95 -0
- package/src/db/Schema.ts +53 -0
- package/src/db/tables/earlyAccess.ts +86 -0
- package/src/db/tables/invitations.ts +26 -1
- package/src/db/tables/inviteLinks.ts +22 -3
- package/src/db/tables/sponsoredTransactions.ts +4 -0
- package/src/db/tables/verifiedTokenRequests.test.ts +200 -0
- package/src/db/tables/verifiedTokenRequests.ts +182 -0
- package/src/db/tables/webhookSubscriptions.ts +31 -0
- package/src/internal/Auth.ts +73 -45
- package/src/internal/FundingQuotes.test.ts +143 -1
- package/src/internal/FundingQuotes.ts +95 -28
- package/src/internal/Log.test-d.ts +21 -0
- package/src/internal/Log.test.ts +50 -0
- package/src/internal/Log.ts +24 -1
- package/src/internal/MetricSink.test.ts +160 -0
- package/src/internal/MetricSink.ts +10 -0
- package/src/internal/OpenApi.ts +3 -0
- package/src/internal/Schema.ts +50 -5
- package/src/internal/Value.test.ts +20 -0
- package/src/internal/Value.ts +24 -1
- package/src/internal/VerifiedTokens.ts +9 -1
- package/src/internal/Webhooks.test.ts +140 -0
- package/src/internal/Webhooks.ts +53 -9
|
@@ -10,8 +10,34 @@ import * as Response from '../../../internal/Response.js'
|
|
|
10
10
|
import * as Schema from '../../../internal/Schema.js'
|
|
11
11
|
import type { Environment } from '../App.js'
|
|
12
12
|
|
|
13
|
+
// A bare domain (`example.org`), normalized before validation.
|
|
14
|
+
const domainPattern = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)+$/
|
|
15
|
+
|
|
13
16
|
/** OpenAPI schemas owned by reusable invite links. */
|
|
14
17
|
export namespace schema {
|
|
18
|
+
const allowedEmailDomains = z
|
|
19
|
+
.nullable(
|
|
20
|
+
z.readonly(
|
|
21
|
+
z
|
|
22
|
+
.array(
|
|
23
|
+
z
|
|
24
|
+
.string()
|
|
25
|
+
.check(
|
|
26
|
+
z.trim(),
|
|
27
|
+
z.toLowerCase(),
|
|
28
|
+
z.regex(domainPattern, 'Must be a bare domain (example.org).'),
|
|
29
|
+
),
|
|
30
|
+
)
|
|
31
|
+
.check(
|
|
32
|
+
z.minLength(1),
|
|
33
|
+
z.overwrite((domains) => [...new Set(domains)]),
|
|
34
|
+
),
|
|
35
|
+
),
|
|
36
|
+
)
|
|
37
|
+
.check(
|
|
38
|
+
z.describe('Email domains allowed to redeem, or null for unrestricted.'),
|
|
39
|
+
z.meta({ examples: [['example.org'], null] }),
|
|
40
|
+
)
|
|
15
41
|
const nullableExpiry = z
|
|
16
42
|
.nullable(z.iso.datetime({ offset: true }))
|
|
17
43
|
.check(
|
|
@@ -31,6 +57,7 @@ export namespace schema {
|
|
|
31
57
|
/** Management representation, including the recopyable bearer id. */
|
|
32
58
|
export const InviteLink = Schema.describe(
|
|
33
59
|
z.object({
|
|
60
|
+
allowedEmailDomains,
|
|
34
61
|
createdAt: z
|
|
35
62
|
.string()
|
|
36
63
|
.check(
|
|
@@ -146,6 +173,7 @@ export namespace schema {
|
|
|
146
173
|
/** Create body. */
|
|
147
174
|
export const CreateBody = z
|
|
148
175
|
.object({
|
|
176
|
+
allowedEmailDomains: z.optional(allowedEmailDomains),
|
|
149
177
|
expiresAt: z.optional(nullableExpiry),
|
|
150
178
|
maxUses: z.optional(nullableUses),
|
|
151
179
|
name: z.optional(
|
|
@@ -318,6 +346,7 @@ export function inviteLinks() {
|
|
|
318
346
|
const body = c.req.valid('json')
|
|
319
347
|
try {
|
|
320
348
|
const record = await InviteLinks.create(Db.get(c.get('db')), {
|
|
349
|
+
allowedEmailDomains: body.allowedEmailDomains ?? null,
|
|
321
350
|
createdBy: Auth.membership(c)?.userId ?? 'super_admin',
|
|
322
351
|
expiresAt:
|
|
323
352
|
body.expiresAt === undefined
|
|
@@ -498,7 +527,13 @@ export function inviteLinks() {
|
|
|
498
527
|
OpenApi.describeRoute({
|
|
499
528
|
operationId: 'acceptInviteLink',
|
|
500
529
|
responses: OpenApi.responses({
|
|
501
|
-
errors: {
|
|
530
|
+
errors: {
|
|
531
|
+
403: {
|
|
532
|
+
codes: ['forbidden', 'invite_link_email_forbidden'],
|
|
533
|
+
description: 'A verified session email from an allowed domain is required.',
|
|
534
|
+
},
|
|
535
|
+
...errors,
|
|
536
|
+
},
|
|
502
537
|
success: { description: 'Membership.', schema: schema.AcceptResponse },
|
|
503
538
|
}),
|
|
504
539
|
summary: 'Accept invite link',
|
|
@@ -531,6 +566,12 @@ export function inviteLinks() {
|
|
|
531
566
|
200,
|
|
532
567
|
)
|
|
533
568
|
} catch (cause) {
|
|
569
|
+
if (cause instanceof InviteLinks.EmailDomainForbiddenError)
|
|
570
|
+
return Response.error(c, {
|
|
571
|
+
code: 'invite_link_email_forbidden',
|
|
572
|
+
message: 'Sign in with an email address on an allowed domain',
|
|
573
|
+
status: 403,
|
|
574
|
+
})
|
|
534
575
|
return Response.upstream(c, cause)
|
|
535
576
|
}
|
|
536
577
|
},
|
|
@@ -539,6 +580,7 @@ export function inviteLinks() {
|
|
|
539
580
|
|
|
540
581
|
function serialize(record: InviteLinks.Record) {
|
|
541
582
|
return {
|
|
583
|
+
allowedEmailDomains: record.allowedEmailDomains,
|
|
542
584
|
createdAt: record.createdAt,
|
|
543
585
|
createdBy: record.createdBy,
|
|
544
586
|
enabled: record.enabled,
|
|
@@ -4,6 +4,8 @@ import { exportJWK, generateKeyPair } from 'jose'
|
|
|
4
4
|
import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'
|
|
5
5
|
|
|
6
6
|
import * as TestApp from '../../../../test/App.js'
|
|
7
|
+
import * as EarlyAccess from '../../../db/tables/earlyAccess.js'
|
|
8
|
+
import * as Users from '../../../db/tables/users.js'
|
|
7
9
|
import * as Me from './me.js'
|
|
8
10
|
|
|
9
11
|
/** Origin pinned for SIWE domain binding; Hono test requests use this host. */
|
|
@@ -18,6 +20,40 @@ function createApp() {
|
|
|
18
20
|
})
|
|
19
21
|
}
|
|
20
22
|
|
|
23
|
+
/** App with the early-access gate enabled; allowlist entries seed through `EarlyAccess.add`. */
|
|
24
|
+
function createGatedApp(db = TestApp.database()) {
|
|
25
|
+
return TestApp.create({
|
|
26
|
+
auth: { keys: [TestApp.key] },
|
|
27
|
+
db,
|
|
28
|
+
session: { wallet: { origin } },
|
|
29
|
+
tmp_earlyAccess: true,
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** RequestInit for a JSON mutation carrying the session cookie. */
|
|
34
|
+
function json(method: 'POST', cookie: string, body: unknown) {
|
|
35
|
+
return {
|
|
36
|
+
body: JSON.stringify(body),
|
|
37
|
+
headers: { 'content-type': 'application/json', cookie },
|
|
38
|
+
method,
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Signs in a fresh scripted account; returns its session cookie and user id. */
|
|
43
|
+
async function session(app: TestApp.signIn.App) {
|
|
44
|
+
const account = privateKeyToAccount(generatePrivateKey())
|
|
45
|
+
const { cookie } = await TestApp.signIn(app, account)
|
|
46
|
+
const me = await app.request('/v1/me', { headers: { cookie: cookie! } })
|
|
47
|
+
const { id } = (await me.json()) as { id: string }
|
|
48
|
+
return { cookie: cookie!, userId: id }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Reads the session's `tmp_earlyAccess` flag from `/v1/me`. TODO: remove after GA. */
|
|
52
|
+
async function tmp_earlyAccess(app: TestApp.signIn.App, cookie: string) {
|
|
53
|
+
const response = await app.request('/v1/me', { headers: { cookie } })
|
|
54
|
+
return (await TestApp.json(response, Me.schema.User)).tmp_earlyAccess
|
|
55
|
+
}
|
|
56
|
+
|
|
21
57
|
describe('GET /me', () => {
|
|
22
58
|
test('returns the signed-in user', async () => {
|
|
23
59
|
const app = createApp()
|
|
@@ -182,13 +218,14 @@ describe('sign-in email requirement', () => {
|
|
|
182
218
|
})
|
|
183
219
|
})
|
|
184
220
|
|
|
185
|
-
describe('sign-up
|
|
186
|
-
test('allows new users on
|
|
187
|
-
const oidc = await createIssuer({ email: 'dev@
|
|
221
|
+
describe('open sign-up', () => {
|
|
222
|
+
test('allows new users on any domain with the gate on', async () => {
|
|
223
|
+
const oidc = await createIssuer({ email: 'dev@example.com' })
|
|
188
224
|
try {
|
|
189
225
|
const app = TestApp.create({
|
|
190
226
|
auth: { keys: [TestApp.key] },
|
|
191
|
-
session: {
|
|
227
|
+
session: { wallet: { issuer: oidc.url, origin } },
|
|
228
|
+
tmp_earlyAccess: true,
|
|
192
229
|
})
|
|
193
230
|
const account = privateKeyToAccount(generatePrivateKey())
|
|
194
231
|
const { cookie, verifyResponse } = await TestApp.signIn(app, account, {
|
|
@@ -199,109 +236,124 @@ describe('sign-up email domains', () => {
|
|
|
199
236
|
const response = await app.request('/v1/me', { headers: { cookie: cookie! } })
|
|
200
237
|
expect(response.status).toBe(200)
|
|
201
238
|
const body = await TestApp.json(response, Me.schema.User)
|
|
202
|
-
expect(body.email).toBe('dev@
|
|
239
|
+
expect(body.email).toBe('dev@example.com')
|
|
203
240
|
} finally {
|
|
204
241
|
await oidc.close()
|
|
205
242
|
}
|
|
206
243
|
})
|
|
207
244
|
|
|
208
|
-
test('
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
auth: { keys: [TestApp.key] },
|
|
213
|
-
session: { emailAllowlist: ['tempo.xyz'], wallet: { issuer: oidc.url, origin } },
|
|
214
|
-
})
|
|
215
|
-
const account = privateKeyToAccount(generatePrivateKey())
|
|
216
|
-
const { cookie, verifyResponse } = await TestApp.signIn(app, account, {
|
|
217
|
-
idToken: ({ nonce }) => oidc.mint({ address: account.address, nonce }),
|
|
218
|
-
})
|
|
245
|
+
test('allows new users without an identity token with the gate on', async () => {
|
|
246
|
+
const app = createGatedApp()
|
|
247
|
+
const account = privateKeyToAccount(generatePrivateKey())
|
|
248
|
+
const { cookie, verifyResponse } = await TestApp.signIn(app, account)
|
|
219
249
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
expect(await verifyResponse.json()).toMatchInlineSnapshot(`
|
|
223
|
-
{
|
|
224
|
-
"error": "sign-ups are restricted to approved email domains or invited emails",
|
|
225
|
-
}
|
|
226
|
-
`)
|
|
227
|
-
} finally {
|
|
228
|
-
await oidc.close()
|
|
229
|
-
}
|
|
250
|
+
expect(verifyResponse.status).toBe(200)
|
|
251
|
+
expect(cookie).toBeDefined()
|
|
230
252
|
})
|
|
253
|
+
})
|
|
231
254
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
const { cookie, verifyResponse } = await TestApp.signIn(app, account)
|
|
255
|
+
describe('tmp_earlyAccess', () => {
|
|
256
|
+
test('defaults to true while the gate is dormant', async () => {
|
|
257
|
+
const app = createApp()
|
|
258
|
+
const { cookie } = await session(app)
|
|
259
|
+
expect(await tmp_earlyAccess(app, cookie)).toBe(true)
|
|
260
|
+
})
|
|
239
261
|
|
|
240
|
-
|
|
241
|
-
|
|
262
|
+
test('matches domain entries case-insensitively', async () => {
|
|
263
|
+
const db = TestApp.database()
|
|
264
|
+
const app = createGatedApp(db)
|
|
265
|
+
await EarlyAccess.add(db, { createdBy: 'test', entry: 'Example.ORG' })
|
|
266
|
+
const user = await session(app)
|
|
267
|
+
await Users.setEmail(db, user.userId, 'Dev@EXAMPLE.org')
|
|
268
|
+
|
|
269
|
+
expect(await tmp_earlyAccess(app, user.cookie)).toBe(true)
|
|
242
270
|
})
|
|
243
271
|
|
|
244
|
-
test('
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
|
|
272
|
+
test('exact-email entries grant only that address', async () => {
|
|
273
|
+
const db = TestApp.database()
|
|
274
|
+
const app = createGatedApp(db)
|
|
275
|
+
await EarlyAccess.add(db, { createdBy: 'test', entry: 'dev@example.org' })
|
|
276
|
+
const granted = await session(app)
|
|
277
|
+
await Users.setEmail(db, granted.userId, 'dev@example.org')
|
|
278
|
+
const denied = await session(app)
|
|
279
|
+
await Users.setEmail(db, denied.userId, 'other@example.org')
|
|
280
|
+
|
|
281
|
+
expect(await tmp_earlyAccess(app, granted.cookie)).toBe(true)
|
|
282
|
+
expect(await tmp_earlyAccess(app, denied.cookie)).toBe(false)
|
|
283
|
+
})
|
|
248
284
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
})
|
|
255
|
-
const owner = privateKeyToAccount(generatePrivateKey())
|
|
256
|
-
const { cookie } = await TestApp.signIn(open, owner)
|
|
257
|
-
const created = await open.request('/v1/orgs', {
|
|
258
|
-
body: JSON.stringify({ name: 'Acme' }),
|
|
259
|
-
headers: { 'content-type': 'application/json', cookie: cookie! },
|
|
260
|
-
method: 'POST',
|
|
261
|
-
})
|
|
262
|
-
const org = (await created.json()) as { id: string }
|
|
263
|
-
const invited = await open.request(`/v1/orgs/${org.id}/invitations`, {
|
|
264
|
-
body: JSON.stringify({ email: 'invited@example.com', role: 'member' }),
|
|
265
|
-
headers: { 'content-type': 'application/json', cookie: cookie! },
|
|
266
|
-
method: 'POST',
|
|
267
|
-
})
|
|
268
|
-
expect(invited.status).toBe(200)
|
|
285
|
+
test('stays false without a matching entry', async () => {
|
|
286
|
+
const db = TestApp.database()
|
|
287
|
+
const app = createGatedApp(db)
|
|
288
|
+
const user = await session(app)
|
|
289
|
+
await Users.setEmail(db, user.userId, 'dev@example.net')
|
|
269
290
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
})
|
|
279
|
-
expect(verifyResponse.status).toBe(200)
|
|
280
|
-
} finally {
|
|
281
|
-
await oidc.close()
|
|
282
|
-
}
|
|
291
|
+
expect(await tmp_earlyAccess(app, user.cookie)).toBe(false)
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
test('stays false without a verified email', async () => {
|
|
295
|
+
const app = createGatedApp()
|
|
296
|
+
const { cookie } = await session(app)
|
|
297
|
+
|
|
298
|
+
expect(await tmp_earlyAccess(app, cookie)).toBe(false)
|
|
283
299
|
})
|
|
284
300
|
|
|
285
|
-
test('
|
|
301
|
+
test('flips false once the entry is removed', async () => {
|
|
286
302
|
const db = TestApp.database()
|
|
287
|
-
const
|
|
303
|
+
const app = createGatedApp(db)
|
|
304
|
+
await EarlyAccess.add(db, { createdBy: 'test', entry: 'example.org' })
|
|
305
|
+
const user = await session(app)
|
|
306
|
+
await Users.setEmail(db, user.userId, 'dev@example.org')
|
|
307
|
+
expect(await tmp_earlyAccess(app, user.cookie)).toBe(true)
|
|
308
|
+
|
|
309
|
+
await EarlyAccess.remove(db, 'example.org')
|
|
310
|
+
expect(await tmp_earlyAccess(app, user.cookie)).toBe(false)
|
|
311
|
+
})
|
|
288
312
|
|
|
289
|
-
|
|
290
|
-
const
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
const
|
|
296
|
-
|
|
313
|
+
test('invitations from early-access inviters grant access until revoked', async () => {
|
|
314
|
+
const db = TestApp.database()
|
|
315
|
+
const app = createGatedApp(db)
|
|
316
|
+
await EarlyAccess.add(db, { createdBy: 'test', entry: 'example.org' })
|
|
317
|
+
const inviter = await session(app)
|
|
318
|
+
await Users.setEmail(db, inviter.userId, 'owner@example.org')
|
|
319
|
+
const created = await app.request('/v1/orgs', json('POST', inviter.cookie, { name: 'Acme' }))
|
|
320
|
+
const org = (await created.json()) as { id: string }
|
|
321
|
+
const invited = await app.request(
|
|
322
|
+
`/v1/orgs/${org.id}/invitations`,
|
|
323
|
+
json('POST', inviter.cookie, { email: 'invitee@example.net', role: 'member' }),
|
|
324
|
+
)
|
|
325
|
+
expect(invited.status).toBe(200)
|
|
326
|
+
const invitation = (await invited.json()) as { id: string }
|
|
297
327
|
|
|
298
|
-
const
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
328
|
+
const invitee = await session(app)
|
|
329
|
+
await Users.setEmail(db, invitee.userId, 'invitee@example.net')
|
|
330
|
+
expect(await tmp_earlyAccess(app, invitee.cookie)).toBe(true)
|
|
331
|
+
|
|
332
|
+
// Revoking the invitation withdraws the grant.
|
|
333
|
+
const revoked = await app.request(`/v1/orgs/${org.id}/invitations/${invitation.id}`, {
|
|
334
|
+
headers: { cookie: inviter.cookie },
|
|
335
|
+
method: 'DELETE',
|
|
302
336
|
})
|
|
303
|
-
|
|
304
|
-
expect(
|
|
337
|
+
expect(revoked.status).toBe(200)
|
|
338
|
+
expect(await tmp_earlyAccess(app, invitee.cookie)).toBe(false)
|
|
339
|
+
})
|
|
340
|
+
|
|
341
|
+
test('invitations from non-early-access inviters do not grant access', async () => {
|
|
342
|
+
const db = TestApp.database()
|
|
343
|
+
const app = createGatedApp(db)
|
|
344
|
+
const inviter = await session(app)
|
|
345
|
+
await Users.setEmail(db, inviter.userId, 'owner@example.net')
|
|
346
|
+
const created = await app.request('/v1/orgs', json('POST', inviter.cookie, { name: 'Acme' }))
|
|
347
|
+
const org = (await created.json()) as { id: string }
|
|
348
|
+
const invited = await app.request(
|
|
349
|
+
`/v1/orgs/${org.id}/invitations`,
|
|
350
|
+
json('POST', inviter.cookie, { email: 'friend@example.net', role: 'member' }),
|
|
351
|
+
)
|
|
352
|
+
expect(invited.status).toBe(200)
|
|
353
|
+
|
|
354
|
+
const invitee = await session(app)
|
|
355
|
+
await Users.setEmail(db, invitee.userId, 'friend@example.net')
|
|
356
|
+
expect(await tmp_earlyAccess(app, invitee.cookie)).toBe(false)
|
|
305
357
|
})
|
|
306
358
|
})
|
|
307
359
|
|
|
@@ -35,6 +35,11 @@ export namespace schema {
|
|
|
35
35
|
z.describe('Opaque user id (`usr_…`).'),
|
|
36
36
|
z.meta({ examples: ['usr_1a2b3c4d5e6f7g8h9j0k1m2n'] }),
|
|
37
37
|
),
|
|
38
|
+
// TODO: remove after GA.
|
|
39
|
+
tmp_earlyAccess: z.boolean().check(
|
|
40
|
+
z.describe('Whether the user has early access; gated features require it while the platform is invite-only.'), // prettier-ignore
|
|
41
|
+
z.meta({ examples: [true] }),
|
|
42
|
+
),
|
|
38
43
|
updatedAt: z
|
|
39
44
|
.string()
|
|
40
45
|
.check(
|
|
@@ -94,6 +99,7 @@ export function me() {
|
|
|
94
99
|
createdAt: user.createdAt,
|
|
95
100
|
...(user.email ? { email: user.email } : {}),
|
|
96
101
|
id: user.id,
|
|
102
|
+
tmp_earlyAccess: await Auth.tmp_earlyAccess(c),
|
|
97
103
|
updatedAt: user.updatedAt,
|
|
98
104
|
}),
|
|
99
105
|
200,
|
|
@@ -247,6 +247,7 @@ describe('GET /orgs/:orgId/usage/sponsorships', () => {
|
|
|
247
247
|
"data": [
|
|
248
248
|
{
|
|
249
249
|
"count": 2,
|
|
250
|
+
"failed": 0,
|
|
250
251
|
"feeTotal": {
|
|
251
252
|
"amount": "0.35",
|
|
252
253
|
"currency": "usd",
|
|
@@ -255,6 +256,7 @@ describe('GET /orgs/:orgId/usage/sponsorships', () => {
|
|
|
255
256
|
},
|
|
256
257
|
{
|
|
257
258
|
"count": 1,
|
|
259
|
+
"failed": 1,
|
|
258
260
|
"feeTotal": {
|
|
259
261
|
"amount": "0",
|
|
260
262
|
"currency": "usd",
|
|
@@ -275,6 +277,7 @@ describe('GET /orgs/:orgId/usage/sponsorships', () => {
|
|
|
275
277
|
"data": [
|
|
276
278
|
{
|
|
277
279
|
"count": 3,
|
|
280
|
+
"failed": 1,
|
|
278
281
|
"feeTotal": {
|
|
279
282
|
"amount": "0.35",
|
|
280
283
|
"currency": "usd",
|
|
@@ -302,6 +305,7 @@ describe('GET /orgs/:orgId/usage/sponsorships', () => {
|
|
|
302
305
|
[
|
|
303
306
|
{
|
|
304
307
|
"count": 1,
|
|
308
|
+
"failed": 0,
|
|
305
309
|
"feeTotal": {
|
|
306
310
|
"amount": "0.5",
|
|
307
311
|
"currency": "usd",
|
|
@@ -333,6 +337,7 @@ describe('GET /orgs/:orgId/usage/sponsorships', () => {
|
|
|
333
337
|
"data": [
|
|
334
338
|
{
|
|
335
339
|
"count": 1,
|
|
340
|
+
"failed": 0,
|
|
336
341
|
"feeTotal": {
|
|
337
342
|
"amount": "0.1",
|
|
338
343
|
"currency": "usd",
|
|
@@ -352,6 +357,7 @@ describe('GET /orgs/:orgId/usage/sponsorships', () => {
|
|
|
352
357
|
"data": [
|
|
353
358
|
{
|
|
354
359
|
"count": 1,
|
|
360
|
+
"failed": 0,
|
|
355
361
|
"feeTotal": {
|
|
356
362
|
"amount": "0.3",
|
|
357
363
|
"currency": "usd",
|
|
@@ -199,6 +199,12 @@ export namespace schema {
|
|
|
199
199
|
z.describe('Sponsored transactions recorded in the bucket, any status.'),
|
|
200
200
|
z.meta({ examples: [42] }),
|
|
201
201
|
),
|
|
202
|
+
failed: z
|
|
203
|
+
.number()
|
|
204
|
+
.check(
|
|
205
|
+
z.describe('Sponsored transactions in the bucket whose status is `failed`.'),
|
|
206
|
+
z.meta({ examples: [3] }),
|
|
207
|
+
),
|
|
202
208
|
feeTotal: z
|
|
203
209
|
.object({
|
|
204
210
|
amount: z.string().check(
|
|
@@ -462,6 +468,7 @@ export function usage() {
|
|
|
462
468
|
Response.validated(schema.getSponsorshipUsage.Response, {
|
|
463
469
|
data: buckets.map((bucket) => ({
|
|
464
470
|
count: bucket.count,
|
|
471
|
+
failed: bucket.failed,
|
|
465
472
|
feeTotal: { amount: core_Billing.fromBaseUnits(bucket.feeTotal), currency },
|
|
466
473
|
timestamp: bucket.timestamp,
|
|
467
474
|
})),
|