UNPKG

better-auth

Version:

The most comprehensive authentication framework for TypeScript.

70 lines (69 loc) 3.37 kB
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 };