@better-auth/utils
Version:
A collection of utilities for better-auth
89 lines (86 loc) • 2.8 kB
JavaScript
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 };