Skip to content

Commit ae5fbfe

Browse files
fix: Delete legacy cookie once v4 cookie is set (#2019)
1 parent b28ea86 commit ae5fbfe

File tree

4 files changed

+117
-2
lines changed

4 files changed

+117
-2
lines changed

src/server/session/stateful-session-store.test.ts

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
ResponseCookies,
1010
sign
1111
} from "../cookies";
12-
import { LegacySessionPayload } from "./normalize-session";
12+
import { LEGACY_COOKIE_NAME, LegacySessionPayload } from "./normalize-session";
1313
import { StatefulSessionStore } from "./stateful-session-store";
1414

1515
describe("Stateful Session Store", async () => {
@@ -645,6 +645,45 @@ describe("Stateful Session Store", async () => {
645645
expect(cookie?.secure).toEqual(false);
646646
});
647647
});
648+
649+
650+
it("should remove the legacy cookie if it exists", async () => {
651+
const currentTime = Date.now();
652+
const createdAt = Math.floor(currentTime / 1000);
653+
const secret = await generateSecret(32);
654+
const session: SessionData = {
655+
user: { sub: "user_123" },
656+
tokenSet: {
657+
accessToken: "at_123",
658+
refreshToken: "rt_123",
659+
expiresAt: 123456
660+
},
661+
internal: {
662+
sid: "auth0-sid",
663+
createdAt
664+
}
665+
};
666+
const store = {
667+
get: vi.fn(),
668+
set: vi.fn(),
669+
delete: vi.fn()
670+
};
671+
672+
const requestCookies = new RequestCookies(new Headers());
673+
const responseCookies = new ResponseCookies(new Headers());
674+
675+
const sessionStore = new StatefulSessionStore({
676+
secret,
677+
store,
678+
});
679+
680+
vi.spyOn(requestCookies, "has").mockReturnValue(true);
681+
vi.spyOn(responseCookies, "delete");
682+
683+
await sessionStore.set(requestCookies, responseCookies, session);
684+
685+
expect(responseCookies.delete).toHaveBeenCalledWith(LEGACY_COOKIE_NAME);
686+
});
648687
});
649688

650689
describe("delete", async () => {

src/server/session/stateful-session-store.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,12 @@ export class StatefulSessionStore extends AbstractSessionStore {
147147

148148
// to enable read-after-write in the same request for middleware
149149
reqCookies.set(this.sessionCookieName, jwe.toString());
150+
151+
// Any existing v3 cookie can also be deleted once we have set a v4 cookie.
152+
// In stateful sessions, we do not have to worry about chunking.
153+
if (reqCookies.has(LEGACY_COOKIE_NAME)) {
154+
resCookies.delete(LEGACY_COOKIE_NAME);
155+
}
150156
}
151157

152158
async delete(

src/server/session/stateless-session-store.test.ts

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
33
import { generateSecret } from "../../test/utils";
44
import { SessionData } from "../../types";
55
import { decrypt, encrypt, RequestCookies, ResponseCookies } from "../cookies";
6-
import { LegacySession } from "./normalize-session";
6+
import { LEGACY_COOKIE_NAME, LegacySession } from "./normalize-session";
77
import { StatelessSessionStore } from "./stateless-session-store";
88

99
describe("Stateless Session Store", async () => {
@@ -313,6 +313,72 @@ describe("Stateless Session Store", async () => {
313313
expect(cookie?.maxAge).toEqual(0); // cookie should expire immediately
314314
expect(cookie?.secure).toEqual(false);
315315
});
316+
317+
it("should delete the legacy cookie if it exists", async () => {
318+
const currentTime = Date.now();
319+
const createdAt = Math.floor(currentTime / 1000);
320+
const secret = await generateSecret(32);
321+
const session: SessionData = {
322+
user: { sub: "user_123" },
323+
tokenSet: {
324+
accessToken: "at_123",
325+
refreshToken: "rt_123",
326+
expiresAt: 123456
327+
},
328+
internal: {
329+
sid: "auth0-sid",
330+
createdAt
331+
}
332+
};
333+
const requestCookies = new RequestCookies(new Headers());
334+
const responseCookies = new ResponseCookies(new Headers());
335+
336+
const sessionStore = new StatelessSessionStore({
337+
secret,
338+
});
339+
340+
vi.spyOn(responseCookies, "delete");
341+
vi.spyOn(requestCookies, "has").mockReturnValue(true);
342+
343+
await sessionStore.set(requestCookies, responseCookies, session);
344+
345+
expect(responseCookies.delete).toHaveBeenCalledWith(LEGACY_COOKIE_NAME);
346+
});
347+
348+
it("should delete the legacy cookie chunks if they exists", async () => {
349+
const currentTime = Date.now();
350+
const createdAt = Math.floor(currentTime / 1000);
351+
const secret = await generateSecret(32);
352+
const session: SessionData = {
353+
user: { sub: "user_123" },
354+
tokenSet: {
355+
accessToken: "at_123",
356+
refreshToken: "rt_123",
357+
expiresAt: 123456
358+
},
359+
internal: {
360+
sid: "auth0-sid",
361+
createdAt
362+
}
363+
};
364+
const requestCookies = new RequestCookies(new Headers());
365+
const responseCookies = new ResponseCookies(new Headers());
366+
367+
const sessionStore = new StatelessSessionStore({
368+
secret,
369+
});
370+
371+
vi.spyOn(responseCookies, "delete");
372+
vi.spyOn(requestCookies, "getAll").mockReturnValue([
373+
{ name: `${LEGACY_COOKIE_NAME}__0`, value: '' },
374+
{ name: `${LEGACY_COOKIE_NAME}__1`, value: '' }
375+
]);
376+
377+
await sessionStore.set(requestCookies, responseCookies, session);
378+
379+
expect(responseCookies.delete).toHaveBeenCalledWith(`${LEGACY_COOKIE_NAME}__0`);
380+
expect(responseCookies.delete).toHaveBeenCalledWith(`${LEGACY_COOKIE_NAME}__1`);
381+
});
316382
});
317383

318384
describe("with rolling sessions disabled", async () => {

src/server/session/stateless-session-store.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,10 @@ export class StatelessSessionStore extends AbstractSessionStore {
117117
)
118118
);
119119
}
120+
121+
// Any existing v3 cookie can be deleted as soon as we have set a v4 cookie.
122+
// In stateless sessions, we do have to ensure we delete all chunks.
123+
cookies.deleteChunkedCookie(LEGACY_COOKIE_NAME, reqCookies, resCookies);
120124
}
121125

122126
async delete(

0 commit comments

Comments
 (0)