tapimo 0.4.3 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/App.d.ts +7222 -6316
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js +4 -0
- package/dist/App.js.map +1 -1
- package/dist/Scope.d.ts +18 -1
- package/dist/Scope.d.ts.map +1 -1
- package/dist/Scope.js +24 -3
- package/dist/Scope.js.map +1 -1
- package/dist/admin/apps/verified-tokens.d.ts +116 -116
- package/dist/apps/data/App.d.ts +2541 -2607
- package/dist/apps/data/App.d.ts.map +1 -1
- package/dist/apps/data/App.js +135 -44
- package/dist/apps/data/App.js.map +1 -1
- package/dist/apps/data/FundingQuoteProviders.d.ts +36 -0
- package/dist/apps/data/FundingQuoteProviders.d.ts.map +1 -0
- package/dist/apps/data/FundingQuoteProviders.js +190 -0
- package/dist/apps/data/FundingQuoteProviders.js.map +1 -0
- package/dist/apps/data/index.d.ts +1 -1
- package/dist/apps/data/index.js +1 -1
- package/dist/apps/data/routes/activities.d.ts +242 -242
- package/dist/apps/data/routes/addresses.d.ts +227 -227
- package/dist/apps/data/routes/balances.d.ts +12 -12
- package/dist/apps/data/routes/balances.d.ts.map +1 -1
- package/dist/apps/data/routes/balances.js +67 -149
- package/dist/apps/data/routes/balances.js.map +1 -1
- package/dist/apps/data/routes/blocks.d.ts +125 -126
- package/dist/apps/data/routes/blocks.d.ts.map +1 -1
- package/dist/apps/data/routes/blocks.js +3 -5
- package/dist/apps/data/routes/blocks.js.map +1 -1
- package/dist/apps/data/routes/coingecko.d.ts +55 -54
- package/dist/apps/data/routes/coingecko.d.ts.map +1 -1
- package/dist/apps/data/routes/coingecko.js +3 -8
- package/dist/apps/data/routes/coingecko.js.map +1 -1
- package/dist/apps/data/routes/exchanges.d.ts +455 -455
- package/dist/apps/data/routes/fee-amm.d.ts +92 -92
- package/dist/apps/data/routes/funding.d.ts +91 -864
- package/dist/apps/data/routes/funding.d.ts.map +1 -1
- package/dist/apps/data/routes/funding.js +138 -450
- package/dist/apps/data/routes/funding.js.map +1 -1
- package/dist/apps/data/routes/rpc.d.ts.map +1 -1
- package/dist/apps/data/routes/rpc.js +86 -16
- package/dist/apps/data/routes/rpc.js.map +1 -1
- package/dist/apps/data/routes/tokenlist.d.ts +36 -36
- package/dist/apps/data/routes/webhooks.d.ts +340 -0
- package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
- package/dist/apps/data/routes/webhooks.js +13 -1
- package/dist/apps/data/routes/webhooks.js.map +1 -1
- package/dist/apps/index.d.ts +1 -1
- package/dist/apps/index.js +1 -1
- package/dist/apps/management/App.d.ts +1843 -861
- package/dist/apps/management/App.d.ts.map +1 -1
- package/dist/apps/management/App.js +4 -0
- package/dist/apps/management/App.js.map +1 -1
- package/dist/apps/management/routes/api-keys.d.ts +121 -121
- package/dist/apps/management/routes/api-keys.js +2 -2
- package/dist/apps/management/routes/api-keys.js.map +1 -1
- package/dist/apps/management/routes/billing.d.ts +2 -2
- package/dist/apps/management/routes/invite-links.d.ts +1100 -0
- package/dist/apps/management/routes/invite-links.d.ts.map +1 -0
- package/dist/apps/management/routes/invite-links.js +411 -0
- package/dist/apps/management/routes/invite-links.js.map +1 -0
- package/dist/apps/management/routes/me.d.ts +13 -13
- package/dist/apps/management/routes/members.d.ts +77 -77
- package/dist/apps/management/routes/orgs.d.ts +96 -96
- package/dist/apps/management/routes/projects.d.ts +62 -62
- package/dist/apps/management/routes/scopes.d.ts +11 -11
- package/dist/apps/management/routes/scopes.d.ts.map +1 -1
- package/dist/apps/management/routes/scopes.js +2 -2
- package/dist/apps/management/routes/scopes.js.map +1 -1
- package/dist/apps/management/routes/usage.d.ts +79 -79
- package/dist/apps/mcp.d.ts.map +1 -1
- package/dist/apps/mcp.js +3 -1
- package/dist/apps/mcp.js.map +1 -1
- package/dist/apps/relay/App.d.ts.map +1 -1
- package/dist/apps/relay/App.js +42 -4
- package/dist/apps/relay/App.js.map +1 -1
- package/dist/cloudflare.d.ts +2 -0
- package/dist/cloudflare.d.ts.map +1 -1
- package/dist/cloudflare.js +4 -0
- package/dist/cloudflare.js.map +1 -1
- package/dist/db/Db.d.ts +3 -0
- package/dist/db/Db.d.ts.map +1 -1
- package/dist/db/Db.js +57 -0
- package/dist/db/Db.js.map +1 -1
- package/dist/db/Schema.d.ts +31 -0
- package/dist/db/Schema.d.ts.map +1 -1
- package/dist/db/Schema.js +31 -0
- package/dist/db/Schema.js.map +1 -1
- package/dist/db/tables/inviteLinks.d.ts +42 -0
- package/dist/db/tables/inviteLinks.d.ts.map +1 -0
- package/dist/db/tables/inviteLinks.js +148 -0
- package/dist/db/tables/inviteLinks.js.map +1 -0
- package/dist/db/tables/organizations.d.ts.map +1 -1
- package/dist/db/tables/organizations.js +2 -0
- package/dist/db/tables/organizations.js.map +1 -1
- package/dist/handlers/relay.d.ts.map +1 -1
- package/dist/handlers/relay.js +13 -3
- package/dist/handlers/relay.js.map +1 -1
- package/dist/internal/Auth.d.ts +42 -23
- package/dist/internal/Auth.d.ts.map +1 -1
- package/dist/internal/Auth.js +169 -20
- package/dist/internal/Auth.js.map +1 -1
- package/dist/internal/EdgeCache.d.ts +3 -1
- package/dist/internal/EdgeCache.d.ts.map +1 -1
- package/dist/internal/EdgeCache.js +17 -2
- package/dist/internal/EdgeCache.js.map +1 -1
- package/dist/internal/Errors.d.ts +6 -0
- package/dist/internal/Errors.d.ts.map +1 -0
- package/dist/internal/Errors.js +16 -0
- package/dist/internal/Errors.js.map +1 -0
- package/dist/internal/FundingQuotes.d.ts +644 -0
- package/dist/internal/FundingQuotes.d.ts.map +1 -0
- package/dist/internal/{FundingRoutes.js → FundingQuotes.js} +93 -289
- package/dist/internal/FundingQuotes.js.map +1 -0
- package/dist/internal/Store.d.ts +61 -16
- package/dist/internal/Store.d.ts.map +1 -1
- package/dist/internal/Store.js +72 -17
- package/dist/internal/Store.js.map +1 -1
- package/dist/internal/Viem.d.ts +2 -0
- package/dist/internal/Viem.d.ts.map +1 -1
- package/dist/internal/Viem.js +11 -2
- package/dist/internal/Viem.js.map +1 -1
- package/dist/internal/index.d.ts +27 -0
- package/dist/internal/index.d.ts.map +1 -0
- package/dist/internal/index.js +27 -0
- package/dist/internal/index.js.map +1 -0
- package/package.json +8 -3
- package/src/ApiKeys.test.ts +23 -0
- package/src/App.test-d.ts +2 -0
- package/src/App.test.ts +60 -4
- package/src/App.ts +6 -0
- package/src/Client.test-d.ts +16 -54
- package/src/Scope.test.ts +1 -0
- package/src/Scope.ts +30 -4
- package/src/apps/data/App.test.ts +354 -0
- package/src/apps/data/App.ts +159 -52
- package/src/apps/data/FundingQuoteProviders.test.ts +151 -0
- package/src/apps/data/FundingQuoteProviders.ts +251 -0
- package/src/apps/data/index.ts +1 -1
- package/src/apps/data/routes/balances.test.ts +64 -18
- package/src/apps/data/routes/balances.ts +76 -177
- package/src/apps/data/routes/blocks.ts +3 -5
- package/src/apps/data/routes/coingecko.ts +3 -8
- package/src/apps/data/routes/funding.test.ts +149 -306
- package/src/apps/data/routes/funding.ts +154 -604
- package/src/apps/data/routes/indexer.test.ts +99 -3
- package/src/apps/data/routes/rpc.test.ts +219 -0
- package/src/apps/data/routes/rpc.ts +92 -17
- package/src/apps/data/routes/webhooks.test.ts +23 -2
- package/src/apps/data/routes/webhooks.ts +13 -1
- package/src/apps/index.ts +1 -1
- package/src/apps/management/App.ts +4 -0
- package/src/apps/management/routes/api-keys.test.ts +47 -3
- package/src/apps/management/routes/api-keys.ts +2 -2
- package/src/apps/management/routes/invite-links.test.ts +385 -0
- package/src/apps/management/routes/invite-links.ts +570 -0
- package/src/apps/management/routes/scopes.test.ts +3 -2
- package/src/apps/management/routes/scopes.ts +4 -2
- package/src/apps/mcp.test.ts +55 -14
- package/src/apps/mcp.ts +3 -1
- package/src/apps/relay/App.test.ts +324 -4
- package/src/apps/relay/App.ts +40 -4
- package/src/cloudflare.ts +5 -0
- package/src/db/Db.ts +65 -0
- package/src/db/Schema.ts +39 -0
- package/src/db/tables/inviteLinks.ts +187 -0
- package/src/db/tables/organizations.ts +2 -0
- package/src/handlers/relay.ts +16 -4
- package/src/internal/Auth.test.ts +528 -37
- package/src/internal/Auth.ts +246 -45
- package/src/internal/EdgeCache.test.ts +24 -3
- package/src/internal/EdgeCache.ts +18 -2
- package/src/internal/FundingQuotes.test.ts +174 -0
- package/src/internal/FundingQuotes.ts +735 -0
- package/src/internal/Log.test.ts +4 -4
- package/src/internal/Store.test-d.ts +15 -0
- package/src/internal/Store.test.ts +153 -2
- package/src/internal/Store.ts +157 -41
- package/src/internal/Viem.ts +14 -5
- package/src/internal/index.test.ts +34 -0
- package/src/internal/index.ts +26 -0
- package/dist/apps/data/FundingRouteProviders.d.ts +0 -143
- package/dist/apps/data/FundingRouteProviders.d.ts.map +0 -1
- package/dist/apps/data/FundingRouteProviders.js +0 -546
- package/dist/apps/data/FundingRouteProviders.js.map +0 -1
- package/dist/internal/FundingRoutes.d.ts +0 -1085
- package/dist/internal/FundingRoutes.d.ts.map +0 -1
- package/dist/internal/FundingRoutes.js.map +0 -1
- package/src/apps/data/FundingRouteProviders.test.ts +0 -485
- package/src/apps/data/FundingRouteProviders.ts +0 -744
- package/src/internal/FundingRoutes.test.ts +0 -239
- package/src/internal/FundingRoutes.ts +0 -1067
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { Hono, type Context } from 'hono'
|
|
2
2
|
import { requestId } from 'hono/request-id'
|
|
3
|
+
import { Challenge, Receipt } from 'mppx'
|
|
3
4
|
import { Mppx, tempo } from 'mppx/hono'
|
|
4
|
-
import {
|
|
5
|
+
import type { Session } from 'mppx/tempo'
|
|
6
|
+
import { RateLimit, Store } from 'tapimo'
|
|
7
|
+
import { Actions } from 'viem/tempo'
|
|
5
8
|
import * as ApiKey from '../ApiKey.js'
|
|
6
9
|
import type * as Db from '../db/Db.js'
|
|
7
10
|
import * as Memberships from '../db/tables/memberships.js'
|
|
@@ -14,6 +17,7 @@ import * as Scope from '../Scope.js'
|
|
|
14
17
|
import * as Viem from './Viem.js'
|
|
15
18
|
import * as TestApp from '../../test/App.js'
|
|
16
19
|
import { createClient as createMppClient } from '../../test/Mppx.js'
|
|
20
|
+
import * as TestStore from '../../test/Store.js'
|
|
17
21
|
import * as Tempo from '../../test/Tempo.js'
|
|
18
22
|
|
|
19
23
|
const key = {
|
|
@@ -559,7 +563,7 @@ describe('require', () => {
|
|
|
559
563
|
const policy: Auth.require.Policy = {
|
|
560
564
|
apiKey: { scopes: [Scope.wildcard] },
|
|
561
565
|
...(options.public === false ? {} : { public: { rateLimit: { perMinute: 60 } } }),
|
|
562
|
-
...(options.mpp ? { mpp: {
|
|
566
|
+
...(options.mpp ? { mpp: { session: { amount: '1' } } } : {}),
|
|
563
567
|
}
|
|
564
568
|
const app = new Hono<Auth.Environment>()
|
|
565
569
|
app.use('*', async (c, next) => {
|
|
@@ -739,6 +743,121 @@ describe('require', () => {
|
|
|
739
743
|
})
|
|
740
744
|
|
|
741
745
|
describe('mpp', () => {
|
|
746
|
+
test('uses a default paid-request limit of 100 per minute', async () => {
|
|
747
|
+
const rateLimit = underQuota()
|
|
748
|
+
const consume = vi.spyOn(rateLimit, 'consume')
|
|
749
|
+
const app = createApp({
|
|
750
|
+
auth: {
|
|
751
|
+
apiKey: { resolve: TestApp.resolver({ keys: [{ ...key, token }] }) },
|
|
752
|
+
mpp: createMppHandler(),
|
|
753
|
+
rateLimit,
|
|
754
|
+
},
|
|
755
|
+
policy: { mpp: { session: { amount: '1' } } },
|
|
756
|
+
})
|
|
757
|
+
const response = await app.request('/protected', {
|
|
758
|
+
headers: {
|
|
759
|
+
authorization: 'Payment paid',
|
|
760
|
+
'x-forwarded-for': '203.0.113.10',
|
|
761
|
+
},
|
|
762
|
+
})
|
|
763
|
+
|
|
764
|
+
expect(response.status).toBe(200)
|
|
765
|
+
expect(response.headers.get('RateLimit-Limit')).toBe('100')
|
|
766
|
+
expect(response.headers.get('RateLimit-Scope')).toBe('mpp')
|
|
767
|
+
expect(consume).toHaveBeenCalledWith({
|
|
768
|
+
key: 'mpp:public:203.0.113.10',
|
|
769
|
+
limit: { perMinute: 100 },
|
|
770
|
+
})
|
|
771
|
+
})
|
|
772
|
+
|
|
773
|
+
test('buckets paid API-key requests by key ID', async () => {
|
|
774
|
+
const rateLimit = underQuota()
|
|
775
|
+
const consume = vi.spyOn(rateLimit, 'consume')
|
|
776
|
+
const app = createApp({
|
|
777
|
+
auth: {
|
|
778
|
+
apiKey: { resolve: TestApp.resolver({ keys: [{ ...key, token }] }) },
|
|
779
|
+
mpp: createMppHandler(),
|
|
780
|
+
rateLimit,
|
|
781
|
+
},
|
|
782
|
+
policy: {
|
|
783
|
+
apiKey: { scopes: ['data:read'] },
|
|
784
|
+
mpp: { session: { amount: '1' } },
|
|
785
|
+
},
|
|
786
|
+
})
|
|
787
|
+
const response = await app.request('/protected', {
|
|
788
|
+
headers: {
|
|
789
|
+
authorization: 'Payment paid',
|
|
790
|
+
'tempo-api-key': token,
|
|
791
|
+
'x-forwarded-for': '203.0.113.10',
|
|
792
|
+
},
|
|
793
|
+
})
|
|
794
|
+
|
|
795
|
+
expect(response.status).toBe(200)
|
|
796
|
+
expect(consume).toHaveBeenCalledWith({
|
|
797
|
+
key: 'mpp:api_key:key_test',
|
|
798
|
+
limit: { perMinute: 100 },
|
|
799
|
+
})
|
|
800
|
+
})
|
|
801
|
+
|
|
802
|
+
test('uses the configured global paid-request limit', async () => {
|
|
803
|
+
const app = createApp({
|
|
804
|
+
auth: {
|
|
805
|
+
apiKey: { resolve: TestApp.resolver({ keys: [{ ...key, token }] }) },
|
|
806
|
+
mpp: createMppHandler({ rateLimit: { perMinute: 25 } }),
|
|
807
|
+
rateLimit: underQuota(),
|
|
808
|
+
},
|
|
809
|
+
policy: { mpp: { session: { amount: '1' } } },
|
|
810
|
+
})
|
|
811
|
+
const response = await app.request('/protected', {
|
|
812
|
+
headers: { authorization: 'Payment paid' },
|
|
813
|
+
})
|
|
814
|
+
|
|
815
|
+
expect(response.status).toBe(200)
|
|
816
|
+
expect(response.headers.get('RateLimit-Limit')).toBe('25')
|
|
817
|
+
})
|
|
818
|
+
|
|
819
|
+
test('route paid-request limit overrides the global limit', async () => {
|
|
820
|
+
const app = createApp({
|
|
821
|
+
auth: {
|
|
822
|
+
apiKey: { resolve: TestApp.resolver({ keys: [{ ...key, token }] }) },
|
|
823
|
+
mpp: createMppHandler({ rateLimit: { perMinute: 25 } }),
|
|
824
|
+
rateLimit: underQuota(),
|
|
825
|
+
},
|
|
826
|
+
policy: {
|
|
827
|
+
mpp: {
|
|
828
|
+
rateLimit: { perMinute: 5 },
|
|
829
|
+
session: { amount: '1' },
|
|
830
|
+
},
|
|
831
|
+
},
|
|
832
|
+
})
|
|
833
|
+
const response = await app.request('/protected', {
|
|
834
|
+
headers: { authorization: 'Payment paid' },
|
|
835
|
+
})
|
|
836
|
+
|
|
837
|
+
expect(response.status).toBe(200)
|
|
838
|
+
expect(response.headers.get('RateLimit-Limit')).toBe('5')
|
|
839
|
+
})
|
|
840
|
+
|
|
841
|
+
test('rejects paid requests over quota before accepting payment', async () => {
|
|
842
|
+
const onPayment = vi.fn()
|
|
843
|
+
const app = createApp({
|
|
844
|
+
auth: {
|
|
845
|
+
apiKey: { resolve: TestApp.resolver({ keys: [{ ...key, token }] }) },
|
|
846
|
+
mpp: createMppHandler({ onPayment }),
|
|
847
|
+
rateLimit: overQuota(),
|
|
848
|
+
},
|
|
849
|
+
policy: { mpp: { session: { amount: '1' } } },
|
|
850
|
+
})
|
|
851
|
+
const response = await app.request('/protected', {
|
|
852
|
+
headers: { authorization: 'Payment paid' },
|
|
853
|
+
})
|
|
854
|
+
|
|
855
|
+
expect(response.status).toBe(429)
|
|
856
|
+
expect(response.headers.get('RateLimit-Limit')).toBe('100')
|
|
857
|
+
expect((await response.json()).error.code).toBe('rate_limit_exceeded')
|
|
858
|
+
expect(onPayment).not.toHaveBeenCalled()
|
|
859
|
+
})
|
|
860
|
+
|
|
742
861
|
test('returns payment challenge for public over-quota requests', async () => {
|
|
743
862
|
const mpp = createMppServer()
|
|
744
863
|
const app = createApp({
|
|
@@ -748,7 +867,7 @@ describe('require', () => {
|
|
|
748
867
|
rateLimit: overQuota(),
|
|
749
868
|
},
|
|
750
869
|
policy: {
|
|
751
|
-
mpp: {
|
|
870
|
+
mpp: { session: { amount: '1', description: 'Protected data' } },
|
|
752
871
|
public: { rateLimit: { perMinute: 1 } },
|
|
753
872
|
},
|
|
754
873
|
})
|
|
@@ -760,16 +879,16 @@ describe('require', () => {
|
|
|
760
879
|
).toMatchInlineSnapshot(`true`)
|
|
761
880
|
})
|
|
762
881
|
|
|
763
|
-
test('accepts paid public retry without consuming quota', async () => {
|
|
882
|
+
test('accepts paid public retry without consuming public quota', async () => {
|
|
764
883
|
const mpp = createMppServer()
|
|
765
884
|
const app = createApp({
|
|
766
885
|
auth: {
|
|
767
886
|
apiKey: { resolve: TestApp.resolver({ keys: [{ ...key, token }] }) },
|
|
768
887
|
mpp,
|
|
769
|
-
rateLimit:
|
|
888
|
+
rateLimit: overQuotaExceptMpp(),
|
|
770
889
|
},
|
|
771
890
|
policy: {
|
|
772
|
-
mpp: {
|
|
891
|
+
mpp: { session: { amount: '1', description: 'Protected data' } },
|
|
773
892
|
public: { rateLimit: { perMinute: 1 } },
|
|
774
893
|
},
|
|
775
894
|
})
|
|
@@ -804,7 +923,7 @@ describe('require', () => {
|
|
|
804
923
|
rateLimit: { perMinute: 1 },
|
|
805
924
|
scopes: ['data:read'],
|
|
806
925
|
},
|
|
807
|
-
mpp: {
|
|
926
|
+
mpp: { session: { amount: '1', description: 'Protected data' } },
|
|
808
927
|
},
|
|
809
928
|
})
|
|
810
929
|
const response = await app.request('/protected', {
|
|
@@ -823,14 +942,14 @@ describe('require', () => {
|
|
|
823
942
|
auth: {
|
|
824
943
|
apiKey: { resolve: TestApp.resolver({ keys: [{ ...key, token }] }) },
|
|
825
944
|
mpp,
|
|
826
|
-
rateLimit:
|
|
945
|
+
rateLimit: overQuotaExceptMpp(),
|
|
827
946
|
},
|
|
828
947
|
policy: {
|
|
829
948
|
apiKey: {
|
|
830
949
|
rateLimit: { perMinute: 1 },
|
|
831
950
|
scopes: ['data:read'],
|
|
832
951
|
},
|
|
833
|
-
mpp: {
|
|
952
|
+
mpp: { session: { amount: '1', description: 'Protected data' } },
|
|
834
953
|
},
|
|
835
954
|
})
|
|
836
955
|
const response = await createMppClient(app).fetch('http://tempo-api.test/protected', {
|
|
@@ -855,6 +974,252 @@ describe('require', () => {
|
|
|
855
974
|
}
|
|
856
975
|
`)
|
|
857
976
|
})
|
|
977
|
+
|
|
978
|
+
test('session challenge advertises the configured request defaults', async () => {
|
|
979
|
+
const mpp = createMppServer()
|
|
980
|
+
const app = createApp({
|
|
981
|
+
auth: {
|
|
982
|
+
apiKey: { resolve: TestApp.resolver({ keys: [{ ...key, token }] }) },
|
|
983
|
+
mpp,
|
|
984
|
+
rateLimit: overQuota(),
|
|
985
|
+
},
|
|
986
|
+
policy: {
|
|
987
|
+
mpp: { session: { amount: '1', description: 'Protected data' } },
|
|
988
|
+
public: { rateLimit: { perMinute: 1 } },
|
|
989
|
+
},
|
|
990
|
+
})
|
|
991
|
+
const response = await app.request('/protected')
|
|
992
|
+
const challenge = Challenge.fromHeaders(response.headers)
|
|
993
|
+
|
|
994
|
+
expect(response.status).toBe(402)
|
|
995
|
+
expect(challenge.method).toBe('tempo')
|
|
996
|
+
expect(challenge.intent).toBe('session')
|
|
997
|
+
expect(challenge.realm).toBe('tempo-api-test')
|
|
998
|
+
expect(challenge.description).toBe('Protected data')
|
|
999
|
+
expect(challenge.request).toMatchObject({
|
|
1000
|
+
// Wire amounts are raw token units: '1' at 6 decimals.
|
|
1001
|
+
amount: '1000000',
|
|
1002
|
+
currency: Tempo.currency,
|
|
1003
|
+
methodDetails: {
|
|
1004
|
+
chainId: Tempo.chain.id,
|
|
1005
|
+
escrowContract: '0x4d50500000000000000000000000000000000000',
|
|
1006
|
+
sessionProtocol: 'v2',
|
|
1007
|
+
},
|
|
1008
|
+
recipient: Tempo.accounts[2].address,
|
|
1009
|
+
unitType: 'request',
|
|
1010
|
+
})
|
|
1011
|
+
})
|
|
1012
|
+
|
|
1013
|
+
test('session challenge uses the explicit request chain', async () => {
|
|
1014
|
+
const mpp = createMppServer({
|
|
1015
|
+
chainId: Viem.chainId.mainnet,
|
|
1016
|
+
getClient: ({ chainId = Viem.chainId.mainnet }) => Viem.getClient({ chainId }),
|
|
1017
|
+
})
|
|
1018
|
+
const app = createApp({
|
|
1019
|
+
auth: {
|
|
1020
|
+
apiKey: { resolve: TestApp.resolver({ keys: [{ ...key, token }] }) },
|
|
1021
|
+
mpp,
|
|
1022
|
+
rateLimit: overQuota(),
|
|
1023
|
+
},
|
|
1024
|
+
policy: {
|
|
1025
|
+
mpp: { session: { amount: '1' } },
|
|
1026
|
+
public: { rateLimit: { perMinute: 1 } },
|
|
1027
|
+
},
|
|
1028
|
+
})
|
|
1029
|
+
|
|
1030
|
+
const defaultResponse = await app.request('/protected')
|
|
1031
|
+
const testnetResponse = await app.request('/protected?chainId=testnet')
|
|
1032
|
+
|
|
1033
|
+
expect(Challenge.fromHeaders(defaultResponse.headers).request).toMatchObject({
|
|
1034
|
+
methodDetails: {
|
|
1035
|
+
chainId: Viem.chainId.mainnet,
|
|
1036
|
+
},
|
|
1037
|
+
})
|
|
1038
|
+
expect(Challenge.fromHeaders(testnetResponse.headers).request).toMatchObject({
|
|
1039
|
+
methodDetails: {
|
|
1040
|
+
chainId: Viem.chainId.testnet,
|
|
1041
|
+
},
|
|
1042
|
+
})
|
|
1043
|
+
})
|
|
1044
|
+
|
|
1045
|
+
test('route session overrides propagate to the challenge', async () => {
|
|
1046
|
+
const mpp = createMppServer()
|
|
1047
|
+
const app = createApp({
|
|
1048
|
+
auth: {
|
|
1049
|
+
apiKey: { resolve: TestApp.resolver({ keys: [{ ...key, token }] }) },
|
|
1050
|
+
mpp,
|
|
1051
|
+
rateLimit: overQuota(),
|
|
1052
|
+
},
|
|
1053
|
+
policy: {
|
|
1054
|
+
mpp: { session: { amount: '2.5', suggestedDeposit: '10' } },
|
|
1055
|
+
public: { rateLimit: { perMinute: 1 } },
|
|
1056
|
+
},
|
|
1057
|
+
})
|
|
1058
|
+
const response = await app.request('/protected')
|
|
1059
|
+
const challenge = Challenge.fromHeaders(response.headers)
|
|
1060
|
+
|
|
1061
|
+
expect(response.status).toBe(402)
|
|
1062
|
+
expect(challenge.request).toMatchObject({
|
|
1063
|
+
// Wire amounts are raw token units at 6 decimals.
|
|
1064
|
+
amount: '2500000',
|
|
1065
|
+
suggestedDeposit: '10000000',
|
|
1066
|
+
})
|
|
1067
|
+
})
|
|
1068
|
+
|
|
1069
|
+
test('paid retry opens a funded on-chain channel', async () => {
|
|
1070
|
+
const mpp = createMppServer()
|
|
1071
|
+
const app = createApp({
|
|
1072
|
+
auth: {
|
|
1073
|
+
apiKey: { resolve: TestApp.resolver({ keys: [{ ...key, token }] }) },
|
|
1074
|
+
mpp,
|
|
1075
|
+
rateLimit: overQuotaExceptMpp(),
|
|
1076
|
+
},
|
|
1077
|
+
policy: {
|
|
1078
|
+
mpp: { session: { amount: '1', description: 'Protected data' } },
|
|
1079
|
+
public: { rateLimit: { perMinute: 1 } },
|
|
1080
|
+
},
|
|
1081
|
+
})
|
|
1082
|
+
const response = await createMppClient(app).fetch('http://tempo-api.test/protected')
|
|
1083
|
+
|
|
1084
|
+
expect(response.status).toBe(200)
|
|
1085
|
+
const receipt = sessionReceipt(response)
|
|
1086
|
+
expect(receipt.method).toBe('tempo')
|
|
1087
|
+
expect(receipt.intent).toBe('session')
|
|
1088
|
+
expect(receipt.status).toBe('success')
|
|
1089
|
+
expect(receipt.channelId).toMatch(/^0x[0-9a-fA-F]{64}$/)
|
|
1090
|
+
// One paid request at amount '1' with 6 decimals.
|
|
1091
|
+
expect(receipt.spent).toBe('1000000')
|
|
1092
|
+
expect(BigInt(receipt.acceptedCumulative)).toBeGreaterThanOrEqual(1_000_000n)
|
|
1093
|
+
|
|
1094
|
+
const state = await Actions.channel.getStates(Tempo.client, {
|
|
1095
|
+
channel: receipt.channelId,
|
|
1096
|
+
})
|
|
1097
|
+
// The client deposits the request amount when no deposit is suggested.
|
|
1098
|
+
expect(state.deposit).toBe(1_000_000n)
|
|
1099
|
+
// No settlement schedule configured, so nothing settles inline.
|
|
1100
|
+
expect(state.settled).toBe(0n)
|
|
1101
|
+
expect(state.closeRequestedAt).toBe(0)
|
|
1102
|
+
})
|
|
1103
|
+
|
|
1104
|
+
test('reuses the session channel across paid requests', async () => {
|
|
1105
|
+
const mpp = createMppServer()
|
|
1106
|
+
const app = createApp({
|
|
1107
|
+
auth: {
|
|
1108
|
+
apiKey: { resolve: TestApp.resolver({ keys: [{ ...key, token }] }) },
|
|
1109
|
+
mpp,
|
|
1110
|
+
rateLimit: overQuotaExceptMpp(),
|
|
1111
|
+
},
|
|
1112
|
+
policy: {
|
|
1113
|
+
// The suggested deposit pre-funds the channel for both requests.
|
|
1114
|
+
mpp: { session: { amount: '1', suggestedDeposit: '2' } },
|
|
1115
|
+
public: { rateLimit: { perMinute: 1 } },
|
|
1116
|
+
},
|
|
1117
|
+
})
|
|
1118
|
+
const client = createMppClient(app)
|
|
1119
|
+
|
|
1120
|
+
const first = await client.fetch('http://tempo-api.test/protected')
|
|
1121
|
+
const second = await client.fetch('http://tempo-api.test/protected')
|
|
1122
|
+
|
|
1123
|
+
expect(first.status).toBe(200)
|
|
1124
|
+
expect(second.status).toBe(200)
|
|
1125
|
+
|
|
1126
|
+
const receipt1 = sessionReceipt(first)
|
|
1127
|
+
const receipt2 = sessionReceipt(second)
|
|
1128
|
+
expect(receipt2.channelId).toBe(receipt1.channelId)
|
|
1129
|
+
expect(BigInt(receipt2.acceptedCumulative)).toBeGreaterThan(
|
|
1130
|
+
BigInt(receipt1.acceptedCumulative),
|
|
1131
|
+
)
|
|
1132
|
+
expect(receipt2.spent).toBe('2000000')
|
|
1133
|
+
|
|
1134
|
+
const state = await Actions.channel.getStates(Tempo.client, {
|
|
1135
|
+
channel: receipt1.channelId,
|
|
1136
|
+
})
|
|
1137
|
+
expect(state.deposit).toBe(2_000_000n)
|
|
1138
|
+
})
|
|
1139
|
+
|
|
1140
|
+
test('reuses persisted session state after server restart', async () => {
|
|
1141
|
+
const { namespace } = TestStore.durableObjectNamespace()
|
|
1142
|
+
const createStore = () => Store.durableObject(namespace)
|
|
1143
|
+
let app = createApp({
|
|
1144
|
+
auth: {
|
|
1145
|
+
apiKey: { resolve: TestApp.resolver({ keys: [{ ...key, token }] }) },
|
|
1146
|
+
mpp: createMppServer({ store: createStore() }),
|
|
1147
|
+
rateLimit: overQuotaExceptMpp(),
|
|
1148
|
+
},
|
|
1149
|
+
policy: {
|
|
1150
|
+
mpp: { session: { amount: '1', suggestedDeposit: '2' } },
|
|
1151
|
+
public: { rateLimit: { perMinute: 1 } },
|
|
1152
|
+
},
|
|
1153
|
+
})
|
|
1154
|
+
const client = createMppClient({ fetch: (request) => app.fetch(request) })
|
|
1155
|
+
|
|
1156
|
+
const first = await client.fetch('http://tempo-api.test/protected')
|
|
1157
|
+
app = createApp({
|
|
1158
|
+
auth: {
|
|
1159
|
+
apiKey: { resolve: TestApp.resolver({ keys: [{ ...key, token }] }) },
|
|
1160
|
+
mpp: createMppServer({ store: createStore() }),
|
|
1161
|
+
rateLimit: overQuotaExceptMpp(),
|
|
1162
|
+
},
|
|
1163
|
+
policy: {
|
|
1164
|
+
mpp: { session: { amount: '1', suggestedDeposit: '2' } },
|
|
1165
|
+
public: { rateLimit: { perMinute: 1 } },
|
|
1166
|
+
},
|
|
1167
|
+
})
|
|
1168
|
+
const second = await client.fetch('http://tempo-api.test/protected')
|
|
1169
|
+
|
|
1170
|
+
expect(first.status).toBe(200)
|
|
1171
|
+
expect(second.status).toBe(200)
|
|
1172
|
+
expect(sessionReceipt(second)).toMatchObject({
|
|
1173
|
+
channelId: sessionReceipt(first).channelId,
|
|
1174
|
+
spent: '2000000',
|
|
1175
|
+
})
|
|
1176
|
+
})
|
|
1177
|
+
|
|
1178
|
+
test('rejects a voucher that exceeds the channel deposit', async () => {
|
|
1179
|
+
const mpp = createMppServer()
|
|
1180
|
+
const app = createApp({
|
|
1181
|
+
auth: {
|
|
1182
|
+
apiKey: { resolve: TestApp.resolver({ keys: [{ ...key, token }] }) },
|
|
1183
|
+
mpp,
|
|
1184
|
+
rateLimit: overQuotaExceptMpp(),
|
|
1185
|
+
},
|
|
1186
|
+
policy: {
|
|
1187
|
+
mpp: { session: { amount: '1' } },
|
|
1188
|
+
public: { rateLimit: { perMinute: 1 } },
|
|
1189
|
+
},
|
|
1190
|
+
})
|
|
1191
|
+
const client = createMppClient(app)
|
|
1192
|
+
|
|
1193
|
+
// The opening deposit covers one request; the second voucher overruns
|
|
1194
|
+
// it and the low-level client cannot top up, so the server re-challenges.
|
|
1195
|
+
const first = await client.fetch('http://tempo-api.test/protected')
|
|
1196
|
+
const second = await client.fetch('http://tempo-api.test/protected')
|
|
1197
|
+
|
|
1198
|
+
expect(first.status).toBe(200)
|
|
1199
|
+
expect(second.status).toBe(402)
|
|
1200
|
+
expect(second.headers.get('www-authenticate')?.startsWith('Payment ')).toBe(true)
|
|
1201
|
+
})
|
|
1202
|
+
|
|
1203
|
+
test('rejects a malformed payment credential with a fresh challenge', async () => {
|
|
1204
|
+
const mpp = createMppServer()
|
|
1205
|
+
const app = createApp({
|
|
1206
|
+
auth: {
|
|
1207
|
+
apiKey: { resolve: TestApp.resolver({ keys: [{ ...key, token }] }) },
|
|
1208
|
+
mpp,
|
|
1209
|
+
rateLimit: underQuota(),
|
|
1210
|
+
},
|
|
1211
|
+
policy: {
|
|
1212
|
+
mpp: { session: { amount: '1' } },
|
|
1213
|
+
public: { rateLimit: { perMinute: 1 } },
|
|
1214
|
+
},
|
|
1215
|
+
})
|
|
1216
|
+
const response = await app.request('/protected', {
|
|
1217
|
+
headers: { authorization: 'Payment not-a-credential' },
|
|
1218
|
+
})
|
|
1219
|
+
|
|
1220
|
+
expect(response.status).toBe(402)
|
|
1221
|
+
expect(response.headers.get('www-authenticate')?.startsWith('Payment ')).toBe(true)
|
|
1222
|
+
})
|
|
858
1223
|
})
|
|
859
1224
|
|
|
860
1225
|
describe('rateLimit', () => {
|
|
@@ -1306,7 +1671,7 @@ describe('require', () => {
|
|
|
1306
1671
|
},
|
|
1307
1672
|
policy: {
|
|
1308
1673
|
apiKey: { rateLimit: { perMinute: 1 }, scopes: ['data:read'] },
|
|
1309
|
-
mpp: {
|
|
1674
|
+
mpp: { session: { amount: '1', description: 'Protected data' } },
|
|
1310
1675
|
},
|
|
1311
1676
|
})
|
|
1312
1677
|
const response = await app.request('/protected', {
|
|
@@ -1324,29 +1689,74 @@ describe('require', () => {
|
|
|
1324
1689
|
})
|
|
1325
1690
|
|
|
1326
1691
|
describe('ensureProject', () => {
|
|
1327
|
-
test('authorizes
|
|
1692
|
+
test('authorizes an organization-attributed API key', async () => {
|
|
1328
1693
|
const db = TestApp.database()
|
|
1329
1694
|
const organization = await Organizations.create(db, { id: key.orgId, name: 'Test org' })
|
|
1330
1695
|
const project = await Projects.create(db, { name: 'Test project', orgId: organization.id })
|
|
1331
|
-
const response = await createProjectGuardApp(
|
|
1332
|
-
|
|
1333
|
-
|
|
1696
|
+
const response = await createProjectGuardApp(
|
|
1697
|
+
db,
|
|
1698
|
+
apiKeyPrincipal({ orgId: organization.id }),
|
|
1699
|
+
).request(`/v1/projects/${project.id}/resource`)
|
|
1700
|
+
const { orgId, projectId, ...body } = await response.json()
|
|
1334
1701
|
|
|
1335
1702
|
expect(response.status).toMatchInlineSnapshot(`200`)
|
|
1336
|
-
expect(
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1703
|
+
expect(orgId).toBe(organization.id)
|
|
1704
|
+
expect(projectId).toBe(project.id)
|
|
1705
|
+
expect(body).toMatchInlineSnapshot(`
|
|
1706
|
+
{
|
|
1707
|
+
"membership": null,
|
|
1708
|
+
}
|
|
1709
|
+
`)
|
|
1710
|
+
})
|
|
1711
|
+
|
|
1712
|
+
test('authorizes a project-attributed API key for its project', async () => {
|
|
1713
|
+
const db = TestApp.database()
|
|
1714
|
+
const organization = await Organizations.create(db, { id: key.orgId, name: 'Test org' })
|
|
1715
|
+
const project = await Projects.create(db, { name: 'Test project', orgId: organization.id })
|
|
1716
|
+
const response = await createProjectGuardApp(
|
|
1717
|
+
db,
|
|
1718
|
+
apiKeyPrincipal({ orgId: organization.id, projectId: project.id }),
|
|
1719
|
+
).request(`/v1/projects/${project.id}/resource`)
|
|
1720
|
+
const { orgId, projectId, ...body } = await response.json()
|
|
1721
|
+
|
|
1722
|
+
expect(response.status).toMatchInlineSnapshot(`200`)
|
|
1723
|
+
expect(orgId).toBe(organization.id)
|
|
1724
|
+
expect(projectId).toBe(project.id)
|
|
1725
|
+
expect(body).toMatchInlineSnapshot(`
|
|
1726
|
+
{
|
|
1727
|
+
"membership": null,
|
|
1728
|
+
}
|
|
1729
|
+
`)
|
|
1730
|
+
})
|
|
1731
|
+
|
|
1732
|
+
test('hides another project from a project-attributed API key', async () => {
|
|
1733
|
+
const db = TestApp.database()
|
|
1734
|
+
const organization = await Organizations.create(db, { id: key.orgId, name: 'Test org' })
|
|
1735
|
+
const project = await Projects.create(db, { name: 'Test project', orgId: organization.id })
|
|
1736
|
+
const response = await createProjectGuardApp(
|
|
1737
|
+
db,
|
|
1738
|
+
apiKeyPrincipal({ orgId: organization.id, projectId: 'prj_other' }),
|
|
1739
|
+
).request(`/v1/projects/${project.id}/resource`)
|
|
1740
|
+
|
|
1741
|
+
expect(response.status).toMatchInlineSnapshot(`404`)
|
|
1742
|
+
expect(await response.json()).toMatchInlineSnapshot(`
|
|
1743
|
+
{
|
|
1744
|
+
"error": {
|
|
1745
|
+
"code": "project_not_found",
|
|
1746
|
+
"message": "Project not found",
|
|
1747
|
+
},
|
|
1748
|
+
}
|
|
1749
|
+
`)
|
|
1341
1750
|
})
|
|
1342
1751
|
|
|
1343
1752
|
test('hides a flat project from an API key in another organization', async () => {
|
|
1344
1753
|
const db = TestApp.database()
|
|
1345
1754
|
const organization = await Organizations.create(db, { id: key.orgId, name: 'Test org' })
|
|
1346
1755
|
const project = await Projects.create(db, { name: 'Test project', orgId: organization.id })
|
|
1347
|
-
const response = await createProjectGuardApp(
|
|
1348
|
-
|
|
1349
|
-
|
|
1756
|
+
const response = await createProjectGuardApp(
|
|
1757
|
+
db,
|
|
1758
|
+
apiKeyPrincipal({ orgId: 'org_foreign' }),
|
|
1759
|
+
).request(`/v1/projects/${project.id}/resource`)
|
|
1350
1760
|
|
|
1351
1761
|
expect(response.status).toMatchInlineSnapshot(`404`)
|
|
1352
1762
|
expect(await response.json()).toMatchInlineSnapshot(`
|
|
@@ -1370,14 +1780,16 @@ describe('ensureProject', () => {
|
|
|
1370
1780
|
const response = await createProjectGuardApp(db, sessionPrincipal(user.id)).request(
|
|
1371
1781
|
`/v1/projects/${project.id}/resource`,
|
|
1372
1782
|
)
|
|
1373
|
-
const body =
|
|
1783
|
+
const { orgId, projectId, ...body } = await response.json()
|
|
1374
1784
|
|
|
1375
1785
|
expect(response.status).toMatchInlineSnapshot(`200`)
|
|
1376
|
-
expect(
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1786
|
+
expect(orgId).toBe(organization.id)
|
|
1787
|
+
expect(projectId).toBe(project.id)
|
|
1788
|
+
expect(body).toMatchInlineSnapshot(`
|
|
1789
|
+
{
|
|
1790
|
+
"membership": "member",
|
|
1791
|
+
}
|
|
1792
|
+
`)
|
|
1381
1793
|
})
|
|
1382
1794
|
|
|
1383
1795
|
test('preserves nested organization role authorization', async () => {
|
|
@@ -1402,6 +1814,26 @@ describe('ensureProject', () => {
|
|
|
1402
1814
|
}
|
|
1403
1815
|
`)
|
|
1404
1816
|
})
|
|
1817
|
+
|
|
1818
|
+
test('hides another nested project from a project-attributed API key', async () => {
|
|
1819
|
+
const db = TestApp.database()
|
|
1820
|
+
const organization = await Organizations.create(db, { id: key.orgId, name: 'Test org' })
|
|
1821
|
+
const project = await Projects.create(db, { name: 'Test project', orgId: organization.id })
|
|
1822
|
+
const response = await createNestedProjectGuardApp(
|
|
1823
|
+
db,
|
|
1824
|
+
apiKeyPrincipal({ orgId: organization.id, projectId: 'prj_other' }),
|
|
1825
|
+
).request(`/v1/orgs/${organization.id}/projects/${project.id}/resource`)
|
|
1826
|
+
|
|
1827
|
+
expect(response.status).toMatchInlineSnapshot(`404`)
|
|
1828
|
+
expect(await response.json()).toMatchInlineSnapshot(`
|
|
1829
|
+
{
|
|
1830
|
+
"error": {
|
|
1831
|
+
"code": "project_not_found",
|
|
1832
|
+
"message": "Project not found",
|
|
1833
|
+
},
|
|
1834
|
+
}
|
|
1835
|
+
`)
|
|
1836
|
+
})
|
|
1405
1837
|
})
|
|
1406
1838
|
|
|
1407
1839
|
function createScopedApp(options: {
|
|
@@ -1467,13 +1899,21 @@ function projectGuardHandler(c: Context<GuardEnvironment>) {
|
|
|
1467
1899
|
})
|
|
1468
1900
|
}
|
|
1469
1901
|
|
|
1470
|
-
function apiKeyPrincipal(
|
|
1471
|
-
|
|
1902
|
+
function apiKeyPrincipal(options: {
|
|
1903
|
+
orgId: string
|
|
1904
|
+
projectId?: string | undefined
|
|
1905
|
+
}): Auth.Principal {
|
|
1906
|
+
const apiKey = {
|
|
1907
|
+
...key,
|
|
1908
|
+
orgId: options.orgId,
|
|
1909
|
+
...(options.projectId === undefined ? {} : { projectId: options.projectId }),
|
|
1910
|
+
}
|
|
1472
1911
|
return {
|
|
1473
1912
|
apiKey,
|
|
1474
1913
|
environment: apiKey.environment,
|
|
1475
1914
|
id: apiKey.id,
|
|
1476
|
-
orgId,
|
|
1915
|
+
orgId: options.orgId,
|
|
1916
|
+
...(options.projectId === undefined ? {} : { projectId: options.projectId }),
|
|
1477
1917
|
type: 'api_key',
|
|
1478
1918
|
}
|
|
1479
1919
|
}
|
|
@@ -1516,15 +1956,18 @@ function createApp(options: Options = {}) {
|
|
|
1516
1956
|
return app
|
|
1517
1957
|
}
|
|
1518
1958
|
|
|
1519
|
-
function createMppServer() {
|
|
1959
|
+
function createMppServer(options: createMppServer.Options = {}) {
|
|
1520
1960
|
return Mppx.create({
|
|
1521
1961
|
methods: [
|
|
1522
|
-
tempo.
|
|
1962
|
+
tempo.session({
|
|
1963
|
+
bootstrap: false,
|
|
1523
1964
|
chainId: Tempo.chain.id,
|
|
1524
1965
|
currency: Tempo.currency,
|
|
1525
1966
|
decimals: 6,
|
|
1526
1967
|
getClient: () => Tempo.client,
|
|
1527
1968
|
recipient: Tempo.accounts[2].address,
|
|
1969
|
+
...options,
|
|
1970
|
+
unitType: 'request',
|
|
1528
1971
|
}),
|
|
1529
1972
|
],
|
|
1530
1973
|
realm: 'tempo-api-test',
|
|
@@ -1532,6 +1975,37 @@ function createMppServer() {
|
|
|
1532
1975
|
})
|
|
1533
1976
|
}
|
|
1534
1977
|
|
|
1978
|
+
declare namespace createMppServer {
|
|
1979
|
+
type Options = Pick<
|
|
1980
|
+
NonNullable<Parameters<typeof tempo.session>[0]>,
|
|
1981
|
+
'chainId' | 'getClient' | 'store'
|
|
1982
|
+
>
|
|
1983
|
+
}
|
|
1984
|
+
|
|
1985
|
+
function createMppHandler(
|
|
1986
|
+
options: createMppHandler.Options = {},
|
|
1987
|
+
): NonNullable<Auth.Context['mpp']> {
|
|
1988
|
+
return {
|
|
1989
|
+
...(options.rateLimit ? { rateLimit: options.rateLimit } : {}),
|
|
1990
|
+
session: () => async (_c, next) => {
|
|
1991
|
+
options.onPayment?.()
|
|
1992
|
+
await next()
|
|
1993
|
+
},
|
|
1994
|
+
}
|
|
1995
|
+
}
|
|
1996
|
+
|
|
1997
|
+
declare namespace createMppHandler {
|
|
1998
|
+
type Options = {
|
|
1999
|
+
onPayment?: (() => void) | undefined
|
|
2000
|
+
rateLimit?: RateLimit.Limit | undefined
|
|
2001
|
+
}
|
|
2002
|
+
}
|
|
2003
|
+
|
|
2004
|
+
/** Reads the session receipt attached to a paid response. */
|
|
2005
|
+
function sessionReceipt(response: Response) {
|
|
2006
|
+
return Receipt.fromResponse(response) as Session.Precompile.Protocol.SessionReceipt
|
|
2007
|
+
}
|
|
2008
|
+
|
|
1535
2009
|
function overQuota(): RateLimit.Store {
|
|
1536
2010
|
return {
|
|
1537
2011
|
async consume(options) {
|
|
@@ -1545,12 +2019,29 @@ function overQuota(): RateLimit.Store {
|
|
|
1545
2019
|
}
|
|
1546
2020
|
}
|
|
1547
2021
|
|
|
1548
|
-
function
|
|
1549
|
-
let consumed = false
|
|
2022
|
+
function underQuota(): RateLimit.Store {
|
|
1550
2023
|
return {
|
|
1551
2024
|
async consume(options) {
|
|
1552
|
-
|
|
1553
|
-
|
|
2025
|
+
return {
|
|
2026
|
+
allowed: true,
|
|
2027
|
+
limit: options.limit.perMinute,
|
|
2028
|
+
remaining: Math.max(options.limit.perMinute - 1, 0),
|
|
2029
|
+
reset: Math.ceil(Date.now() / 1_000) + 60,
|
|
2030
|
+
}
|
|
2031
|
+
},
|
|
2032
|
+
}
|
|
2033
|
+
}
|
|
2034
|
+
|
|
2035
|
+
function overQuotaExceptMpp(): RateLimit.Store {
|
|
2036
|
+
return {
|
|
2037
|
+
async consume(options) {
|
|
2038
|
+
if (options.key.startsWith('mpp:'))
|
|
2039
|
+
return {
|
|
2040
|
+
allowed: true,
|
|
2041
|
+
limit: options.limit.perMinute,
|
|
2042
|
+
remaining: Math.max(options.limit.perMinute - 1, 0),
|
|
2043
|
+
reset: Math.ceil(Date.now() / 1_000) + 60,
|
|
2044
|
+
}
|
|
1554
2045
|
return {
|
|
1555
2046
|
allowed: false,
|
|
1556
2047
|
limit: options.limit.perMinute,
|