@auth0/nextjs-auth0
Version:
Auth0 Next.js SDK
98 lines (97 loc) • 4.34 kB
JavaScript
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);
}
});
}
}