diff --git a/.changeset/skip-invalid-secondary-sessions.md b/.changeset/skip-invalid-secondary-sessions.md new file mode 100644 index 0000000000..848da57997 --- /dev/null +++ b/.changeset/skip-invalid-secondary-sessions.md @@ -0,0 +1,5 @@ +--- +"better-auth": patch +--- + +Skip invalid secondary-storage session entries without discarding other valid sessions. diff --git a/packages/better-auth/src/db/internal-adapter.test.ts b/packages/better-auth/src/db/internal-adapter.test.ts index 7ed9b3d122..6054d99d00 100644 --- a/packages/better-auth/src/db/internal-adapter.test.ts +++ b/packages/better-auth/src/db/internal-adapter.test.ts @@ -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(); - - 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(); + 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 () => { diff --git a/packages/better-auth/src/db/internal-adapter.ts b/packages/better-auth/src/db/internal-adapter.ts index 93984dd61d..ba4061aee5 100644 --- a/packages/better-auth/src/db/internal-adapter.ts +++ b/packages/better-auth/src/db/internal-adapter.ts @@ -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;