import base32Decode from 'base32-decode'; import crypto from 'crypto'; const RFC_4648 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; export class TotpChecker { static async ValidateTotp(totpSecret, code, period, digits, algorithm) { if (!totpSecret && !code) return true; if ((!totpSecret && code) || (totpSecret && !code)) return false; let currentPeriod = TotpChecker.getCurrentPeriod(period); const input = Buffer.from(code.replace(/[^0-9]/g, "").trim()); return TotpChecker.DoGenerateCode(totpSecret, [currentPeriod - 1, currentPeriod, currentPeriod + 1], digits || 6, algorithm || "SHA-1") .find(x => { try { return crypto.timingSafeEqual(Buffer.from(x), input); } catch (err) { return false; } }) !== undefined; } static DoGenerateCode(totpSecret, period, digits, algorithm) { const secretAsBase64 = Buffer.from(base32Decode(totpSecret, "RFC4648")); return (Array.isArray(period) ? period : [period]).map(i => { // Encode period as a Buffer var periodAsBuffer = Buffer.alloc(8); periodAsBuffer.write((i.toString(16)).padStart(16, '0'), 0, 'hex'); return periodAsBuffer; }).map(period => { // Encode period using algorithm and secret return crypto.createHmac(algorithm, secretAsBase64) .update(period) .digest(); }).map(hash => { // Truncate output hash let offset = hash[hash.length - 1] & 0xF; var truncatedHash = ((hash[offset + 0] & 0x7F) << 24 | (hash[offset + 1] & 0xFF) << 16 | (hash[offset + 2] & 0xFF) << 8 | (hash[offset + 3] & 0xFF)) % (10 ** digits); return (`${truncatedHash}`.padStart(digits, '0')); }); } static getCurrentPeriod(period) { return Math.floor(Date.now() / ((period || 30) * 1000)); } static GenerateCode(totpSecret, period, digits, algorithm) { return TotpChecker.DoGenerateCode(totpSecret, this.getCurrentPeriod(period), digits || 6, algorithm || "SHA-1").shift(); } static GenerateUrl(optionsOrIssuer) { let options = typeof optionsOrIssuer === "string" ? { issuer: optionsOrIssuer } : optionsOrIssuer; options.digits = options.digits || 6; options.period = options.period || 30; options.algorithm = options.algorithm || "SHA-1"; options.label = encodeURIComponent(options.label || options.issuer); options.secretLength = options.secretLength || 13; const secretStr = Array.from(crypto.randomBytes(options.secretLength)).map(x => RFC_4648[x % RFC_4648.length]).join(""); return { url: `otpauth://totp/${options.issuer}?issuer=${options.issuer}&secret=${secretStr}&digits=${options.digits}&period=${options.period}&algorithm=${options.algorithm}`, secret: secretStr }; } } //# sourceMappingURL=totpChecker.js.map