tapimo 0.6.0 → 0.7.1
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 +8641 -8263
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js +9 -1
- package/dist/App.js.map +1 -1
- package/dist/Scope.d.ts +4 -0
- package/dist/Scope.d.ts.map +1 -1
- package/dist/Scope.js +5 -0
- package/dist/Scope.js.map +1 -1
- package/dist/admin/App.d.ts +112 -11
- package/dist/admin/App.d.ts.map +1 -1
- package/dist/admin/apps/api-keys.d.ts +2 -1
- package/dist/admin/apps/api-keys.d.ts.map +1 -1
- package/dist/admin/apps/early-access.d.ts +2 -2
- package/dist/admin/apps/early-access.d.ts.map +1 -1
- package/dist/admin/apps/early-access.js +5 -5
- package/dist/admin/apps/early-access.js.map +1 -1
- package/dist/admin/apps/organizations.d.ts +124 -1
- package/dist/admin/apps/organizations.d.ts.map +1 -1
- package/dist/admin/apps/organizations.js +97 -6
- package/dist/admin/apps/organizations.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 +47 -39
- package/dist/admin/ui.generated.js.map +1 -1
- package/dist/analytics/Analytics.d.ts.map +1 -1
- package/dist/analytics/Analytics.js +20 -0
- package/dist/analytics/Analytics.js.map +1 -1
- package/dist/analytics/tables/requestEvents.d.ts +18 -0
- package/dist/analytics/tables/requestEvents.d.ts.map +1 -1
- package/dist/analytics/tables/requestEvents.js +36 -7
- package/dist/analytics/tables/requestEvents.js.map +1 -1
- package/dist/apps/data/App.d.ts +8050 -8019
- package/dist/apps/data/App.d.ts.map +1 -1
- package/dist/apps/data/FundingQuoteProviders.js +2 -15
- package/dist/apps/data/FundingQuoteProviders.js.map +1 -1
- package/dist/apps/data/FxOracle.d.ts +1 -1
- package/dist/apps/data/FxOracle.d.ts.map +1 -1
- package/dist/apps/data/FxOracle.js +1 -1
- package/dist/apps/data/FxOracle.js.map +1 -1
- package/dist/apps/data/routes/activities.d.ts +27 -27
- package/dist/apps/data/routes/activities.d.ts.map +1 -1
- package/dist/apps/data/routes/activities.js +21 -1
- package/dist/apps/data/routes/activities.js.map +1 -1
- package/dist/apps/data/routes/balances.d.ts +58 -58
- package/dist/apps/data/routes/blocks.d.ts +39 -39
- package/dist/apps/data/routes/coingecko.d.ts +176 -176
- package/dist/apps/data/routes/exchanges.d.ts +669 -669
- package/dist/apps/data/routes/fee-amm.d.ts +157 -143
- package/dist/apps/data/routes/fee-amm.d.ts.map +1 -1
- package/dist/apps/data/routes/fee-amm.js +76 -91
- package/dist/apps/data/routes/fee-amm.js.map +1 -1
- package/dist/apps/data/routes/funding.d.ts +72 -34
- package/dist/apps/data/routes/funding.d.ts.map +1 -1
- package/dist/apps/data/routes/funding.js +34 -26
- package/dist/apps/data/routes/funding.js.map +1 -1
- package/dist/apps/data/routes/indexer.d.ts +30 -30
- package/dist/apps/data/routes/receipts.d.ts +8 -8
- package/dist/apps/data/routes/rpc.d.ts +31 -31
- package/dist/apps/data/routes/tokenlist.d.ts.map +1 -1
- package/dist/apps/data/routes/tokenlist.js +12 -3
- package/dist/apps/data/routes/tokenlist.js.map +1 -1
- package/dist/apps/data/routes/tokens.d.ts +4 -4
- package/dist/apps/data/routes/tokens.d.ts.map +1 -1
- package/dist/apps/data/routes/tokens.js +5 -1
- package/dist/apps/data/routes/tokens.js.map +1 -1
- package/dist/apps/data/routes/transactions.d.ts +6 -6
- package/dist/apps/data/routes/transfers.d.ts +101 -90
- package/dist/apps/data/routes/transfers.d.ts.map +1 -1
- package/dist/apps/data/routes/transfers.js +226 -141
- package/dist/apps/data/routes/transfers.js.map +1 -1
- package/dist/apps/data/routes/valuation.d.ts +2 -2
- package/dist/apps/data/routes/valuation.js +3 -3
- package/dist/apps/data/routes/valuation.js.map +1 -1
- package/dist/apps/data/routes/verified-tokens.d.ts +36 -36
- package/dist/apps/data/routes/verified-tokens.d.ts.map +1 -1
- package/dist/apps/data/routes/verified-tokens.js +34 -9
- package/dist/apps/data/routes/verified-tokens.js.map +1 -1
- package/dist/apps/data/routes/webhooks.d.ts +2 -2
- package/dist/apps/data/routes/webhooks.js +14 -6
- package/dist/apps/data/routes/webhooks.js.map +1 -1
- package/dist/apps/index.d.ts +1 -0
- package/dist/apps/index.d.ts.map +1 -1
- package/dist/apps/index.js +1 -0
- package/dist/apps/index.js.map +1 -1
- package/dist/apps/management/App.d.ts +460 -523
- package/dist/apps/management/App.d.ts.map +1 -1
- package/dist/apps/management/routes/api-keys.d.ts +7 -23
- package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
- package/dist/apps/management/routes/api-keys.js +20 -8
- package/dist/apps/management/routes/api-keys.js.map +1 -1
- package/dist/apps/management/routes/billing.d.ts +106 -151
- package/dist/apps/management/routes/billing.d.ts.map +1 -1
- package/dist/apps/management/routes/billing.js +28 -28
- package/dist/apps/management/routes/billing.js.map +1 -1
- package/dist/apps/management/routes/faucet.d.ts +48 -34
- package/dist/apps/management/routes/faucet.d.ts.map +1 -1
- package/dist/apps/management/routes/faucet.js +33 -18
- package/dist/apps/management/routes/faucet.js.map +1 -1
- package/dist/apps/management/routes/invitations.d.ts +131 -131
- package/dist/apps/management/routes/invitations.d.ts.map +1 -1
- package/dist/apps/management/routes/invitations.js +0 -3
- package/dist/apps/management/routes/invitations.js.map +1 -1
- package/dist/apps/management/routes/me.d.ts +6 -8
- package/dist/apps/management/routes/me.d.ts.map +1 -1
- package/dist/apps/management/routes/me.js +0 -4
- package/dist/apps/management/routes/me.js.map +1 -1
- package/dist/apps/management/routes/members.d.ts +73 -73
- package/dist/apps/management/routes/orgs.d.ts +94 -94
- package/dist/apps/management/routes/orgs.d.ts.map +1 -1
- package/dist/apps/management/routes/orgs.js +5 -0
- package/dist/apps/management/routes/orgs.js.map +1 -1
- package/dist/apps/management/routes/usage.d.ts +72 -72
- package/dist/apps/management/routes/verified-token-requests.d.ts +94 -94
- package/dist/apps/management/routes/webhooks.d.ts +1 -1
- package/dist/apps/mpp/App.d.ts +363 -0
- package/dist/apps/mpp/App.d.ts.map +1 -0
- package/dist/apps/mpp/App.js +72 -0
- package/dist/apps/mpp/App.js.map +1 -0
- package/dist/apps/mpp/Mpp.d.ts +178 -0
- package/dist/apps/mpp/Mpp.d.ts.map +1 -0
- package/dist/apps/mpp/Mpp.js +183 -0
- package/dist/apps/mpp/Mpp.js.map +1 -0
- package/dist/apps/mpp/index.d.ts +3 -0
- package/dist/apps/mpp/index.d.ts.map +1 -0
- package/dist/apps/mpp/index.js +3 -0
- package/dist/apps/mpp/index.js.map +1 -0
- package/dist/apps/relay/App.d.ts.map +1 -1
- package/dist/apps/relay/App.js +57 -10
- package/dist/apps/relay/App.js.map +1 -1
- package/dist/apps/relay/Sponsorships.js +1 -1
- package/dist/apps/relay/Sponsorships.js.map +1 -1
- package/dist/db/Db.d.ts +2 -0
- package/dist/db/Db.d.ts.map +1 -1
- package/dist/db/Db.js +27 -0
- package/dist/db/Db.js.map +1 -1
- package/dist/db/Schema.d.ts +12 -0
- package/dist/db/Schema.d.ts.map +1 -1
- package/dist/db/Schema.js +11 -0
- package/dist/db/Schema.js.map +1 -1
- package/dist/db/tables/enabledBillingSources.d.ts +73 -0
- package/dist/db/tables/enabledBillingSources.d.ts.map +1 -0
- package/dist/db/tables/enabledBillingSources.js +69 -0
- package/dist/db/tables/enabledBillingSources.js.map +1 -0
- package/dist/db/tables/invitations.d.ts +1 -12
- package/dist/db/tables/invitations.d.ts.map +1 -1
- package/dist/db/tables/invitations.js +3 -23
- package/dist/db/tables/invitations.js.map +1 -1
- package/dist/db/tables/organizations.d.ts +5 -2
- package/dist/db/tables/organizations.d.ts.map +1 -1
- package/dist/db/tables/organizations.js +32 -2
- package/dist/db/tables/organizations.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 +13 -3
- package/dist/db/tables/sponsoredTransactions.js.map +1 -1
- package/dist/handlers/internal/utils.d.ts +1 -16
- package/dist/handlers/internal/utils.d.ts.map +1 -1
- package/dist/handlers/internal/utils.js +17 -6
- package/dist/handlers/internal/utils.js.map +1 -1
- package/dist/handlers/relay.d.ts.map +1 -1
- package/dist/handlers/relay.js +37 -5
- package/dist/handlers/relay.js.map +1 -1
- package/dist/internal/Auth.d.ts +0 -32
- package/dist/internal/Auth.d.ts.map +1 -1
- package/dist/internal/Auth.js +3 -51
- package/dist/internal/Auth.js.map +1 -1
- package/dist/internal/EdgeCache.d.ts +1 -1
- package/dist/internal/EdgeCache.d.ts.map +1 -1
- package/dist/internal/EdgeCache.js +3 -2
- package/dist/internal/EdgeCache.js.map +1 -1
- package/dist/internal/FundingQuotes.d.ts +78 -32
- package/dist/internal/FundingQuotes.d.ts.map +1 -1
- package/dist/internal/FundingQuotes.js +45 -8
- package/dist/internal/FundingQuotes.js.map +1 -1
- package/dist/internal/Log.d.ts +18 -4
- package/dist/internal/Log.d.ts.map +1 -1
- package/dist/internal/Log.js +50 -3
- package/dist/internal/Log.js.map +1 -1
- package/dist/internal/MetricSink.d.ts.map +1 -1
- package/dist/internal/MetricSink.js +15 -0
- package/dist/internal/MetricSink.js.map +1 -1
- package/dist/internal/Response.d.ts +2 -2
- package/dist/internal/Response.d.ts.map +1 -1
- package/dist/internal/Response.js +12 -4
- package/dist/internal/Response.js.map +1 -1
- package/dist/internal/Schema.d.ts +2 -2
- package/dist/internal/Schema.d.ts.map +1 -1
- package/dist/internal/Schema.js +4 -2
- package/dist/internal/Schema.js.map +1 -1
- package/dist/internal/Viem.d.ts +1 -1
- package/package.json +7 -2
- package/src/App.test.ts +9 -4
- package/src/App.ts +14 -2
- package/src/Client.test-d.ts +109 -28
- package/src/Scope.ts +5 -0
- package/src/admin/App.test.ts +4 -0
- package/src/admin/apps/early-access.ts +6 -5
- package/src/admin/apps/organizations.test.ts +86 -2
- package/src/admin/apps/organizations.ts +155 -31
- package/src/admin/apps/scopes.test.ts +5 -0
- package/src/admin/ui/src/lib/organizations.test.ts +81 -1
- package/src/admin/ui/src/lib/organizations.ts +92 -0
- package/src/admin/ui/src/routes/api-keys.tsx +1 -1
- package/src/admin/ui/src/routes/early-access.tsx +8 -3
- package/src/admin/ui/src/routes/organizations_.$orgId.tsx +103 -0
- package/src/admin/ui.generated.ts +49 -39
- package/src/analytics/Analytics.test.ts +83 -0
- package/src/analytics/Analytics.ts +20 -0
- package/src/analytics/tables/requestEvents.ts +60 -7
- package/src/apps/composition.test.ts +11 -2
- package/src/apps/data/App.test.ts +14 -2
- package/src/apps/data/FundingQuoteProviders.test.ts +0 -5
- package/src/apps/data/FundingQuoteProviders.ts +2 -29
- package/src/apps/data/FxOracle.test.ts +2 -2
- package/src/apps/data/FxOracle.ts +2 -2
- package/src/apps/data/routes/activities.test.ts +6 -2
- package/src/apps/data/routes/activities.ts +39 -1
- package/src/apps/data/routes/balances.test.ts +6 -2
- package/src/apps/data/routes/exchanges.test.ts +7 -3
- package/src/apps/data/routes/fee-amm.test.ts +47 -23
- package/src/apps/data/routes/fee-amm.ts +98 -119
- package/src/apps/data/routes/funding.test.ts +29 -8
- package/src/apps/data/routes/funding.ts +42 -48
- package/src/apps/data/routes/receipts.test.ts +6 -2
- package/src/apps/data/routes/tokenlist.test.ts +15 -0
- package/src/apps/data/routes/tokenlist.ts +18 -5
- package/src/apps/data/routes/tokens.ts +5 -1
- package/src/apps/data/routes/transfers.test.ts +203 -35
- package/src/apps/data/routes/transfers.ts +294 -172
- package/src/apps/data/routes/valuation.test.ts +18 -3
- package/src/apps/data/routes/valuation.ts +4 -4
- package/src/apps/data/routes/verified-tokens.test.ts +51 -0
- package/src/apps/data/routes/verified-tokens.ts +52 -15
- package/src/apps/data/routes/webhooks.test.ts +56 -0
- package/src/apps/data/routes/webhooks.ts +17 -8
- package/src/apps/index.ts +1 -0
- package/src/apps/management/routes/api-keys.test.ts +2 -75
- package/src/apps/management/routes/api-keys.ts +30 -7
- package/src/apps/management/routes/billing.test.ts +106 -85
- package/src/apps/management/routes/billing.ts +32 -21
- package/src/apps/management/routes/faucet.test.ts +11 -3
- package/src/apps/management/routes/faucet.ts +46 -30
- package/src/apps/management/routes/invitations.test.ts +0 -172
- package/src/apps/management/routes/invitations.ts +0 -3
- package/src/apps/management/routes/me.test.ts +3 -145
- package/src/apps/management/routes/me.ts +0 -6
- package/src/apps/management/routes/orgs.test.ts +45 -2
- package/src/apps/management/routes/orgs.ts +6 -0
- package/src/apps/management/routes/scopes.test.ts +1 -0
- package/src/apps/mpp/App.ts +86 -0
- package/src/apps/mpp/Mpp.test.ts +193 -0
- package/src/apps/mpp/Mpp.ts +353 -0
- package/src/apps/mpp/index.ts +2 -0
- package/src/apps/mpp/internal/Adapter.ts +11 -0
- package/src/apps/mpp/internal/Error.ts +9 -0
- package/src/apps/mpp/internal/Idempotency.ts +7 -0
- package/src/apps/mpp/internal/Screening.ts +10 -0
- package/src/apps/relay/App.test.ts +90 -8
- package/src/apps/relay/App.ts +62 -10
- package/src/apps/relay/Sponsorships.test.ts +2 -0
- package/src/apps/relay/Sponsorships.ts +1 -1
- package/src/db/Db.test.ts +2 -2
- package/src/db/Db.ts +30 -0
- package/src/db/Schema.ts +15 -0
- package/src/db/tables/enabledBillingSources.test.ts +303 -0
- package/src/db/tables/enabledBillingSources.ts +118 -0
- package/src/db/tables/invitations.ts +4 -26
- package/src/db/tables/organizations.test.ts +191 -0
- package/src/db/tables/organizations.ts +44 -3
- package/src/db/tables/sponsoredTransactions.test.ts +41 -2
- package/src/db/tables/sponsoredTransactions.ts +15 -3
- package/src/handlers/internal/utils.ts +22 -6
- package/src/handlers/relay.errors.test.ts +153 -0
- package/src/handlers/relay.ts +42 -7
- package/src/internal/Auth.test.ts +3 -3
- package/src/internal/Auth.ts +3 -63
- package/src/internal/EdgeCache.test.ts +18 -5
- package/src/internal/EdgeCache.ts +4 -2
- package/src/internal/FundingQuotes.test.ts +18 -5
- package/src/internal/FundingQuotes.ts +46 -11
- package/src/internal/Log.test.ts +91 -0
- package/src/internal/Log.ts +69 -5
- package/src/internal/MetricSink.test.ts +55 -0
- package/src/internal/MetricSink.ts +14 -0
- package/src/internal/Response.test.ts +28 -0
- package/src/internal/Response.ts +12 -4
- package/src/internal/Schema.test.ts +13 -0
- package/src/internal/Schema.ts +14 -7
|
@@ -2,7 +2,6 @@ import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'
|
|
|
2
2
|
|
|
3
3
|
import * as App from '../App.js'
|
|
4
4
|
import * as TestApp from '../../../../test/App.js'
|
|
5
|
-
import * as EarlyAccess from '../../../db/tables/earlyAccess.js'
|
|
6
5
|
import * as Invitations from '../../../db/tables/invitations.js'
|
|
7
6
|
import * as InvitationsApp from './invitations.js'
|
|
8
7
|
import * as Memberships from '../../../db/tables/memberships.js'
|
|
@@ -51,7 +50,6 @@ function createApp(
|
|
|
51
50
|
db?: ReturnType<typeof TestApp.database>
|
|
52
51
|
email?: App.Email
|
|
53
52
|
kv?: { store: ReturnType<typeof TestApp.kvStore> }
|
|
54
|
-
tmp_earlyAccess?: boolean
|
|
55
53
|
} = {},
|
|
56
54
|
) {
|
|
57
55
|
return TestApp.create({
|
|
@@ -60,7 +58,6 @@ function createApp(
|
|
|
60
58
|
...(options.email ? { email: options.email } : {}),
|
|
61
59
|
...(options.kv ? { kv: options.kv } : {}),
|
|
62
60
|
session: { wallet: { origin } },
|
|
63
|
-
...(options.tmp_earlyAccess === undefined ? {} : { tmp_earlyAccess: options.tmp_earlyAccess }),
|
|
64
61
|
})
|
|
65
62
|
}
|
|
66
63
|
|
|
@@ -349,7 +346,6 @@ describe('invitations', () => {
|
|
|
349
346
|
const expired = await Invitations.create(db, {
|
|
350
347
|
email: 'dev@example.com',
|
|
351
348
|
expiresAt: '2020-01-01T00:00:00.000Z',
|
|
352
|
-
grantsEarlyAccess: false,
|
|
353
349
|
invitedBy: owner.userId,
|
|
354
350
|
orgId: org.id,
|
|
355
351
|
role: 'member',
|
|
@@ -512,171 +508,3 @@ describe('invitations', () => {
|
|
|
512
508
|
)
|
|
513
509
|
})
|
|
514
510
|
})
|
|
515
|
-
|
|
516
|
-
describe('early access stamping', () => {
|
|
517
|
-
test('does not persist a dormant gate bypass', async () => {
|
|
518
|
-
const db = TestApp.database()
|
|
519
|
-
const app = createApp({ db })
|
|
520
|
-
const owner = await session(app)
|
|
521
|
-
await Users.setEmail(db, owner.userId, 'owner@example.net')
|
|
522
|
-
const created = await app.request('/v1/orgs', json('POST', owner.cookie, { name: 'Acme' }))
|
|
523
|
-
const org = await TestApp.json(created, Orgs.schema.Organization)
|
|
524
|
-
|
|
525
|
-
const invited = await app.request(
|
|
526
|
-
`/v1/orgs/${org.id}/invitations`,
|
|
527
|
-
json('POST', owner.cookie, { email: 'owner@example.net', role: 'member' }),
|
|
528
|
-
)
|
|
529
|
-
const invitation = await TestApp.json(invited, InvitationsApp.schema.Invitation)
|
|
530
|
-
|
|
531
|
-
expect((await Invitations.get(db, invitation.id))?.grantsEarlyAccess).toBe(false)
|
|
532
|
-
})
|
|
533
|
-
|
|
534
|
-
test('stamps grants_early_access from the session inviter', async () => {
|
|
535
|
-
const db = TestApp.database()
|
|
536
|
-
const app = createApp({ db, tmp_earlyAccess: true })
|
|
537
|
-
await EarlyAccess.add(db, { createdBy: 'test', entry: 'example.org' })
|
|
538
|
-
|
|
539
|
-
// A non-early-access inviter stamps no grant.
|
|
540
|
-
const plain = await session(app)
|
|
541
|
-
const createdPlain = await app.request('/v1/orgs', json('POST', plain.cookie, { name: 'Plain' })) // prettier-ignore
|
|
542
|
-
const orgPlain = await TestApp.json(createdPlain, Orgs.schema.Organization)
|
|
543
|
-
const invitedPlain = await app.request(
|
|
544
|
-
`/v1/orgs/${orgPlain.id}/invitations`,
|
|
545
|
-
json('POST', plain.cookie, { email: 'a@example.net', role: 'member' }),
|
|
546
|
-
)
|
|
547
|
-
const invitationPlain = await TestApp.json(invitedPlain, InvitationsApp.schema.Invitation)
|
|
548
|
-
expect((await Invitations.get(db, invitationPlain.id))?.grantsEarlyAccess).toBe(false)
|
|
549
|
-
|
|
550
|
-
// An early-access inviter stamps the grant.
|
|
551
|
-
const granted = await session(app)
|
|
552
|
-
await Users.setEmail(db, granted.userId, 'owner@example.org')
|
|
553
|
-
const createdGranted = await app.request('/v1/orgs', json('POST', granted.cookie, { name: 'Granted' })) // prettier-ignore
|
|
554
|
-
const orgGranted = await TestApp.json(createdGranted, Orgs.schema.Organization)
|
|
555
|
-
const invitedGranted = await app.request(
|
|
556
|
-
`/v1/orgs/${orgGranted.id}/invitations`,
|
|
557
|
-
json('POST', granted.cookie, { email: 'b@example.net', role: 'member' }),
|
|
558
|
-
)
|
|
559
|
-
const invitationGranted = await TestApp.json(invitedGranted, InvitationsApp.schema.Invitation)
|
|
560
|
-
expect((await Invitations.get(db, invitationGranted.id))?.grantsEarlyAccess).toBe(true)
|
|
561
|
-
})
|
|
562
|
-
|
|
563
|
-
test('stamps grants_early_access true for API-key and super-admin inviters', async () => {
|
|
564
|
-
const db = TestApp.database()
|
|
565
|
-
await Organizations.create(db, { id: management.orgId, name: 'Acme' })
|
|
566
|
-
const app = createApp({
|
|
567
|
-
db,
|
|
568
|
-
tmp_earlyAccess: true,
|
|
569
|
-
kv: { store: TestApp.kvStore({ keys: [management] }) },
|
|
570
|
-
})
|
|
571
|
-
|
|
572
|
-
const viaKey = await app.request(`/v1/orgs/${management.orgId}/invitations`, {
|
|
573
|
-
body: JSON.stringify({ email: 'key@example.net', role: 'member' }),
|
|
574
|
-
headers: { 'content-type': 'application/json', 'tempo-api-key': management.token },
|
|
575
|
-
method: 'POST',
|
|
576
|
-
})
|
|
577
|
-
expect(viaKey.status).toBe(200)
|
|
578
|
-
const keyInvitation = await TestApp.json(viaKey, InvitationsApp.schema.Invitation)
|
|
579
|
-
expect((await Invitations.get(db, keyInvitation.id))?.grantsEarlyAccess).toBe(true)
|
|
580
|
-
|
|
581
|
-
const created = await app.request('/v1/orgs', {
|
|
582
|
-
body: JSON.stringify({ name: 'Provisioned' }),
|
|
583
|
-
headers: { 'content-type': 'application/json', 'tempo-api-key': secret },
|
|
584
|
-
method: 'POST',
|
|
585
|
-
})
|
|
586
|
-
const org = await TestApp.json(created, Orgs.schema.Organization)
|
|
587
|
-
const viaAdmin = await app.request(`/v1/orgs/${org.id}/invitations`, {
|
|
588
|
-
body: JSON.stringify({ email: 'admin@example.net', role: 'owner' }),
|
|
589
|
-
headers: { 'content-type': 'application/json', 'tempo-api-key': secret },
|
|
590
|
-
method: 'POST',
|
|
591
|
-
})
|
|
592
|
-
expect(viaAdmin.status).toBe(200)
|
|
593
|
-
const adminInvitation = await TestApp.json(viaAdmin, InvitationsApp.schema.Invitation)
|
|
594
|
-
expect((await Invitations.get(db, adminInvitation.id))?.grantsEarlyAccess).toBe(true)
|
|
595
|
-
})
|
|
596
|
-
|
|
597
|
-
test('re-invites upgrade the grant and never downgrade it', async () => {
|
|
598
|
-
const db = TestApp.database()
|
|
599
|
-
const app = createApp({ db, tmp_earlyAccess: true })
|
|
600
|
-
await EarlyAccess.add(db, { createdBy: 'test', entry: 'example.org' })
|
|
601
|
-
const owner = await session(app)
|
|
602
|
-
const created = await app.request('/v1/orgs', json('POST', owner.cookie, { name: 'Acme' }))
|
|
603
|
-
const org = await TestApp.json(created, Orgs.schema.Organization)
|
|
604
|
-
const admin = await session(app)
|
|
605
|
-
await Users.setEmail(db, admin.userId, 'admin@example.org')
|
|
606
|
-
await Memberships.create(db, { orgId: org.id, role: 'admin', userId: admin.userId })
|
|
607
|
-
|
|
608
|
-
const first = await app.request(
|
|
609
|
-
`/v1/orgs/${org.id}/invitations`,
|
|
610
|
-
json('POST', owner.cookie, { email: 'x@example.net', role: 'member' }),
|
|
611
|
-
)
|
|
612
|
-
const firstInvite = await TestApp.json(first, InvitationsApp.schema.Invitation)
|
|
613
|
-
expect((await Invitations.get(db, firstInvite.id))?.grantsEarlyAccess).toBe(false)
|
|
614
|
-
|
|
615
|
-
// An early-access re-invite upgrades the same pending row.
|
|
616
|
-
const second = await app.request(
|
|
617
|
-
`/v1/orgs/${org.id}/invitations`,
|
|
618
|
-
json('POST', admin.cookie, { email: 'x@example.net', role: 'member' }),
|
|
619
|
-
)
|
|
620
|
-
const secondInvite = await TestApp.json(second, InvitationsApp.schema.Invitation)
|
|
621
|
-
expect(secondInvite.id).toBe(firstInvite.id)
|
|
622
|
-
expect((await Invitations.get(db, firstInvite.id))?.grantsEarlyAccess).toBe(true)
|
|
623
|
-
|
|
624
|
-
// A later non-early-access re-invite never withdraws the grant.
|
|
625
|
-
const third = await app.request(
|
|
626
|
-
`/v1/orgs/${org.id}/invitations`,
|
|
627
|
-
json('POST', owner.cookie, { email: 'x@example.net', role: 'member' }),
|
|
628
|
-
)
|
|
629
|
-
expect(third.status).toBe(200)
|
|
630
|
-
expect((await Invitations.get(db, firstInvite.id))?.grantsEarlyAccess).toBe(true)
|
|
631
|
-
})
|
|
632
|
-
|
|
633
|
-
test('hasEarlyAccessGrant survives acceptance and expiry, dies on revoke', async () => {
|
|
634
|
-
const db = TestApp.database()
|
|
635
|
-
const app = createApp({ db, tmp_earlyAccess: true })
|
|
636
|
-
await EarlyAccess.add(db, { createdBy: 'test', entry: 'example.org' })
|
|
637
|
-
const owner = await session(app)
|
|
638
|
-
await Users.setEmail(db, owner.userId, 'owner@example.org')
|
|
639
|
-
const created = await app.request('/v1/orgs', json('POST', owner.cookie, { name: 'Acme' }))
|
|
640
|
-
const org = await TestApp.json(created, Orgs.schema.Organization)
|
|
641
|
-
|
|
642
|
-
// Acceptance settles the invitation but keeps the grant.
|
|
643
|
-
const invited = await app.request(
|
|
644
|
-
`/v1/orgs/${org.id}/invitations`,
|
|
645
|
-
json('POST', owner.cookie, { email: 'z@example.net', role: 'member' }),
|
|
646
|
-
)
|
|
647
|
-
const invitation = await TestApp.json(invited, InvitationsApp.schema.Invitation)
|
|
648
|
-
const invitee = await session(app)
|
|
649
|
-
await Users.setEmail(db, invitee.userId, 'z@example.net')
|
|
650
|
-
const accepted = await app.request(`/v1/invitations/${invitation.id}/accept`, {
|
|
651
|
-
headers: { cookie: invitee.cookie },
|
|
652
|
-
method: 'POST',
|
|
653
|
-
})
|
|
654
|
-
expect(accepted.status).toBe(200)
|
|
655
|
-
expect(await Invitations.hasEarlyAccessGrant(db, 'z@example.net')).toBe(true)
|
|
656
|
-
|
|
657
|
-
// Expiry is ignored by the grant lookup.
|
|
658
|
-
await Invitations.create(db, {
|
|
659
|
-
email: 'e@example.net',
|
|
660
|
-
expiresAt: '2020-01-01T00:00:00.000Z',
|
|
661
|
-
grantsEarlyAccess: true,
|
|
662
|
-
invitedBy: owner.userId,
|
|
663
|
-
orgId: org.id,
|
|
664
|
-
role: 'member',
|
|
665
|
-
})
|
|
666
|
-
expect(await Invitations.hasEarlyAccessGrant(db, 'e@example.net')).toBe(true)
|
|
667
|
-
|
|
668
|
-
// Revocation withdraws the grant.
|
|
669
|
-
const pending = await app.request(
|
|
670
|
-
`/v1/orgs/${org.id}/invitations`,
|
|
671
|
-
json('POST', owner.cookie, { email: 'r@example.net', role: 'member' }),
|
|
672
|
-
)
|
|
673
|
-
const pendingInvite = await TestApp.json(pending, InvitationsApp.schema.Invitation)
|
|
674
|
-
expect(await Invitations.hasEarlyAccessGrant(db, 'r@example.net')).toBe(true)
|
|
675
|
-
const revoked = await app.request(`/v1/orgs/${org.id}/invitations/${pendingInvite.id}`, {
|
|
676
|
-
headers: { cookie: owner.cookie },
|
|
677
|
-
method: 'DELETE',
|
|
678
|
-
})
|
|
679
|
-
expect(revoked.status).toBe(200)
|
|
680
|
-
expect(await Invitations.hasEarlyAccessGrant(db, 'r@example.net')).toBe(false)
|
|
681
|
-
})
|
|
682
|
-
})
|
|
@@ -261,15 +261,12 @@ export function invitations() {
|
|
|
261
261
|
const principal = Auth.getPrincipal(c)
|
|
262
262
|
const invitedBy =
|
|
263
263
|
principal?.type === 'api_key' ? principal.id : (caller?.userId ?? 'super_admin')
|
|
264
|
-
// Elevation transfers only from inviters who themselves have early access.
|
|
265
|
-
const grantsEarlyAccess = await Auth.tmp_earlyAccessGrant(c)
|
|
266
264
|
|
|
267
265
|
const db = Db.get(c.get('db'))
|
|
268
266
|
try {
|
|
269
267
|
const record = await Invitations.create(db, {
|
|
270
268
|
email: body.email,
|
|
271
269
|
expiresAt: new Date(Date.now() + ttlDays * 86_400_000).toISOString(),
|
|
272
|
-
grantsEarlyAccess,
|
|
273
270
|
invitedBy,
|
|
274
271
|
orgId: Auth.org(c).id,
|
|
275
272
|
role: body.role,
|
|
@@ -4,8 +4,6 @@ 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'
|
|
9
7
|
import * as Me from './me.js'
|
|
10
8
|
|
|
11
9
|
/** Origin pinned for SIWE domain binding; Hono test requests use this host. */
|
|
@@ -20,40 +18,6 @@ function createApp() {
|
|
|
20
18
|
})
|
|
21
19
|
}
|
|
22
20
|
|
|
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
|
-
|
|
57
21
|
describe('GET /me', () => {
|
|
58
22
|
test('returns the signed-in user', async () => {
|
|
59
23
|
const app = createApp()
|
|
@@ -219,13 +183,12 @@ describe('sign-in email requirement', () => {
|
|
|
219
183
|
})
|
|
220
184
|
|
|
221
185
|
describe('open sign-up', () => {
|
|
222
|
-
test('allows new users on any domain
|
|
186
|
+
test('allows new users on any domain', async () => {
|
|
223
187
|
const oidc = await createIssuer({ email: 'dev@example.com' })
|
|
224
188
|
try {
|
|
225
189
|
const app = TestApp.create({
|
|
226
190
|
auth: { keys: [TestApp.key] },
|
|
227
191
|
session: { wallet: { issuer: oidc.url, origin } },
|
|
228
|
-
tmp_earlyAccess: true,
|
|
229
192
|
})
|
|
230
193
|
const account = privateKeyToAccount(generatePrivateKey())
|
|
231
194
|
const { cookie, verifyResponse } = await TestApp.signIn(app, account, {
|
|
@@ -242,8 +205,8 @@ describe('open sign-up', () => {
|
|
|
242
205
|
}
|
|
243
206
|
})
|
|
244
207
|
|
|
245
|
-
test('allows new users without an identity token
|
|
246
|
-
const app =
|
|
208
|
+
test('allows new users without an identity token', async () => {
|
|
209
|
+
const app = createApp()
|
|
247
210
|
const account = privateKeyToAccount(generatePrivateKey())
|
|
248
211
|
const { cookie, verifyResponse } = await TestApp.signIn(app, account)
|
|
249
212
|
|
|
@@ -252,111 +215,6 @@ describe('open sign-up', () => {
|
|
|
252
215
|
})
|
|
253
216
|
})
|
|
254
217
|
|
|
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
|
-
})
|
|
261
|
-
|
|
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)
|
|
270
|
-
})
|
|
271
|
-
|
|
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
|
-
})
|
|
284
|
-
|
|
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')
|
|
290
|
-
|
|
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)
|
|
299
|
-
})
|
|
300
|
-
|
|
301
|
-
test('flips false once the entry is removed', async () => {
|
|
302
|
-
const db = TestApp.database()
|
|
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
|
-
})
|
|
312
|
-
|
|
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 }
|
|
327
|
-
|
|
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',
|
|
336
|
-
})
|
|
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)
|
|
357
|
-
})
|
|
358
|
-
})
|
|
359
|
-
|
|
360
218
|
/** Local OIDC issuer standing in for the wallet: the SDK's own provider (discovery, JWKS, token mint). */
|
|
361
219
|
async function createIssuer(options: { email: string }) {
|
|
362
220
|
// Extractable: the provider takes key material as JWK strings.
|
|
@@ -35,11 +35,6 @@ 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
|
-
),
|
|
43
38
|
updatedAt: z
|
|
44
39
|
.string()
|
|
45
40
|
.check(
|
|
@@ -99,7 +94,6 @@ export function me() {
|
|
|
99
94
|
createdAt: user.createdAt,
|
|
100
95
|
...(user.email ? { email: user.email } : {}),
|
|
101
96
|
id: user.id,
|
|
102
|
-
tmp_earlyAccess: await Auth.tmp_earlyAccess(c),
|
|
103
97
|
updatedAt: user.updatedAt,
|
|
104
98
|
}),
|
|
105
99
|
200,
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'
|
|
2
2
|
|
|
3
3
|
import * as TestApp from '../../../../test/App.js'
|
|
4
|
+
import * as EarlyAccess from '../../../db/tables/earlyAccess.js'
|
|
5
|
+
import * as EnabledBillingSources from '../../../db/tables/enabledBillingSources.js'
|
|
4
6
|
import * as Organizations from '../../../db/tables/organizations.js'
|
|
5
7
|
import * as Projects from '../../../db/tables/projects.js'
|
|
8
|
+
import * as Users from '../../../db/tables/users.js'
|
|
6
9
|
import type * as Log from '../../../internal/Log.js'
|
|
7
10
|
import * as Orgs from './orgs.js'
|
|
8
11
|
|
|
@@ -196,7 +199,8 @@ describe('GET /orgs', () => {
|
|
|
196
199
|
|
|
197
200
|
describe('POST /orgs', () => {
|
|
198
201
|
test('creates a session-owned organization', async () => {
|
|
199
|
-
const
|
|
202
|
+
const db = TestApp.database()
|
|
203
|
+
const app = createApp(db)
|
|
200
204
|
const cookie = await session(app)
|
|
201
205
|
|
|
202
206
|
const response = await app.request('/v1/orgs', json('POST', cookie, { name: 'Acme' }))
|
|
@@ -204,10 +208,48 @@ describe('POST /orgs', () => {
|
|
|
204
208
|
const body = await TestApp.json(response, Orgs.schema.Organization)
|
|
205
209
|
expect(body.id.startsWith('org_')).toBe(true)
|
|
206
210
|
expect(body.name).toBe('Acme')
|
|
211
|
+
expect(await EnabledBillingSources.listByOrg(db, body.id)).toEqual([])
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
test.each([
|
|
215
|
+
{ email: 'Dev@Example.NET', entry: 'dev@example.net', kind: 'email' },
|
|
216
|
+
{ email: 'Dev@Example.ORG', entry: 'example.org', kind: 'domain' },
|
|
217
|
+
])('enables Stripe for a session matching an early-access $kind', async ({ email, entry }) => {
|
|
218
|
+
const db = TestApp.database()
|
|
219
|
+
const app = createApp(db)
|
|
220
|
+
await EarlyAccess.add(db, { createdBy: 'test', entry })
|
|
221
|
+
const account = privateKeyToAccount(generatePrivateKey())
|
|
222
|
+
const { cookie } = await TestApp.signIn(app, account)
|
|
223
|
+
const user = await Users.getByAddress(db, account.address)
|
|
224
|
+
if (!user) throw new Error('sign-in did not create a user')
|
|
225
|
+
await Users.setEmail(db, user.id, email)
|
|
226
|
+
|
|
227
|
+
const response = await app.request('/v1/orgs', json('POST', cookie!, { name: 'Acme' }))
|
|
228
|
+
expect(response.status).toBe(200)
|
|
229
|
+
const org = await TestApp.json(response, Orgs.schema.Organization)
|
|
230
|
+
const sources = await EnabledBillingSources.listByOrg(db, org.id)
|
|
231
|
+
expect(sources.map(({ createdBy, source }) => ({ createdBy, source }))).toEqual([
|
|
232
|
+
{ createdBy: user.id, source: 'stripe' },
|
|
233
|
+
])
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
test('does not enable Stripe without an early-access grant', async () => {
|
|
237
|
+
const db = TestApp.database()
|
|
238
|
+
const app = createApp(db)
|
|
239
|
+
const account = privateKeyToAccount(generatePrivateKey())
|
|
240
|
+
const { cookie } = await TestApp.signIn(app, account)
|
|
241
|
+
const user = await Users.getByAddress(db, account.address)
|
|
242
|
+
if (!user) throw new Error('sign-in did not create a user')
|
|
243
|
+
await Users.setEmail(db, user.id, 'dev@example.net')
|
|
244
|
+
|
|
245
|
+
const response = await app.request('/v1/orgs', json('POST', cookie!, { name: 'Acme' }))
|
|
246
|
+
const org = await TestApp.json(response, Orgs.schema.Organization)
|
|
247
|
+
expect(await EnabledBillingSources.listByOrg(db, org.id)).toEqual([])
|
|
207
248
|
})
|
|
208
249
|
|
|
209
250
|
test('creates a memberless org as the super admin', async () => {
|
|
210
|
-
const
|
|
251
|
+
const db = TestApp.database()
|
|
252
|
+
const app = createApp(db)
|
|
211
253
|
const response = await app.request('/v1/orgs', {
|
|
212
254
|
body: JSON.stringify({ name: 'Ops' }),
|
|
213
255
|
headers: { 'content-type': 'application/json', 'tempo-api-key': secret },
|
|
@@ -216,6 +258,7 @@ describe('POST /orgs', () => {
|
|
|
216
258
|
expect(response.status).toBe(200)
|
|
217
259
|
const org = await TestApp.json(response, Orgs.schema.Organization)
|
|
218
260
|
expect(org.name).toBe('Ops')
|
|
261
|
+
expect(await EnabledBillingSources.listByOrg(db, org.id)).toEqual([])
|
|
219
262
|
|
|
220
263
|
// Memberless: invisible to sessions until an owner is seated.
|
|
221
264
|
const cookie = await session(app)
|
|
@@ -5,6 +5,7 @@ import * as ApiKeys from '../../../ApiKeys.js'
|
|
|
5
5
|
import type * as App from '../../../App.js'
|
|
6
6
|
import * as Auth from '../../../internal/Auth.js'
|
|
7
7
|
import * as Db from '../../../db/Db.js'
|
|
8
|
+
import * as EarlyAccess from '../../../db/tables/earlyAccess.js'
|
|
8
9
|
import * as Memberships from '../../../db/tables/memberships.js'
|
|
9
10
|
import * as OpenApi from '../../../internal/OpenApi.js'
|
|
10
11
|
import * as Organizations from '../../../db/tables/organizations.js'
|
|
@@ -189,6 +190,11 @@ export function orgs() {
|
|
|
189
190
|
? await Organizations.create(db, { createdBy: 'super_admin', name: body.name })
|
|
190
191
|
: {
|
|
191
192
|
...(await Organizations.createOwned(db, {
|
|
193
|
+
enabledBillingSources:
|
|
194
|
+
principal.email &&
|
|
195
|
+
(await EarlyAccess.matches(db, principal.email.trim().toLowerCase()))
|
|
196
|
+
? ['stripe']
|
|
197
|
+
: [],
|
|
192
198
|
name: body.name,
|
|
193
199
|
userId: principal.id,
|
|
194
200
|
})),
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { Hono } from 'hono'
|
|
2
|
+
import type * as z from 'zod/mini'
|
|
3
|
+
|
|
4
|
+
import type * as App from '../../App.js'
|
|
5
|
+
import * as Auth from '../../internal/Auth.js'
|
|
6
|
+
import * as OpenApi from '../../internal/OpenApi.js'
|
|
7
|
+
import * as Metadata from '../metadata.js'
|
|
8
|
+
import * as Mpp from './Mpp.js'
|
|
9
|
+
|
|
10
|
+
/** Mounts MPP credential verification and broadcast routes. */
|
|
11
|
+
export function mpp() {
|
|
12
|
+
const app = new Hono<App.Environment>()
|
|
13
|
+
.post(
|
|
14
|
+
'/v1/mpp/verify',
|
|
15
|
+
Auth.policy({ apiKey: { scopes: ['mpp:write'] }, mpp: false, public: false }),
|
|
16
|
+
OpenApi.validate('json', Mpp.schema.RelayInput, bodyValidation),
|
|
17
|
+
OpenApi.describeRoute({
|
|
18
|
+
operationId: 'verifyMppCredential',
|
|
19
|
+
responses: responses({
|
|
20
|
+
description: 'The MPP credential validation result.',
|
|
21
|
+
schema: Mpp.schema.VerifyResponse,
|
|
22
|
+
}),
|
|
23
|
+
summary: 'Verify MPP credential',
|
|
24
|
+
tags: ['MPP Relay'],
|
|
25
|
+
}),
|
|
26
|
+
(c) => {
|
|
27
|
+
if (Auth.narrowAccess) return Auth.accessError(c)
|
|
28
|
+
if (OpenApi.narrowValidation) return OpenApi.validationError(c, bodyValidation)
|
|
29
|
+
return Mpp.unavailable<Mpp.VerifyResponse>(c)
|
|
30
|
+
},
|
|
31
|
+
)
|
|
32
|
+
.post(
|
|
33
|
+
'/v1/mpp/broadcast',
|
|
34
|
+
Auth.policy({ apiKey: { scopes: ['mpp:write'] }, mpp: false, public: false }),
|
|
35
|
+
OpenApi.validate('json', Mpp.schema.RelayInput, bodyValidation),
|
|
36
|
+
OpenApi.describeRoute({
|
|
37
|
+
operationId: 'broadcastMppCredential',
|
|
38
|
+
responses: responses({
|
|
39
|
+
description: 'The MPP credential broadcast result.',
|
|
40
|
+
schema: Mpp.schema.BroadcastResponse,
|
|
41
|
+
}),
|
|
42
|
+
summary: 'Broadcast MPP credential',
|
|
43
|
+
tags: ['MPP Relay'],
|
|
44
|
+
}),
|
|
45
|
+
(c) => {
|
|
46
|
+
if (Auth.narrowAccess) return Auth.accessError(c)
|
|
47
|
+
if (OpenApi.narrowValidation) return OpenApi.validationError(c, bodyValidation)
|
|
48
|
+
return Mpp.unavailable<Mpp.BroadcastResponse>(c)
|
|
49
|
+
},
|
|
50
|
+
)
|
|
51
|
+
return Metadata.attach(app, openapi)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const bodyValidation = {
|
|
55
|
+
code: 'body_invalid',
|
|
56
|
+
message: 'Check the MPP credential and challenge, then try again.',
|
|
57
|
+
} as const
|
|
58
|
+
|
|
59
|
+
function responses<schema extends z.ZodMiniType>(options: { description: string; schema: schema }) {
|
|
60
|
+
return {
|
|
61
|
+
...OpenApi.responses({
|
|
62
|
+
errors: { 403: 'The API key does not grant MPP relay access.' },
|
|
63
|
+
success: options,
|
|
64
|
+
}),
|
|
65
|
+
503: {
|
|
66
|
+
content: { 'application/json': { schema: OpenApi.resolver(Mpp.schema.Unavailable) } },
|
|
67
|
+
description: 'MPP relay settlement is temporarily unavailable.',
|
|
68
|
+
headers: {
|
|
69
|
+
'Retry-After': {
|
|
70
|
+
description: 'How many seconds to wait before retrying the relay.',
|
|
71
|
+
schema: { type: 'integer' as const },
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const openapi = {
|
|
79
|
+
tags: [
|
|
80
|
+
{
|
|
81
|
+
name: 'MPP Relay',
|
|
82
|
+
description: 'Validate and broadcast MPP payment credentials through Tempo.',
|
|
83
|
+
},
|
|
84
|
+
],
|
|
85
|
+
'x-tagGroups': [{ name: 'Payments', tags: ['MPP Relay'] }],
|
|
86
|
+
} satisfies App.create.Metadata
|