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
37 changes: 36 additions & 1 deletion src/session.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,41 @@ describe('session', () => {
});
});

it('should pass through non-JSON responses with just the cookie added', async () => {
// Set up a custom loader that returns HTML
const htmlContent = '<html><body><h1>Hello World!</h1></body></html>';
const customLoader = jest.fn().mockReturnValue(
new Response(htmlContent, {
headers: {
'Content-Type': 'text/html',
'X-Custom-Header': 'test-value',
},
}),
);

// Call authkitLoader with the HTML-returning loader
const result = await authkitLoader(createLoaderArgs(createMockRequest()), customLoader);

// Verify we got back a Response, not a DataWithResponseInit
assertIsResponse(result);

// Check that the response body wasn't modified
const resultText = await result.clone().text();
expect(resultText).toBe(htmlContent);

// Check that original headers were preserved
expect(result.headers.get('Content-Type')).toBe('text/html');
expect(result.headers.get('X-Custom-Header')).toBe('test-value');

// Check that session cookie was added
expect(result.headers.get('Set-Cookie')).toBe('session-cookie');

// Verify that the JSON parsing method was not called
const jsonSpy = jest.spyOn(Response.prototype, 'json');
expect(jsonSpy).not.toHaveBeenCalled();
jsonSpy.mockRestore();
});

it('should return authorized data with session claims', async () => {
const { data } = await authkitLoader(createLoaderArgs(createMockRequest()));

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

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

expect(data).toEqual(
expect.objectContaining({
Expand Down
12 changes: 7 additions & 5 deletions src/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { sealData, unsealData } from 'iron-session';
import { createRemoteJWKSet, decodeJwt, jwtVerify } from 'jose';
import { getConfig } from './config.js';
import { configureSessionStorage, getSessionStorage } from './sessionStorage.js';
import { isResponse, isRedirect } from './utils.js';
import { isResponse, isRedirect, isJsonResponse } from './utils.js';

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

const newResponse = new Response(loaderResult.body, loaderResult);
const responseData = await newResponse.json();

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

if (!isJsonResponse(newResponse)) {
return newResponse;
}

const responseData = await newResponse.json();

return data({ ...responseData, ...auth }, newResponse);
}

Expand Down
11 changes: 11 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,17 @@ export function isResponse(response: unknown): response is Response {
return response instanceof Response;
}

/**
* Returns true if the response is a JSON response.
* This is determined by checking if the Content-Type header includes 'application/json'.
* @param res - The response to check.
* @returns True if the response is a JSON response.
*/
export function isJsonResponse(res: Response): boolean {
const contentType = res.headers.get('Content-Type')?.toLowerCase();
return !!contentType?.includes('application/json');
}

/**
* Checks if the data is a DataWithResponseInit object.
* @param data - The data to check.
Expand Down