UNPKG

@auth0/nextjs-auth0

Version:
114 lines (113 loc) 5.67 kB
import * as cookies from "../cookies.js"; import { AbstractSessionStore } from "./abstract-session-store.js"; import { LEGACY_COOKIE_NAME, normalizeStatelessSession } from "./normalize-session.js"; export class StatelessSessionStore extends AbstractSessionStore { constructor({ secret, rolling, absoluteDuration, inactivityDuration, cookieOptions }) { super({ secret, rolling, absoluteDuration, inactivityDuration, cookieOptions }); this.connectionTokenSetsCookieName = "__FC"; } async get(reqCookies) { const cookieValue = cookies.getChunkedCookie(this.sessionCookieName, reqCookies) ?? cookies.getChunkedCookie(LEGACY_COOKIE_NAME, reqCookies, true); if (!cookieValue) { return null; } const originalSession = await cookies.decrypt(cookieValue, this.secret); if (!originalSession) { return null; } const normalizedStatelessSession = normalizeStatelessSession(originalSession); // As connection access tokens are stored in seperate cookies, // we need to get all cookies and only use those that are prefixed with `this.connectionTokenSetsCookieName` const connectionTokenSetsCookies = this.getConnectionTokenSetsCookies(reqCookies); const connectionTokenSets = []; for (const cookie of connectionTokenSetsCookies) { const decryptedCookie = await cookies.decrypt(cookie.value, this.secret); if (decryptedCookie) { connectionTokenSets.push(decryptedCookie.payload); } } return { ...normalizedStatelessSession, // Ensure that when there are no connection token sets, we omit the property. ...(connectionTokenSets.length ? { connectionTokenSets } : {}) }; } /** * save adds the encrypted session cookie as a `Set-Cookie` header. */ async set(reqCookies, resCookies, session) { const { connectionTokenSets, ...originalSession } = session; const maxAge = this.calculateMaxAge(session.internal.createdAt); const expiration = Math.floor(Date.now() / 1000) + maxAge; const jwe = await cookies.encrypt(originalSession, this.secret, expiration); const cookieValue = jwe.toString(); const options = { ...this.cookieConfig, maxAge }; cookies.setChunkedCookie(this.sessionCookieName, cookieValue, options, reqCookies, resCookies); // Store connection access tokens, each in its own cookie if (connectionTokenSets?.length) { await Promise.all(connectionTokenSets.map((connectionTokenSet, index) => this.storeInCookie(reqCookies, resCookies, connectionTokenSet, `${this.connectionTokenSetsCookieName}_${index}`, maxAge))); } // 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, true, { domain: this.cookieConfig.domain, path: this.cookieConfig.path }); } async delete(reqCookies, resCookies) { const deleteOptions = { domain: this.cookieConfig.domain, path: this.cookieConfig.path }; cookies.deleteChunkedCookie(this.sessionCookieName, reqCookies, resCookies, false, deleteOptions); this.getConnectionTokenSetsCookies(reqCookies).forEach((cookie) => cookies.deleteCookie(resCookies, cookie.name, deleteOptions)); } async storeInCookie(reqCookies, resCookies, session, cookieName, maxAge) { const expiration = Math.floor(Date.now() / 1000 + maxAge); const jwe = await cookies.encrypt(session, this.secret, expiration); const cookieValue = jwe.toString(); resCookies.set(cookieName, jwe.toString(), { ...this.cookieConfig, maxAge }); // to enable read-after-write in the same request for middleware reqCookies.set(cookieName, cookieValue); // check if the session cookie size exceeds 4096 bytes, and if so, log a warning const cookieJarSizeTest = new cookies.ResponseCookies(new Headers()); cookieJarSizeTest.set(cookieName, cookieValue, { ...this.cookieConfig, maxAge }); if (new TextEncoder().encode(cookieJarSizeTest.toString()).length >= 4096) { // if the cookie is the session cookie, log a warning with additional information about the claims and user profile. if (cookieName === this.sessionCookieName) { console.warn(`The ${cookieName} cookie size exceeds 4096 bytes, which may cause issues in some browsers. ` + "Consider removing any unnecessary custom claims from the access token or the user profile. " + "Alternatively, you can use a stateful session implementation to store the session data in a data store."); } else { console.warn(`The ${cookieName} cookie size exceeds 4096 bytes, which may cause issues in some browsers. ` + "You can use a stateful session implementation to store the session data in a data store."); } } } getConnectionTokenSetsCookies(cookies) { return cookies .getAll() .filter((cookie) => cookie.name.startsWith(this.connectionTokenSetsCookieName)); } }