UNPKG

@better-auth/utils

Version:

A collection of utilities for better-auth

89 lines (86 loc) 2.8 kB
import { base32 } from './base32.mjs'; import { createHMAC } from './hmac.mjs'; import './hex.mjs'; import './base64.mjs'; import './index.mjs'; const defaultPeriod = 30; const defaultDigits = 6; async function generateHOTP(secret, { counter, digits, hash = "SHA-1" }) { const _digits = digits ?? defaultDigits; if (_digits < 1 || _digits > 8) { throw new TypeError("Digits must be between 1 and 8"); } const buffer = new ArrayBuffer(8); new DataView(buffer).setBigUint64(0, BigInt(counter), false); const bytes = new Uint8Array(buffer); const hmacResult = new Uint8Array(await createHMAC(hash).sign(secret, bytes)); const offset = hmacResult[hmacResult.length - 1] & 15; const truncated = (hmacResult[offset] & 127) << 24 | (hmacResult[offset + 1] & 255) << 16 | (hmacResult[offset + 2] & 255) << 8 | hmacResult[offset + 3] & 255; const otp = truncated % 10 ** _digits; return otp.toString().padStart(_digits, "0"); } async function generateTOTP(secret, options) { const digits = options?.digits ?? defaultDigits; const period = options?.period ?? defaultPeriod; const milliseconds = period * 1e3; const counter = Math.floor(Date.now() / milliseconds); return await generateHOTP(secret, { counter, digits, hash: options?.hash }); } async function verifyTOTP(otp, { window = 1, digits = defaultDigits, secret, period = defaultPeriod }) { const milliseconds = period * 1e3; const counter = Math.floor(Date.now() / milliseconds); for (let i = -window; i <= window; i++) { const generatedOTP = await generateHOTP(secret, { counter: counter + i, digits }); if (otp === generatedOTP) { return true; } } return false; } function generateQRCode({ issuer, account, secret, digits = defaultDigits, period = defaultPeriod }) { const encodedIssuer = encodeURIComponent(issuer); const encodedAccountName = encodeURIComponent(account); const baseURI = `otpauth://totp/${encodedIssuer}:${encodedAccountName}`; const params = new URLSearchParams({ secret: base32.encode(secret, { padding: false }), issuer }); if (digits !== void 0) { params.set("digits", digits.toString()); } if (period !== void 0) { params.set("period", period.toString()); } return `${baseURI}?${params.toString()}`; } const createOTP = (secret, opts) => { const digits = opts?.digits ?? defaultDigits; const period = opts?.period ?? defaultPeriod; return { hotp: (counter) => generateHOTP(secret, { counter, digits }), totp: () => generateTOTP(secret, { digits, period }), verify: (otp, options) => verifyTOTP(otp, { secret, digits, period, ...options }), url: (issuer, account) => generateQRCode({ issuer, account, secret, digits, period }) }; }; export { createOTP };