Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions src/proxy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});
Expand All @@ -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' };
Expand Down
9 changes: 7 additions & 2 deletions src/proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 <walletId>:<signature>:<timestamp>` (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;
Expand Down
Loading