@auth0/nextjs-auth0
Version:
Auth0 Next.js SDK
115 lines (114 loc) • 4.65 kB
JavaScript
import * as cookies from "../cookies.js";
import { AbstractSessionStore } from "./abstract-session-store.js";
import { LEGACY_COOKIE_NAME, normalizeStatefulSession } from "./normalize-session.js";
const generateId = () => {
const bytes = new Uint8Array(16);
crypto.getRandomValues(bytes);
return Array.from(bytes)
.map((b) => b.toString(16).padStart(2, "0"))
.join("");
};
export class StatefulSessionStore extends AbstractSessionStore {
constructor({ secret, store, rolling, absoluteDuration, inactivityDuration, cookieOptions }) {
super({
secret,
rolling,
absoluteDuration,
inactivityDuration,
cookieOptions
});
this.store = store;
}
async get(reqCookies) {
const cookie = reqCookies.get(this.sessionCookieName) ||
reqCookies.get(LEGACY_COOKIE_NAME);
if (!cookie || !cookie.value) {
return null;
}
// we attempt to extract the session ID by decrypting the cookie value (assuming it's a JWE, v4+) first
// if that fails, we attempt to verify the cookie value as a signed cookie (legacy, v3-)
// if both fail, we return null
// this ensures that v3 sessions are respected and can be transparently rolled over to v4+ sessions
let sessionId = null;
try {
const sessionCookie = await cookies.decrypt(cookie.value, this.secret);
if (sessionCookie === null) {
return null;
}
sessionId = sessionCookie.payload.id;
}
catch (e) {
// the session cookie could not be decrypted, try to verify if it's a legacy session
if (e.code === "ERR_JWE_INVALID") {
const legacySessionId = await cookies.verifySigned(cookie.name, cookie.value, this.secret);
if (!legacySessionId) {
return null;
}
sessionId = legacySessionId;
}
}
if (!sessionId) {
return null;
}
const session = await this.store.get(sessionId);
if (!session) {
return null;
}
return normalizeStatefulSession(session);
}
async set(reqCookies, resCookies, session, isNew = false) {
// check if a session already exists. If so, maintain the existing session ID
let sessionId = null;
const cookieValue = reqCookies.get(this.sessionCookieName)?.value;
if (cookieValue) {
const sessionCookie = await cookies.decrypt(cookieValue, this.secret);
if (sessionCookie) {
sessionId = sessionCookie.payload.id;
}
}
// if this is a new session created by a new login we need to remove the old session
// from the store and regenerate the session ID to prevent session fixation.
if (sessionId && isNew) {
await this.store.delete(sessionId);
sessionId = generateId();
}
if (!sessionId) {
sessionId = generateId();
}
const maxAge = this.calculateMaxAge(session.internal.createdAt);
const expiration = Date.now() / 1000 + maxAge;
const jwe = await cookies.encrypt({
id: sessionId
}, this.secret, expiration);
resCookies.set(this.sessionCookieName, jwe.toString(), {
...this.cookieConfig,
maxAge
});
await this.store.set(sessionId, session);
// 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 (this.sessionCookieName !== LEGACY_COOKIE_NAME &&
reqCookies.has(LEGACY_COOKIE_NAME)) {
cookies.deleteCookie(resCookies, LEGACY_COOKIE_NAME, {
domain: this.cookieConfig.domain,
path: this.cookieConfig.path
});
}
}
async delete(reqCookies, resCookies) {
const cookieValue = reqCookies.get(this.sessionCookieName)?.value;
cookies.deleteCookie(resCookies, this.sessionCookieName, {
domain: this.cookieConfig.domain,
path: this.cookieConfig.path
});
if (!cookieValue) {
return;
}
const session = await cookies.decrypt(cookieValue, this.secret);
if (session) {
await this.store.delete(session.payload.id);
}
}
}