fix(db): skip null-parsed session token in findSessions instead of returning early (#10580)

Co-authored-by: Taesu <bytaesu@gmail.com>
This commit is contained in:
Emmanuel Lucius
2026-07-29 09:02:48 +00:00
committed by GitHub
co-authored by Taesu
parent 7552a3b563
commit ea38fcac74
3 changed files with 36 additions and 26 deletions
@@ -0,0 +1,5 @@
---
"better-auth": patch
---
Skip invalid secondary-storage session entries without discarding other valid sessions.
@@ -939,52 +939,57 @@ describe("internal adapter test", async () => {
expect(sessions.length).toBe(0);
});
it("findSessions should skip corrupt sessions without blanking the list", async () => {
const testMap = new Map<string, string>();
const testOpts = {
it.each([
{
caseName: "malformed JSON sessions",
storedValue: "invalid-json{{{",
},
{
caseName: "JSON null sessions",
storedValue: "null",
},
])("findSessions skips $caseName without discarding valid sessions", async ({
storedValue,
}) => {
const secondaryStorageValues = new Map<string, string>();
const options = {
database: new DatabaseSync(":memory:"),
secondaryStorage: {
set(key: string, value: string, ttl?: number) {
testMap.set(key, value);
secondaryStorageValues.set(key, value);
},
get(key: string) {
return testMap.get(key) || null;
return secondaryStorageValues.get(key) || null;
},
delete(key: string) {
testMap.delete(key);
secondaryStorageValues.delete(key);
},
},
} satisfies BetterAuthOptions;
(await getMigrations(testOpts)).runMigrations();
(await getMigrations(options)).runMigrations();
const testCtx = await init(testOpts);
const testInternalAdapter = testCtx.internalAdapter;
const user = await testInternalAdapter.createUser({
const { internalAdapter } = await init(options);
const user = await internalAdapter.createUser({
name: "test-user-find",
email: "test-find@email.com",
});
const session1 = await internalAdapter.createSession(user.id);
const session2 = await internalAdapter.createSession(user.id);
const session3 = await internalAdapter.createSession(user.id);
// Create 3 sessions
const session1 = await testInternalAdapter.createSession(user.id);
const session2 = await testInternalAdapter.createSession(user.id);
const session3 = await testInternalAdapter.createSession(user.id);
secondaryStorageValues.set(session2.token, storedValue);
// Corrupt session2 data
testMap.set(session2.token, "invalid-json{{{");
// findSessions should still return session1 and session3
const sessions = await testInternalAdapter.findSessions([
const sessions = await internalAdapter.findSessions([
session1.token,
session2.token,
session3.token,
]);
expect(sessions.length).toBe(2);
expect(sessions.map((s) => s.session.token).sort()).toEqual(
[session1.token, session3.token].sort(),
);
expect(sessions.map(({ session }) => session.token)).toEqual([
session1.token,
session3.token,
]);
});
it("should update session and active-sessions list in secondary storage", async () => {
@@ -560,7 +560,7 @@ export const createInternalAdapter = (
session: Session;
user: User;
};
if (!s) return [];
if (!s) continue;
const expiresAt = new Date(s.session.expiresAt);
if (options?.onlyActiveSessions && expiresAt <= new Date()) {
continue;