uncsrf
Version:
Single API for CSRF functions, working in Node.js, Browsers and other runtimes
52 lines (50 loc) • 1.78 kB
JavaScript
const webCrypto = globalThis.crypto;
const subtle = webCrypto.subtle;
const getRandomValues = (array) => webCrypto.getRandomValues(array);
const defaultEncryptAlgorithm = "AES-CBC";
const importEncryptSecret = async (secret, encryptAlgorithm) => {
const keyData = new TextEncoder().encode(secret ?? randomEncryptSecret());
return await subtle.importKey(
"raw",
keyData,
{ name: encryptAlgorithm || defaultEncryptAlgorithm },
false,
["encrypt", "decrypt"]
);
};
const create = async (secret, encryptSecret, encryptAlgorithm) => {
const iv = getRandomValues(new Uint8Array(16));
const encrypted = await subtle.encrypt(
{ name: encryptAlgorithm || defaultEncryptAlgorithm, iv },
encryptSecret,
new TextEncoder().encode(secret)
);
const encryptedBuffer = Buffer.from(new Uint8Array(encrypted));
return `${Buffer.from(iv).toString("base64")}:${encryptedBuffer.toString(
"base64"
)}`;
};
const verify = async (secret, token, encryptSecret, encryptAlgorithm) => {
const [iv, encrypted] = token.split(":");
if (!iv || !encrypted) {
return false;
}
let decrypted;
try {
const encodedDecrypted = await subtle.decrypt(
{
name: encryptAlgorithm || defaultEncryptAlgorithm,
iv: Buffer.from(iv, "base64")
},
encryptSecret,
Buffer.from(encrypted, "base64")
);
decrypted = new TextDecoder().decode(encodedDecrypted);
} catch {
return false;
}
return decrypted === secret;
};
const randomSecret = () => webCrypto.randomUUID();
const randomEncryptSecret = () => [...crypto.getRandomValues(new Uint8Array(16))].map((b) => b.toString(16).padStart(2, "0")).join("");
export { create, defaultEncryptAlgorithm, importEncryptSecret, randomEncryptSecret, randomSecret, verify };