Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 40 additions & 1 deletion src/server/session/stateful-session-store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
ResponseCookies,
sign
} from "../cookies";
import { LegacySessionPayload } from "./normalize-session";
import { LEGACY_COOKIE_NAME, LegacySessionPayload } from "./normalize-session";
import { StatefulSessionStore } from "./stateful-session-store";

describe("Stateful Session Store", async () => {
Expand Down Expand Up @@ -645,6 +645,45 @@ describe("Stateful Session Store", async () => {
expect(cookie?.secure).toEqual(false);
});
});


it("should remove the legacy cookie if it exists", async () => {
const currentTime = Date.now();
const createdAt = Math.floor(currentTime / 1000);
const secret = await generateSecret(32);
const session: SessionData = {
user: { sub: "user_123" },
tokenSet: {
accessToken: "at_123",
refreshToken: "rt_123",
expiresAt: 123456
},
internal: {
sid: "auth0-sid",
createdAt
}
};
const store = {
get: vi.fn(),
set: vi.fn(),
delete: vi.fn()
};

const requestCookies = new RequestCookies(new Headers());
const responseCookies = new ResponseCookies(new Headers());

const sessionStore = new StatefulSessionStore({
secret,
store,
});

vi.spyOn(requestCookies, "has").mockReturnValue(true);
vi.spyOn(responseCookies, "delete");

await sessionStore.set(requestCookies, responseCookies, session);

expect(responseCookies.delete).toHaveBeenCalledWith(LEGACY_COOKIE_NAME);
});
});

describe("delete", async () => {
Expand Down
6 changes: 6 additions & 0 deletions src/server/session/stateful-session-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,12 @@ export class StatefulSessionStore extends AbstractSessionStore {

// to enable read-after-write in the same request for middleware
reqCookies.set(this.sessionCookieName, jwe.toString());

// Any existing v3 cookie can also be deleted once we have set a v4 cookie.
// In stateful sessions, we do not have to worry about chunking.
if (reqCookies.has(LEGACY_COOKIE_NAME)) {
resCookies.delete(LEGACY_COOKIE_NAME);
}
}

async delete(
Expand Down
68 changes: 67 additions & 1 deletion src/server/session/stateless-session-store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { generateSecret } from "../../test/utils";
import { SessionData } from "../../types";
import { decrypt, encrypt, RequestCookies, ResponseCookies } from "../cookies";
import { LegacySession } from "./normalize-session";
import { LEGACY_COOKIE_NAME, LegacySession } from "./normalize-session";
import { StatelessSessionStore } from "./stateless-session-store";

describe("Stateless Session Store", async () => {
Expand Down Expand Up @@ -313,6 +313,72 @@ describe("Stateless Session Store", async () => {
expect(cookie?.maxAge).toEqual(0); // cookie should expire immediately
expect(cookie?.secure).toEqual(false);
});

it("should delete the legacy cookie if it exists", async () => {
const currentTime = Date.now();
const createdAt = Math.floor(currentTime / 1000);
const secret = await generateSecret(32);
const session: SessionData = {
user: { sub: "user_123" },
tokenSet: {
accessToken: "at_123",
refreshToken: "rt_123",
expiresAt: 123456
},
internal: {
sid: "auth0-sid",
createdAt
}
};
const requestCookies = new RequestCookies(new Headers());
const responseCookies = new ResponseCookies(new Headers());

const sessionStore = new StatelessSessionStore({
secret,
});

vi.spyOn(responseCookies, "delete");
vi.spyOn(requestCookies, "has").mockReturnValue(true);

await sessionStore.set(requestCookies, responseCookies, session);

expect(responseCookies.delete).toHaveBeenCalledWith(LEGACY_COOKIE_NAME);
});

it("should delete the legacy cookie chunks if they exists", async () => {
const currentTime = Date.now();
const createdAt = Math.floor(currentTime / 1000);
const secret = await generateSecret(32);
const session: SessionData = {
user: { sub: "user_123" },
tokenSet: {
accessToken: "at_123",
refreshToken: "rt_123",
expiresAt: 123456
},
internal: {
sid: "auth0-sid",
createdAt
}
};
const requestCookies = new RequestCookies(new Headers());
const responseCookies = new ResponseCookies(new Headers());

const sessionStore = new StatelessSessionStore({
secret,
});

vi.spyOn(responseCookies, "delete");
vi.spyOn(requestCookies, "getAll").mockReturnValue([
{ name: `${LEGACY_COOKIE_NAME}__0`, value: '' },
{ name: `${LEGACY_COOKIE_NAME}__1`, value: '' }
]);

await sessionStore.set(requestCookies, responseCookies, session);

expect(responseCookies.delete).toHaveBeenCalledWith(`${LEGACY_COOKIE_NAME}__0`);
expect(responseCookies.delete).toHaveBeenCalledWith(`${LEGACY_COOKIE_NAME}__1`);
});
});

describe("with rolling sessions disabled", async () => {
Expand Down
4 changes: 4 additions & 0 deletions src/server/session/stateless-session-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,10 @@ export class StatelessSessionStore extends AbstractSessionStore {
)
);
}

// Any existing v3 cookie can be deleted as soon as we have set a v4 cookie.
// In stateless sessions, we do have to ensure we delete all chunks.
cookies.deleteChunkedCookie(LEGACY_COOKIE_NAME, reqCookies, resCookies);
}

async delete(
Expand Down