@auth/core
Version:
Authentication for the Web.
206 lines (205 loc) • 8.93 kB
JavaScript
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;
};