diff --git a/src/__tests__/usePolicies.test.tsx b/src/__tests__/usePolicies.test.tsx index 52404db..c29d163 100644 --- a/src/__tests__/usePolicies.test.tsx +++ b/src/__tests__/usePolicies.test.tsx @@ -58,6 +58,18 @@ describe('usePolicies', () => { expect(hook.current.error).toBeNull(); }); + // #525: the wallet must not leak into the /policies query string -- the + // backend resolves it from the JWT, so the API call takes no wallet arg. + it('does not pass the wallet address to fetchUserPolicies', async () => { + fetchUserPolicies.mockResolvedValue([]); + + renderHook(() => usePolicies('GWALLET')); + await flushMicrotasks(); + + expect(fetchUserPolicies).toHaveBeenCalledTimes(1); + expect(fetchUserPolicies).toHaveBeenCalledWith(); + }); + it('surfaces an error message when the fetch fails', async () => { fetchUserPolicies.mockRejectedValue(new Error('failed to reach api')); @@ -88,13 +100,17 @@ describe('usePolicies', () => { }); it('discards a stale response from a previous wallet after the wallet address changes', async () => { + // fetchUserPolicies takes no wallet arg (#525 -- the backend reads it from + // the JWT), so key pending requests by the wallet active at call time. const pending: Record void> = {}; + let wallet = 'GWALLET_A'; fetchUserPolicies.mockImplementation( - (wallet: string) => - new Promise((resolve) => { pending[wallet] = resolve; }), + () => { + const forWallet = wallet; + return new Promise((resolve) => { pending[forWallet] = resolve; }); + }, ); - let wallet = 'GWALLET_A'; const hook = renderHook(() => usePolicies(wallet)); await flushMicrotasks(); expect(pending['GWALLET_A']).toBeDefined(); diff --git a/src/hooks/usePolicies.ts b/src/hooks/usePolicies.ts index beccb22..cba8356 100644 --- a/src/hooks/usePolicies.ts +++ b/src/hooks/usePolicies.ts @@ -23,7 +23,7 @@ export function usePolicies(walletAddress: string | null) { } setError(null); try { - const data = await fetchUserPolicies(walletAddress); + const data = await fetchUserPolicies(); if (signal.aborted) return; setPolicies(data); } catch (err) { diff --git a/src/lib/api.ts b/src/lib/api.ts index d2be109..e15429e 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -147,8 +147,14 @@ export function fetchProduct(id: string): Promise { // ── Policies ────────────────────────────────────────────────────────────────── -export function fetchUserPolicies(wallet: string): Promise { - return get('/policies', { params: { wallet } }); +/** + * Policies for the authenticated wallet. The backend resolves the wallet from + * the JWT (backend #345), so it is deliberately NOT sent as a `?wallet=` query + * param -- query strings end up in server/proxy access logs and browser + * history, leaking the user's address in plaintext (#525). + */ +export function fetchUserPolicies(): Promise { + return get('/policies'); } export function fetchPolicy(id: string): Promise {