diff --git a/frontend/src/api/http-init.ts b/frontend/src/api/http-init.ts index 2ead50f9f..2261d0d90 100644 --- a/frontend/src/api/http-init.ts +++ b/frontend/src/api/http-init.ts @@ -152,8 +152,11 @@ export async function httpRequest( if (res.status === 403 && !SAFE_METHODS.has(method.toUpperCase())) { csrfToken = null; - const fresh = await ensureCsrfToken(); - if (fresh) res = await performFetch(method, url, data, options, fresh); + const fresh = await fetchCsrfToken(); + if (fresh) { + csrfToken = fresh; + res = await performFetch(method, url, data, options, fresh); + } } if (res.status === 401) { diff --git a/frontend/src/test/http-init-msw.test.ts b/frontend/src/test/http-init-msw.test.ts index a70c7f00e..ab12bd555 100644 --- a/frontend/src/test/http-init-msw.test.ts +++ b/frontend/src/test/http-init-msw.test.ts @@ -41,6 +41,35 @@ describe('httpRequest against the MSW-mocked network', () => { expect(res.data).toEqual({ success: true, obj: 'saved' }); }); + it('on a 403 refetches a fresh token from the server even when a stale meta tag is present', async () => { + const STALE = 'stale-meta-token'; + const FRESH = 'fresh-server-token'; + vi.stubGlobal('document', { + querySelector: (selector: string) => + selector === 'meta[name="csrf-token"]' ? { getAttribute: () => STALE } : null, + }); + setupHttp(); + + let posts = 0; + const sentTokens: (string | null)[] = []; + server.use( + http.get(`${ORIGIN}/csrf-token`, () => HttpResponse.json({ success: true, obj: FRESH })), + http.post(`${ORIGIN}/panel/api/test`, ({ request }) => { + posts += 1; + const token = request.headers.get('X-CSRF-Token'); + sentTokens.push(token); + if (token !== FRESH) return new HttpResponse(null, { status: 403 }); + return HttpResponse.json({ success: true, obj: 'saved' }); + }), + ); + + const res = await httpRequest('POST', '/panel/api/test', { hello: 'world' }); + + expect(sentTokens).toEqual([STALE, FRESH]); + expect(posts).toBe(2); + expect(res.status).toBe(200); + }); + it('resolves a safe GET without requesting a CSRF token', async () => { let csrfHits = 0; server.use(