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
JavaScript
"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;