diff --git a/packages/better-auth/src/api/routes/session-api.test.ts b/packages/better-auth/src/api/routes/session-api.test.ts index c4008e1887..555fd78d56 100644 --- a/packages/better-auth/src/api/routes/session-api.test.ts +++ b/packages/better-auth/src/api/routes/session-api.test.ts @@ -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 () => { diff --git a/packages/better-auth/src/api/to-auth-endpoints.ts b/packages/better-auth/src/api/to-auth-endpoints.ts index 0aee97f8a7..3509386355 100644 --- a/packages/better-auth/src/api/to-auth-endpoints.ts +++ b/packages/better-auth/src/api/to-auth-endpoints.ts @@ -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; } }