UNPKG

@auth/core

Version:

Authentication for the Web.

206 lines (205 loc) 8.93 kB
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { if (kind === "m") throw new TypeError("Private method is not writable"); if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it"); return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; }; var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); }; var _SessionStore_instances, _SessionStore_chunks, _SessionStore_option, _SessionStore_logger, _SessionStore_chunk, _SessionStore_clean; // Uncomment to recalculate the estimated size // of an empty session cookie // import * as cookie from "../vendored/cookie.js" // const { serialize } = cookie // console.log( // "Cookie estimated to be ", // serialize(`__Secure.authjs.session-token.0`, "", { // expires: new Date(), // httpOnly: true, // maxAge: Number.MAX_SAFE_INTEGER, // path: "/", // sameSite: "strict", // secure: true, // domain: "example.com", // }).length, // " bytes" // ) const ALLOWED_COOKIE_SIZE = 4096; // Based on commented out section above const ESTIMATED_EMPTY_COOKIE_SIZE = 160; const CHUNK_SIZE = ALLOWED_COOKIE_SIZE - ESTIMATED_EMPTY_COOKIE_SIZE; /** * Use secure cookies if the site uses HTTPS * This being conditional allows cookies to work non-HTTPS development URLs * Honour secure cookie option, which sets 'secure' and also adds '__Secure-' * prefix, but enable them by default if the site URL is HTTPS; but not for * non-HTTPS URLs like http://localhost which are used in development). * For more on prefixes see https://googlechrome.github.io/samples/cookie-prefixes/ * * @TODO Review cookie settings (names, options) */ export function defaultCookies(useSecureCookies) { const cookiePrefix = useSecureCookies ? "__Secure-" : ""; return { // default cookie options sessionToken: { name: `${cookiePrefix}authjs.session-token`, options: { httpOnly: true, sameSite: "lax", path: "/", secure: useSecureCookies, }, }, callbackUrl: { name: `${cookiePrefix}authjs.callback-url`, options: { httpOnly: true, sameSite: "lax", path: "/", secure: useSecureCookies, }, }, csrfToken: { // Default to __Host- for CSRF token for additional protection if using useSecureCookies // NB: The `__Host-` prefix is stricter than the `__Secure-` prefix. name: `${useSecureCookies ? "__Host-" : ""}authjs.csrf-token`, options: { httpOnly: true, sameSite: "lax", path: "/", secure: useSecureCookies, }, }, pkceCodeVerifier: { name: `${cookiePrefix}authjs.pkce.code_verifier`, options: { httpOnly: true, sameSite: "lax", path: "/", secure: useSecureCookies, maxAge: 60 * 15, // 15 minutes in seconds }, }, state: { name: `${cookiePrefix}authjs.state`, options: { httpOnly: true, sameSite: "lax", path: "/", secure: useSecureCookies, maxAge: 60 * 15, // 15 minutes in seconds }, }, nonce: { name: `${cookiePrefix}authjs.nonce`, options: { httpOnly: true, sameSite: "lax", path: "/", secure: useSecureCookies, }, }, webauthnChallenge: { name: `${cookiePrefix}authjs.challenge`, options: { httpOnly: true, sameSite: "lax", path: "/", secure: useSecureCookies, maxAge: 60 * 15, // 15 minutes in seconds }, }, }; } export class SessionStore { constructor(option, cookies, logger) { _SessionStore_instances.add(this); _SessionStore_chunks.set(this, {}); _SessionStore_option.set(this, void 0); _SessionStore_logger.set(this, void 0); __classPrivateFieldSet(this, _SessionStore_logger, logger, "f"); __classPrivateFieldSet(this, _SessionStore_option, option, "f"); if (!cookies) return; const { name: sessionCookiePrefix } = option; for (const [name, value] of Object.entries(cookies)) { if (!name.startsWith(sessionCookiePrefix) || !value) continue; __classPrivateFieldGet(this, _SessionStore_chunks, "f")[name] = value; } } /** * The JWT Session or database Session ID * constructed from the cookie chunks. */ get value() { // Sort the chunks by their keys before joining const sortedKeys = Object.keys(__classPrivateFieldGet(this, _SessionStore_chunks, "f")).sort((a, b) => { const aSuffix = parseInt(a.split(".").pop() || "0"); const bSuffix = parseInt(b.split(".").pop() || "0"); return aSuffix - bSuffix; }); // Use the sorted keys to join the chunks in the correct order return sortedKeys.map((key) => __classPrivateFieldGet(this, _SessionStore_chunks, "f")[key]).join(""); } /** * Given a cookie value, return new cookies, chunked, to fit the allowed cookie size. * If the cookie has changed from chunked to unchunked or vice versa, * it deletes the old cookies as well. */ chunk(value, options) { // Assume all cookies should be cleaned by default const cookies = __classPrivateFieldGet(this, _SessionStore_instances, "m", _SessionStore_clean).call(this); // Calculate new chunks const chunked = __classPrivateFieldGet(this, _SessionStore_instances, "m", _SessionStore_chunk).call(this, { name: __classPrivateFieldGet(this, _SessionStore_option, "f").name, value, options: { ...__classPrivateFieldGet(this, _SessionStore_option, "f").options, ...options }, }); // Update stored chunks / cookies for (const chunk of chunked) { cookies[chunk.name] = chunk; } return Object.values(cookies); } /** Returns a list of cookies that should be cleaned. */ clean() { return Object.values(__classPrivateFieldGet(this, _SessionStore_instances, "m", _SessionStore_clean).call(this)); } } _SessionStore_chunks = new WeakMap(), _SessionStore_option = new WeakMap(), _SessionStore_logger = new WeakMap(), _SessionStore_instances = new WeakSet(), _SessionStore_chunk = function _SessionStore_chunk(cookie) { const chunkCount = Math.ceil(cookie.value.length / CHUNK_SIZE); if (chunkCount === 1) { __classPrivateFieldGet(this, _SessionStore_chunks, "f")[cookie.name] = cookie.value; return [cookie]; } const cookies = []; for (let i = 0; i < chunkCount; i++) { const name = `${cookie.name}.${i}`; const value = cookie.value.substr(i * CHUNK_SIZE, CHUNK_SIZE); cookies.push({ ...cookie, name, value }); __classPrivateFieldGet(this, _SessionStore_chunks, "f")[name] = value; } __classPrivateFieldGet(this, _SessionStore_logger, "f").debug("CHUNKING_SESSION_COOKIE", { message: `Session cookie exceeds allowed ${ALLOWED_COOKIE_SIZE} bytes.`, emptyCookieSize: ESTIMATED_EMPTY_COOKIE_SIZE, valueSize: cookie.value.length, chunks: cookies.map((c) => c.value.length + ESTIMATED_EMPTY_COOKIE_SIZE), }); return cookies; }, _SessionStore_clean = function _SessionStore_clean() { const cleanedChunks = {}; for (const name in __classPrivateFieldGet(this, _SessionStore_chunks, "f")) { delete __classPrivateFieldGet(this, _SessionStore_chunks, "f")?.[name]; cleanedChunks[name] = { name, value: "", options: { ...__classPrivateFieldGet(this, _SessionStore_option, "f").options, maxAge: 0 }, }; } return cleanedChunks; };