fix: respect returnHeaders and returnStatus options in auth.api endpoints

Fixes #6520

This change ensures returnHeaders and returnStatus options are properly
respected in auth.api endpoints, fixing a bug where responses were always
wrapped regardless of these options.

Changes:
- Set returnHeaders and returnStatus defaults to false instead of true
- Fix type assertion to allow undefined headers
- Add null safety check before accessing result.headers
- Change null to undefined for consistency with optional types
- Add comprehensive test cases for all return format combinations
This commit is contained in:
yoshifumi-kondo
2025-12-05 19:14:06 +09:00
parent 5436b95ada
commit ae4e013ce5
2 changed files with 246 additions and 26 deletions

View File

@@ -369,6 +369,214 @@ describe("session", async () => {
},
);
});
it("should return session data directly without wrapping when returnHeaders is not set", async () => {
const context = await auth.$context;
await runWithEndpointContext(
{
context,
} as unknown as GenericEndpointContext,
async () => {
const signInRes = await auth.api.signInEmail({
body: {
email: testUser.email,
password: testUser.password,
},
returnHeaders: true,
});
const signInHeaders = new Headers();
signInHeaders.set("cookie", signInRes.headers.getSetCookie()[0]!);
const sessionRes = await auth.api.getSession({
headers: signInHeaders,
});
// Should return session data directly, not wrapped in { response: ... }
expect(sessionRes).toHaveProperty("user");
expect(sessionRes).toHaveProperty("session");
// @ts-expect-error: response property should not exist
expect(sessionRes.response).toBeUndefined();
// @ts-expect-error: headers property should not exist
expect(sessionRes.headers).toBeUndefined();
// @ts-expect-error: status property should not exist
expect(sessionRes.status).toBeUndefined();
expect(sessionRes?.user).toBeDefined();
expect(sessionRes?.session).toBeDefined();
expect(sessionRes?.user.email).toBe(testUser.email);
},
);
});
it("should return status without headers when only returnStatus is true", async () => {
const context = await auth.$context;
await runWithEndpointContext(
{
context,
} as unknown as GenericEndpointContext,
async () => {
const signInRes = await auth.api.signInEmail({
body: {
email: testUser.email,
password: testUser.password,
},
returnHeaders: true,
});
const signInHeaders = new Headers();
signInHeaders.set("cookie", signInRes.headers.getSetCookie()[0]!);
const sessionRes = await auth.api.getSession({
headers: signInHeaders,
returnStatus: true,
});
expect(sessionRes).toHaveProperty("response");
expect(sessionRes).toHaveProperty("status");
expect(sessionRes.response).toHaveProperty("user");
expect(sessionRes.response).toHaveProperty("session");
// @ts-expect-error: headers property should not exist
expect(sessionRes.headers).toBeUndefined();
},
);
});
it("should respect explicit returnHeaders: false", async () => {
const context = await auth.$context;
await runWithEndpointContext(
{
context,
} as unknown as GenericEndpointContext,
async () => {
const signInRes = await auth.api.signInEmail({
body: {
email: testUser.email,
password: testUser.password,
},
returnHeaders: true,
});
const signInHeaders = new Headers();
signInHeaders.set("cookie", signInRes.headers.getSetCookie()[0]!);
const sessionRes = await auth.api.getSession({
headers: signInHeaders,
returnHeaders: false,
returnStatus: false,
});
expect(sessionRes).toHaveProperty("user");
expect(sessionRes).toHaveProperty("session");
// @ts-expect-error: response property should not exist
expect(sessionRes.response).toBeUndefined();
},
);
});
it("should return both headers and status when both options are true", async () => {
const context = await auth.$context;
await runWithEndpointContext(
{
context,
} as unknown as GenericEndpointContext,
async () => {
const signInRes = await auth.api.signInEmail({
body: {
email: testUser.email,
password: testUser.password,
},
returnHeaders: true,
returnStatus: true,
});
expect(signInRes).toHaveProperty("headers");
expect(signInRes).toHaveProperty("response");
expect(signInRes).toHaveProperty("status");
expect(signInRes.response).toHaveProperty("user");
expect(signInRes.response).toHaveProperty("session");
},
);
});
it("should use APIError statusCode when asResponse is true", async () => {
const context = await auth.$context;
await runWithEndpointContext(
{
context,
} as unknown as GenericEndpointContext,
async () => {
const errorRes = await auth.api.signInEmail(
{
body: {
email: "wrong@example.com",
password: "wrongpassword",
},
asResponse: true,
},
{
onError: (context) => {
expect(context.response).toBeInstanceOf(Response);
expect(context.response.status).toBe(401);
},
},
);
expect(errorRes.error).toBeDefined();
},
);
});
it("should include APIError statusCode when returnStatus is true", async () => {
const context = await auth.$context;
await runWithEndpointContext(
{
context,
} as unknown as GenericEndpointContext,
async () => {
const errorRes = await auth.api.signInEmail({
body: {
email: "wrong@example.com",
password: "wrongpassword",
},
returnStatus: true,
});
expect(errorRes.error).toBeDefined();
expect(errorRes.error?.status).toBe(401);
},
);
});
it("should handle different APIError status codes correctly", async () => {
const context = await auth.$context;
await runWithEndpointContext(
{
context,
} as unknown as GenericEndpointContext,
async () => {
const unauthorizedRes = await auth.api.signInEmail({
body: {
email: "wrong@example.com",
password: "wrongpassword",
},
returnStatus: true,
returnHeaders: true,
});
expect(unauthorizedRes.error?.status).toBe(401);
const badRequestRes = await auth.api.signInEmail({
body: {
email: "invalid-email",
password: "password",
},
returnStatus: true,
returnHeaders: true,
});
expect(badRequestRes.error?.status).toBe(400);
},
);
});
});
describe("session storage", async () => {

View File

@@ -106,8 +106,8 @@ export function toAuthEndpoints<
}
internalContext.asResponse = false;
internalContext.returnHeaders = true;
internalContext.returnStatus = true;
internalContext.returnHeaders = context?.returnHeaders ?? false;
internalContext.returnStatus = context?.returnStatus ?? false;
const result = (await runWithEndpointContext(internalContext, () =>
(endpoint as any)(internalContext as any),
).catch((e: any) => {
@@ -119,12 +119,12 @@ export function toAuthEndpoints<
return {
response: e,
status: e.statusCode,
headers: e.headers ? new Headers(e.headers) : null,
headers: e.headers ? new Headers(e.headers) : undefined,
};
}
throw e;
})) as {
headers: Headers;
headers: Headers | undefined;
response: any;
status: number;
};
@@ -134,46 +134,58 @@ export function toAuthEndpoints<
return result;
}
internalContext.context.returned = result.response;
internalContext.context.responseHeaders = result.headers;
if (result) {
internalContext.context.returned = result.response;
if (result.headers) {
internalContext.context.responseHeaders = result.headers;
}
}
const after = await runAfterHooks(internalContext, afterHooks);
// Use result or fallback to after response
let responseData = result?.response;
let headers = result?.headers;
let status = result?.status;
if (after.response) {
result.response = after.response;
responseData = after.response;
}
if (
result.response instanceof APIError &&
responseData instanceof APIError &&
shouldPublishLog(authContext.logger.level, "debug")
) {
// inherit stack from errorStack if debug mode is enabled
result.response.stack = result.response.errorStack;
responseData.stack = responseData.errorStack;
}
if (result.response instanceof APIError && !context?.asResponse) {
throw result.response;
if (responseData instanceof APIError && !context?.asResponse) {
throw responseData;
}
const response = context?.asResponse
? toResponse(result.response, {
headers: result.headers,
status: result.status,
? toResponse(responseData, {
headers: headers,
status:
responseData instanceof APIError
? responseData.statusCode
: (status ?? 200),
})
: context?.returnHeaders
? context?.returnStatus
? {
headers: result.headers,
response: result.response,
status: result.status,
headers: headers,
response: responseData,
status: status ?? 200,
}
: {
headers: result.headers,
response: result.response,
headers: headers,
response: responseData,
}
: context?.returnStatus
? { response: result.response, status: result.status }
: result.response;
? { response: responseData, status: status ?? 200 }
: responseData;
return response;
});
};
@@ -257,15 +269,15 @@ async function runAfterHooks(
}
return {
response: e,
headers: e.headers ? new Headers(e.headers) : null,
headers: e.headers ? new Headers(e.headers) : undefined,
};
}
throw e;
})) as {
response: any;
headers: Headers;
};
if (result.headers) {
headers?: Headers;
} | void;
if (result && result.headers) {
result.headers.forEach((value, key) => {
if (!context.context.responseHeaders) {
context.context.responseHeaders = new Headers({
@@ -280,7 +292,7 @@ async function runAfterHooks(
}
});
}
if (result.response) {
if (result && result.response) {
context.context.returned = result.response;
}
}