diff --git a/src/proxy.test.ts b/src/proxy.test.ts index 46b46e5..8e88a83 100644 --- a/src/proxy.test.ts +++ b/src/proxy.test.ts @@ -52,6 +52,19 @@ describe('presentedCredential', () => { expect(presentedCredential(headersOf({ 'x-api-key': 'sk_live_xyz' }))).toBe('sk_live_xyz'); }); + it('reads the wallet extension\'s Wallet scheme, not just Bearer', () => { + // packages/extension/src/core/api.ts signs with this exact shape. + expect( + presentedCredential(headersOf({ authorization: 'Wallet wid-1:sig-abc:1234567890' })) + ).toBe('wid-1:sig-abc:1234567890'); + }); + + it('distinguishes two wallets using the same scheme', () => { + const a = presentedCredential(headersOf({ authorization: 'Wallet wid-1:sig:1' })); + const b = presentedCredential(headersOf({ authorization: 'Wallet wid-2:sig:1' })); + expect(a).not.toBe(b); + }); + it('is null when no credential is presented', () => { expect(presentedCredential(headersOf({}))).toBeNull(); }); @@ -77,6 +90,15 @@ describe('proxy rate limiting', () => { expect(allowed).toBe(200); }); + // The extension uses the `Wallet` scheme; a batch spends two API calls per + // payment, so treating it as anonymous capped a payout at ~30 payments. + it('gives the wallet extension the credentialed budget', () => { + const allowed = countUntilLimited('/api/web-wallet/w1/broadcast', '10.1.0.6', 200, { + authorization: 'Wallet wid-1:sig-abc:1234567890', + }); + expect(allowed).toBe(200); + }); + it('budgets each API key separately from the same host', () => { const headersA = { authorization: 'Bearer sk_live_a' }; const headersB = { authorization: 'Bearer sk_live_b' }; diff --git a/src/proxy.ts b/src/proxy.ts index 33d9fdf..ee492da 100644 --- a/src/proxy.ts +++ b/src/proxy.ts @@ -118,8 +118,13 @@ export function presentedCredential( ): string | null { const authorization = headers.get('authorization'); if (authorization) { - const match = /^bearer\s+(\S+)/i.exec(authorization.trim()); - if (match) return match[1]; + // Any auth scheme, not just Bearer. The wallet extension signs with + // `Authorization: Wallet ::` (see + // packages/extension/src/core/api.ts), and matching Bearer alone dropped it + // into the anonymous per-IP bucket — which a bulk payout exhausts in + // seconds, since every payment costs a prepare-tx plus a broadcast. + const match = /^(\S+)\s+(\S+)/.exec(authorization.trim()); + if (match) return match[2]; } const apiKey = headers.get('x-api-key')?.trim(); return apiKey ? apiKey : null;