UNPKG

@auth0/nextjs-auth0

Version:
98 lines (97 loc) 4.34 kB
import * as cookies from "./cookies.js"; const TRANSACTION_COOKIE_PREFIX = "__txn_"; /** * TransactionStore is responsible for storing the state required to successfully complete * an authentication transaction. The store relies on encrypted, stateless cookies to store * the transaction state. */ export class TransactionStore { constructor({ secret, cookieOptions, enableParallelTransactions }) { this.secret = secret; this.transactionCookiePrefix = cookieOptions?.prefix ?? TRANSACTION_COOKIE_PREFIX; this.cookieOptions = { httpOnly: true, sameSite: cookieOptions?.sameSite ?? "lax", // required to allow the cookie to be sent on the callback request secure: cookieOptions?.secure ?? false, path: cookieOptions?.path ?? "/", domain: cookieOptions?.domain, maxAge: cookieOptions?.maxAge || 60 * 60 // 1 hour in seconds }; this.enableParallelTransactions = enableParallelTransactions ?? true; } /** * Returns the name of the cookie used to store the transaction state. * The cookie name is derived from the state parameter to prevent collisions * between different transactions. */ getTransactionCookieName(state) { return this.enableParallelTransactions ? `${this.transactionCookiePrefix}${state}` : `${this.transactionCookiePrefix}`; } /** * Returns the configured prefix for transaction cookies. */ getCookiePrefix() { return this.transactionCookiePrefix; } /** * Saves the transaction state to an encrypted cookie. * * @param resCookies - The response cookies object to set the transaction cookie on * @param transactionState - The transaction state to save * @param reqCookies - Optional request cookies to check for existing transactions. * When provided and `enableParallelTransactions` is false, * will check for existing transaction cookies. When omitted, * the existence check is skipped for performance optimization. * @throws {Error} When transaction state is missing required state parameter */ async save(resCookies, transactionState, reqCookies) { if (!transactionState.state) { throw new Error("Transaction state is required"); } // When parallel transactions are disabled, check if a transaction already exists if (reqCookies && !this.enableParallelTransactions) { const cookieName = this.getTransactionCookieName(transactionState.state); const existingCookie = reqCookies.get(cookieName); if (existingCookie) { console.warn("A transaction is already in progress. Only one transaction is allowed when parallel transactions are disabled."); return; } } const expirationSeconds = this.cookieOptions.maxAge; const expiration = Math.floor(Date.now() / 1000 + expirationSeconds); const jwe = await cookies.encrypt(transactionState, this.secret, expiration); resCookies.set(this.getTransactionCookieName(transactionState.state), jwe.toString(), this.cookieOptions); } async get(reqCookies, state) { const cookieName = this.getTransactionCookieName(state); const cookieValue = reqCookies.get(cookieName)?.value; if (!cookieValue) { return null; } return cookies.decrypt(cookieValue, this.secret); } async delete(resCookies, state) { cookies.deleteCookie(resCookies, this.getTransactionCookieName(state), { domain: this.cookieOptions.domain, path: this.cookieOptions.path }); } /** * Deletes all transaction cookies based on the configured prefix. */ async deleteAll(reqCookies, resCookies) { const txnPrefix = this.getCookiePrefix(); const deleteOptions = { domain: this.cookieOptions.domain, path: this.cookieOptions.path }; reqCookies.getAll().forEach((cookie) => { if (cookie.name.startsWith(txnPrefix)) { cookies.deleteCookie(resCookies, cookie.name, deleteOptions); } }); } }