From a048f804ff447db43cb2090e276d17cc10ca3c13 Mon Sep 17 00:00:00 2001 From: Ahmed Abdellahi Abdat Date: Thu, 16 Oct 2025 00:10:46 +0200 Subject: [PATCH] fix: respect additionalFields returned config for user data when setting cookie cache (#5327) Co-authored-by: Bereket Engida <86073083+Bekacru@users.noreply.github.com> Co-authored-by: Bereket Engida --- .../better-auth/src/cookies/cookies.test.ts | 260 ++++++++++++++++++ packages/better-auth/src/cookies/index.ts | 5 +- 2 files changed, 264 insertions(+), 1 deletion(-) diff --git a/packages/better-auth/src/cookies/cookies.test.ts b/packages/better-auth/src/cookies/cookies.test.ts index 6349438c9d..31fed26087 100644 --- a/packages/better-auth/src/cookies/cookies.test.ts +++ b/packages/better-auth/src/cookies/cookies.test.ts @@ -433,3 +433,263 @@ describe("getSessionCookie", async () => { expect(result?.user).toBeDefined(); }); }); + +describe("Cookie Cache Field Filtering", () => { + it("should exclude user fields with returned: false from cookie cache", async () => { + const { client, testUser, cookieSetter } = await getTestInstance({ + secret: "better-auth.secret", + user: { + additionalFields: { + internalNote: { + type: "string", + defaultValue: "", + returned: false, + }, + }, + }, + session: { + cookieCache: { + enabled: true, + }, + }, + }); + + const headers = new Headers(); + + await client.signIn.email( + { + email: testUser.email, + password: testUser.password, + }, + { + onSuccess: cookieSetter(headers), + }, + ); + + const request = new Request("https://example.com/api/auth/session", { + headers, + }); + + const cache = await getCookieCache(request, { + secret: "better-auth.secret", + }); + + expect(cache).not.toBeNull(); + expect(cache?.user?.email).toEqual(testUser.email); + expect(cache?.user?.internalNote).toBeUndefined(); + }); + + it("should correctly filter multiple user fields based on returned config", async () => { + const { client, testUser, cookieSetter } = await getTestInstance({ + secret: "better-auth.secret", + user: { + additionalFields: { + publicBio: { + type: "string", + defaultValue: "default-bio", + returned: true, + }, + internalNotes: { + type: "string", + defaultValue: "internal-notes", + returned: false, + }, + preferences: { + type: "string", + defaultValue: "default-prefs", + returned: true, + }, + adminFlags: { + type: "string", + defaultValue: "admin-flags", + returned: false, + }, + }, + }, + session: { + cookieCache: { + enabled: true, + }, + }, + }); + + const headers = new Headers(); + await client.signIn.email( + { + email: testUser.email, + password: testUser.password, + }, + { + onSuccess: cookieSetter(headers), + }, + ); + + const request = new Request("https://example.com/api/auth/session", { + headers, + }); + + const cache = await getCookieCache(request, { + secret: "better-auth.secret", + }); + + expect(cache).not.toBeNull(); + // Fields with returned: true should be included + expect(cache?.user?.publicBio).toBeDefined(); + expect(cache?.user?.preferences).toBeDefined(); + // Fields with returned: false should be excluded + expect(cache?.user?.internalNotes).toBeUndefined(); + expect(cache?.user?.adminFlags).toBeUndefined(); + }); + + it("should reduce cookie size when large fields are excluded", async () => { + const largeString = "x".repeat(2000); + const { client, testUser, cookieSetter } = await getTestInstance({ + secret: "better-auth.secret", + user: { + additionalFields: { + largeBio: { + type: "string", + defaultValue: largeString, + returned: false, + }, + smallField: { + type: "string", + defaultValue: "small-value", + returned: true, + }, + }, + }, + session: { + cookieCache: { + enabled: true, + }, + }, + }); + + const headers = new Headers(); + + // Sign in with testUser + await client.signIn.email( + { + email: testUser.email, + password: testUser.password, + }, + { + onSuccess: cookieSetter(headers), + }, + ); + + const request = new Request("https://example.com/api/auth/session", { + headers, + }); + + const cache = await getCookieCache(request, { + secret: "better-auth.secret", + }); + + // Cookie cache should exist (not exceed size limit) + expect(cache).not.toBeNull(); + // Large field should be excluded + expect(cache?.user?.largeBio).toBeUndefined(); + // Small field should be included + expect(cache?.user?.smallField).toBeDefined(); + }); + + it("should maintain session field filtering (regression check)", async () => { + const { client, testUser, cookieSetter } = await getTestInstance({ + secret: "better-auth.secret", + session: { + additionalFields: { + internalSessionData: { + type: "string", + defaultValue: "internal-data", + returned: false, + }, + publicSessionData: { + type: "string", + defaultValue: "public-data", + returned: true, + }, + }, + cookieCache: { + enabled: true, + }, + }, + }); + + const headers = new Headers(); + + // Sign in with testUser + await client.signIn.email( + { + email: testUser.email, + password: testUser.password, + }, + { + onSuccess: cookieSetter(headers), + }, + ); + + const request = new Request("https://example.com/api/auth/session", { + headers, + }); + + const cache = await getCookieCache(request, { + secret: "better-auth.secret", + }); + + expect(cache).not.toBeNull(); + // Verify session field filtering still works correctly + // Session should have token + expect(cache?.session?.token).toEqual(expect.any(String)); + // Session field with returned: false should be excluded + expect(cache?.session?.internalSessionData).toBeUndefined(); + }); + + it("should include unknown user fields for backward compatibility", async () => { + const { client, testUser, cookieSetter } = await getTestInstance({ + secret: "better-auth.secret", + user: { + additionalFields: { + knownField: { + type: "string", + defaultValue: "known-value", + returned: false, + }, + }, + }, + session: { + cookieCache: { + enabled: true, + }, + }, + }); + + const headers = new Headers(); + + await client.signIn.email( + { + email: testUser.email, + password: testUser.password, + }, + { + onSuccess: cookieSetter(headers), + }, + ); + + const request = new Request("https://example.com/api/auth/session", { + headers, + }); + + const cache = await getCookieCache(request, { + secret: "better-auth.secret", + }); + + expect(cache).not.toBeNull(); + // Known field with returned: false should be excluded + expect(cache?.user?.knownField).toBeUndefined(); + // Standard fields like email, name should be included (backward compatibility) + expect(cache?.user?.email).toEqual(testUser.email); + expect(cache?.user?.name).toBeDefined(); + }); +}); diff --git a/packages/better-auth/src/cookies/index.ts b/packages/better-auth/src/cookies/index.ts index 5f58b9903e..6414c10273 100644 --- a/packages/better-auth/src/cookies/index.ts +++ b/packages/better-auth/src/cookies/index.ts @@ -14,6 +14,7 @@ import type { BetterAuthCookies, GenericEndpointContext, } from "@better-auth/core"; +import { parseUserOutput } from "../db/schema"; export function createCookieGetter(options: BetterAuthOptions) { const secure = @@ -119,7 +120,9 @@ export async function setCookieCache( {} as Record, ); - const sessionData = { session: filteredSession, user: session.user }; + // Apply field filtering to user data + const filteredUser = parseUserOutput(ctx.context.options, session.user); + const sessionData = { session: filteredSession, user: filteredUser }; const options = { ...ctx.context.authCookies.sessionData.options,