fix(session): persist additionalFields in cookie cache (#5735)

This commit is contained in:
Ridhim Singh Raizada
2025-11-04 05:37:32 +05:30
committed by GitHub
parent 82be680ed6
commit 51bd7deaaa
2 changed files with 130 additions and 10 deletions

View File

@@ -1342,4 +1342,74 @@ describe("cookie cache versioning", async () => {
expect(session.data).not.toBeNull();
expect(session.data?.user.email).toBe(testUser.email);
});
it("should include additionalFields when retrieving from cookie cache", async () => {
const { client, testUser, cookieSetter, auth } = await getTestInstance({
session: {
additionalFields: {
role: {
type: "string",
defaultValue: "user",
returned: true, // Should be included
},
preferences: {
type: "json",
defaultValue: "{}",
returned: true,
},
},
cookieCache: {
enabled: true,
strategy: "compact",
},
},
});
const ctx = await auth.$context;
const fn = vi.spyOn(ctx.adapter, "findOne");
const headers = new Headers();
// Sign in
await client.signIn.email(
{
email: testUser.email,
password: testUser.password,
},
{
onSuccess: cookieSetter(headers),
},
);
// First call - should hit database
const firstCall = fn.mock.calls.length;
const session1 = await client.getSession({
fetchOptions: {
headers,
},
});
expect(session1.data).toBeTruthy();
expect(session1.data?.session).toHaveProperty("role"); // ? Should have additionalFields
expect(session1.data?.session).toHaveProperty("preferences"); // ? Should have additionalFields
// Second call - should use cookie cache (no DB call)
const session2 = await client.getSession({
fetchOptions: {
headers,
},
});
// Verify cache was used (no additional DB calls)
expect(fn.mock.calls.length).toBe(firstCall);
// ? THIS IS THE KEY TEST - additionalFields should be present from cache
expect(session2.data?.session).toHaveProperty("role");
expect(session2.data?.session).toHaveProperty("preferences");
// Verify values match
const s1 = session1.data?.session as Record<string, any>;
const s2 = session2.data?.session as Record<string, any>;
expect(s2.role).toBe(s1.role);
});
});

View File

@@ -20,6 +20,7 @@ import {
} from "../../cookies";
import { getSessionQuerySchema } from "../../cookies/session-store";
import { symmetricDecodeJWT, verifyJWT } from "../../crypto";
import { parseSessionOutput, parseUserOutput } from "../../db";
import type { InferSession, InferUser, Session, User } from "../../types";
import type { Prettify } from "../../types/helper";
import { getDate } from "../../utils/date";
@@ -269,20 +270,57 @@ export const getSession = <Option extends BetterAuthOptions>() =>
// Set the refreshed cookie cache
await setCookieCache(ctx, refreshedSession, false);
ctx.context.session = refreshedSession;
// Parse session and user to ensure additionalFields are included
// Rehydrate date fields from JSON strings before parsing
const parsedRefreshedSession = parseSessionOutput(
ctx.context.options,
{
...refreshedSession.session,
expiresAt: new Date(refreshedSession.session.expiresAt),
createdAt: new Date(refreshedSession.session.createdAt),
updatedAt: new Date(refreshedSession.session.updatedAt),
},
);
const parsedRefreshedUser = parseUserOutput(
ctx.context.options,
{
...refreshedSession.user,
createdAt: new Date(refreshedSession.user.createdAt),
updatedAt: new Date(refreshedSession.user.updatedAt),
},
);
ctx.context.session = {
session: parsedRefreshedSession,
user: parsedRefreshedUser,
};
return ctx.json({
session: refreshedSession.session,
user: refreshedSession.user,
session: parsedRefreshedSession,
user: parsedRefreshedUser,
} as {
session: InferSession<Option>;
user: InferUser<Option>;
});
}
ctx.context.session = session;
// Parse session and user to ensure additionalFields are included
const parsedSession = parseSessionOutput(ctx.context.options, {
...session.session,
expiresAt: new Date(session.session.expiresAt),
createdAt: new Date(session.session.createdAt),
updatedAt: new Date(session.session.updatedAt),
});
const parsedUser = parseUserOutput(ctx.context.options, {
...session.user,
createdAt: new Date(session.user.createdAt),
updatedAt: new Date(session.user.updatedAt),
});
ctx.context.session = {
session: parsedSession,
user: parsedUser,
};
return ctx.json({
session: session.session,
user: session.user,
session: parsedSession,
user: parsedUser,
} as {
session: InferSession<Option>;
user: InferUser<Option>;
@@ -311,9 +349,15 @@ export const getSession = <Option extends BetterAuthOptions>() =>
* or if the session refresh is disabled
*/
if (dontRememberMe || ctx.query?.disableRefresh) {
// Parse session and user to ensure additionalFields are included
const parsedSession = parseSessionOutput(
ctx.context.options,
session.session,
);
const parsedUser = parseUserOutput(ctx.context.options, session.user);
return ctx.json({
session: session.session,
user: session.user,
session: parsedSession,
user: parsedUser,
} as {
session: InferSession<Option>;
user: InferUser<Option>;
@@ -369,9 +413,15 @@ export const getSession = <Option extends BetterAuthOptions>() =>
},
);
// Parse session and user to ensure additionalFields are included
const parsedUpdatedSession = parseSessionOutput(
ctx.context.options,
updatedSession,
);
const parsedUser = parseUserOutput(ctx.context.options, session.user);
return ctx.json({
session: updatedSession,
user: session.user,
session: parsedUpdatedSession,
user: parsedUser,
} as unknown as {
session: InferSession<Option>;
user: InferUser<Option>;