Skip to content

Commit 217dd17

Browse files
authored
fix: pass through non-json responses without modifying the body (#8)
* fix: pass through non-json responses without modifying the body * fix: normalize content type to lowercase before checking
1 parent 3681a24 commit 217dd17

File tree

3 files changed

+54
-6
lines changed

3 files changed

+54
-6
lines changed

src/session.spec.ts

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,41 @@ describe('session', () => {
323323
});
324324
});
325325

326+
it('should pass through non-JSON responses with just the cookie added', async () => {
327+
// Set up a custom loader that returns HTML
328+
const htmlContent = '<html><body><h1>Hello World!</h1></body></html>';
329+
const customLoader = jest.fn().mockReturnValue(
330+
new Response(htmlContent, {
331+
headers: {
332+
'Content-Type': 'text/html',
333+
'X-Custom-Header': 'test-value',
334+
},
335+
}),
336+
);
337+
338+
// Call authkitLoader with the HTML-returning loader
339+
const result = await authkitLoader(createLoaderArgs(createMockRequest()), customLoader);
340+
341+
// Verify we got back a Response, not a DataWithResponseInit
342+
assertIsResponse(result);
343+
344+
// Check that the response body wasn't modified
345+
const resultText = await result.clone().text();
346+
expect(resultText).toBe(htmlContent);
347+
348+
// Check that original headers were preserved
349+
expect(result.headers.get('Content-Type')).toBe('text/html');
350+
expect(result.headers.get('X-Custom-Header')).toBe('test-value');
351+
352+
// Check that session cookie was added
353+
expect(result.headers.get('Set-Cookie')).toBe('session-cookie');
354+
355+
// Verify that the JSON parsing method was not called
356+
const jsonSpy = jest.spyOn(Response.prototype, 'json');
357+
expect(jsonSpy).not.toHaveBeenCalled();
358+
jsonSpy.mockRestore();
359+
});
360+
326361
it('should return authorized data with session claims', async () => {
327362
const { data } = await authkitLoader(createLoaderArgs(createMockRequest()));
328363

@@ -371,7 +406,7 @@ describe('session', () => {
371406
const { data, init } = await authkitLoader(createLoaderArgs(createMockRequest()), customLoader);
372407

373408
expect(getHeaderValue(init?.headers, 'Custom-Header')).toBe('test-header');
374-
expect(getHeaderValue(init?.headers, 'Content-Type')).toBe('application/json; charset=utf-8');
409+
expect(getHeaderValue(init?.headers, 'Content-Type')).toBe('application/json');
375410

376411
expect(data).toEqual(
377412
expect.objectContaining({

src/session.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { sealData, unsealData } from 'iron-session';
1414
import { createRemoteJWKSet, decodeJwt, jwtVerify } from 'jose';
1515
import { getConfig } from './config.js';
1616
import { configureSessionStorage, getSessionStorage } from './sessionStorage.js';
17-
import { isResponse, isRedirect } from './utils.js';
17+
import { isResponse, isRedirect, isJsonResponse } from './utils.js';
1818

1919
// must be a type since this is a subtype of response
2020
// interfaces must conform to the types they extend
@@ -309,15 +309,17 @@ async function handleAuthLoader(
309309
}
310310

311311
const newResponse = new Response(loaderResult.body, loaderResult);
312-
const responseData = await newResponse.json();
313312

314-
// Set the content type in case the user returned a Response instead of the
315-
// json helper method
316-
newResponse.headers.set('Content-Type', 'application/json; charset=utf-8');
317313
if (session) {
318314
newResponse.headers.append('Set-Cookie', session.headers['Set-Cookie']);
319315
}
320316

317+
if (!isJsonResponse(newResponse)) {
318+
return newResponse;
319+
}
320+
321+
const responseData = await newResponse.json();
322+
321323
return data({ ...responseData, ...auth }, newResponse);
322324
}
323325

src/utils.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,17 @@ export function isResponse(response: unknown): response is Response {
3737
return response instanceof Response;
3838
}
3939

40+
/**
41+
* Returns true if the response is a JSON response.
42+
* This is determined by checking if the Content-Type header includes 'application/json'.
43+
* @param res - The response to check.
44+
* @returns True if the response is a JSON response.
45+
*/
46+
export function isJsonResponse(res: Response): boolean {
47+
const contentType = res.headers.get('Content-Type')?.toLowerCase();
48+
return !!contentType?.includes('application/json');
49+
}
50+
4051
/**
4152
* Checks if the data is a DataWithResponseInit object.
4253
* @param data - The data to check.

0 commit comments

Comments
 (0)