@designerstrust/remix-utils
Version:
This package contains simple utility functions to use with [Remix.run](https://remix.run).
72 lines (71 loc) • 3.09 kB
JavaScript
import { v4 as uuid } from "uuid";
import { unprocessableEntity } from "./responses";
/**
* Create a random string in Base64 to be used as an authenticity token for
* CSRF protection. You should run this on the `root.tsx` loader only.
* @example
* let token = createAuthenticityToken(session); // create and set in session
* return json({ ...otherData, csrf: token }); // return the token in the data
* @example
* // create and set in session with the key `csrf-token`
* let token = createAuthenticityToken(session, "csrfToken");
* return json({ ...otherData, csrf: token }); // return the token in the data
*/
export function createAuthenticityToken(session, sessionKey = "csrf") {
let token = session.get(sessionKey);
if (typeof token === "string")
return token;
let newToken = uuid();
session.set(sessionKey, newToken);
return newToken;
}
/**
* Verify if a request and session has a valid CSRF token.
* @example
* let action: ActionFunction = async ({ request }) => {
* let session = await getSession(request.headers.get("Cookie"));
* await verifyAuthenticityToken(request, session);
* // the request is authenticated and you can do anything here
* }
* @example
* let action: ActionFunction = async ({ request }) => {
* let session = await getSession(request.headers.get("Cookie"));
* await verifyAuthenticityToken(request, session, "csrfToken");
* // the request is authenticated and you can do anything here
* }
* @example
* let action: ActionFunction = async ({ request }) => {
* let session = await getSession(request.headers.get("Cookie"));
* let formData = await unstable_parseMultipartFormData(request, uploadHandler);
* await verifyAuthenticityToken(formData, session);
* // the request is authenticated and you can do anything here
* }
*/
export async function verifyAuthenticityToken(data, session, sessionKey = "csrf") {
if (data instanceof Request && data.bodyUsed) {
throw new Error("The body of the request was read before calling verifyAuthenticityToken. Ensure you clone it before reading it.");
}
// We clone the request to ensure we don't modify the original request.
// This allow us to parse the body of the request and let the original request
// still be used and parsed without errors.
let formData = data instanceof FormData ? data : await data.clone().formData();
// if the session doesn't have a csrf token, throw an error
if (!session.has(sessionKey)) {
throw unprocessableEntity({
message: "Can't find CSRF token in session.",
});
}
// if the body doesn't have a csrf token, throw an error
if (!formData.get(sessionKey)) {
throw unprocessableEntity({
message: "Can't find CSRF token in body.",
});
}
// if the body csrf token doesn't match the session csrf token, throw an
// error
if (formData.get(sessionKey) !== session.get(sessionKey)) {
throw unprocessableEntity({
message: "Can't verify CSRF token authenticity.",
});
}
}