better-auth
Version:
The most comprehensive authentication framework for TypeScript.
70 lines (69 loc) • 3.37 kB
JavaScript
import { signJWT, symmetricDecodeJWT, symmetricEncodeJWT, verifyJWT } from "./jwt.mjs";
import { constantTimeEqual } from "./buffer.mjs";
import { hashPassword, verifyPassword } from "./password.mjs";
import { generateRandomString } from "./random.mjs";
import { getWebcryptoSubtle } from "@better-auth/utils";
import { createHash } from "@better-auth/utils/hash";
import { xchacha20poly1305 } from "@noble/ciphers/chacha.js";
import { bytesToHex, hexToBytes, managedNonce, utf8ToBytes } from "@noble/ciphers/utils.js";
//#region src/crypto/index.ts
const algorithm = {
name: "HMAC",
hash: "SHA-256"
};
const ENVELOPE_PREFIX = "$ba$";
function parseEnvelope(data) {
if (!data.startsWith(ENVELOPE_PREFIX)) return null;
const firstSep = 4;
const secondSep = data.indexOf("$", firstSep);
if (secondSep === -1) return null;
const version = parseInt(data.slice(firstSep, secondSep), 10);
if (!Number.isInteger(version) || version < 0) return null;
return {
version,
ciphertext: data.slice(secondSep + 1)
};
}
function formatEnvelope(version, ciphertext) {
return `${ENVELOPE_PREFIX}${version}$${ciphertext}`;
}
async function rawEncrypt(secret, data) {
const keyAsBytes = await createHash("SHA-256").digest(secret);
const dataAsBytes = utf8ToBytes(data);
return bytesToHex(managedNonce(xchacha20poly1305)(new Uint8Array(keyAsBytes)).encrypt(dataAsBytes));
}
async function rawDecrypt(secret, hex) {
const keyAsBytes = await createHash("SHA-256").digest(secret);
const dataAsBytes = hexToBytes(hex);
const chacha = managedNonce(xchacha20poly1305)(new Uint8Array(keyAsBytes));
return new TextDecoder().decode(chacha.decrypt(dataAsBytes));
}
const symmetricEncrypt = async ({ key, data }) => {
if (typeof key === "string") return rawEncrypt(key, data);
const secret = key.keys.get(key.currentVersion);
if (!secret) throw new Error(`Secret version ${key.currentVersion} not found in keys`);
const ciphertext = await rawEncrypt(secret, data);
return formatEnvelope(key.currentVersion, ciphertext);
};
const symmetricDecrypt = async ({ key, data }) => {
if (typeof key === "string") return rawDecrypt(key, data);
const envelope = parseEnvelope(data);
if (envelope) {
const secret = key.keys.get(envelope.version);
if (!secret) throw new Error(`Secret version ${envelope.version} not found in keys (key may have been retired)`);
return rawDecrypt(secret, envelope.ciphertext);
}
if (key.legacySecret) return rawDecrypt(key.legacySecret, data);
throw new Error("Cannot decrypt legacy bare-hex payload: no legacy secret available. Set BETTER_AUTH_SECRET for backwards compatibility.");
};
const getCryptoKey = async (secret) => {
const secretBuf = typeof secret === "string" ? new TextEncoder().encode(secret) : secret;
return await getWebcryptoSubtle().importKey("raw", secretBuf, algorithm, false, ["sign", "verify"]);
};
const makeSignature = async (value, secret) => {
const key = await getCryptoKey(secret);
const signature = await getWebcryptoSubtle().sign(algorithm.name, key, new TextEncoder().encode(value));
return btoa(String.fromCharCode(...new Uint8Array(signature)));
};
//#endregion
export { constantTimeEqual, formatEnvelope, generateRandomString, getCryptoKey, hashPassword, makeSignature, parseEnvelope, signJWT, symmetricDecodeJWT, symmetricDecrypt, symmetricEncodeJWT, symmetricEncrypt, verifyJWT, verifyPassword };