UNPKG

xotp

Version:

One-Time Password (HOTP/TOTP) library for Node.js, Deno and Bun, with support for Google Authenticator.

91 lines (90 loc) 3.67 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.HOTP = void 0; const node_crypto_1 = require("node:crypto"); const encoding_1 = require("./encoding"); const utils_1 = require("./utils"); class HOTP { constructor({ algorithm = this.defaults.algorithm, window = this.defaults.window, counter = this.defaults.counter, digits = this.defaults.digits, issuer = this.defaults.issuer, account = this.defaults.account, } = {}) { this.algorithm = this.defaults.algorithm; this.counter = this.defaults.counter; this.digits = this.defaults.digits; this.window = this.defaults.window; this.issuer = this.defaults.issuer; this.account = this.defaults.account; this.digits = digits; this.algorithm = algorithm; this.window = window; this.counter = counter; this.issuer = issuer; this.account = account; } get defaults() { return Object.freeze({ algorithm: "sha1", counter: 0, digits: 6, window: 1, issuer: "xotp", account: "", }); } generate({ secret, counter = ++this.counter, algorithm = this.algorithm, digits = this.digits, }) { const digest = (0, node_crypto_1.createHmac)(algorithm, secret.buffer) .update((0, encoding_1.uintEncode)(counter)) .digest(); const offset = digest[digest.byteLength - 1] & 0xf; const truncatedBinary = ((digest[offset] & 0x7f) << 24) | ((digest[offset + 1] & 0xff) << 16) | ((digest[offset + 2] & 0xff) << 8) | (digest[offset + 3] & 0xff); const token = truncatedBinary % Math.pow(10, digits); return (0, utils_1.padStart)(`${token}`, digits, "0"); } validate({ token, secret, counter = this.counter, algorithm = this.algorithm, digits = this.digits, window = this.window, }) { return (this.compare({ token, secret, counter, digits, algorithm, window, }) != null); } compare({ token, secret, counter = this.counter, digits = this.digits, algorithm = this.algorithm, window = this.window, }) { if (this.equals({ token, secret, counter, digits, algorithm })) return 0; for (let i = 1; i <= window; i++) { if (this.equals({ token, secret, counter: counter + i, digits, algorithm })) return i; if (this.equals({ token, secret, counter: counter - i, digits, algorithm })) return -i; } return null; } equals({ token, secret, counter = this.counter, algorithm = this.algorithm, digits = this.digits, }) { const generatedToken = this.generate({ secret, counter, algorithm, digits, }); return (0, node_crypto_1.timingSafeEqual)(Buffer.from(token), Buffer.from(generatedToken)); } keyUri({ secret, account, issuer = this.issuer, algorithm = this.algorithm, counter = this.counter, digits = this.digits, }) { const e = encodeURIComponent; const params = [ `secret=${e(secret.toString("base32").replace(/=+$/, ""))}`, `algorithm=${e(algorithm.toUpperCase())}`, `digits=${e(digits)}`, `counter=${e(counter)}`, ]; let label = account; if (issuer) { label = `${e(issuer)}:${e(label)}`; params.push(`issuer=${e(issuer)}`); } return `otpauth://hotp/${label}?${params.join("&")}`; } } exports.HOTP = HOTP;