const crypto = require('node:crypto');
const B32 = require('base32.js');
const QRCode = require('qrcode-svg');
-const { promisify } = require('util');
+const { promisify } = require('node:util');
const randomBytesAsync = promisify(crypto.randomBytes);
class HMACBasedOneTimePassword {
/**
*
- * @param {Object} options
- * @param {Buffer|String} options.key
- * @param {String=} options.keyEncoding
- * @param {Number=} options.codeLength
- * @param {BigInt|Number|String=} options.counter
- * @param {String=} options.algorithm
+ * @param {object} options options
+ * @param {Buffer|string} options.key key
+ * @param {string=} options.keyEncoding key encoding
+ * @param {number=} options.codeLength digits in code
+ * @param {bigint|number|string=} options.counter initial counter value
+ * @param {string=} options.algorithm algorithm
*/
constructor(options) {
Object.assign(this, this.constructor._defaultOptions, options);
/**
* The type used when constructing the otpauth URI.
+ * @returns {string} otpauth type
*/
static get _type() {
return 'hotp';
return {
codeLength: 6,
counter: 0n,
- keyEncoding: 'ascii',
+ keyEncoding: 'hex',
algorithm: 'sha1',
};
}
/**
*
- * @param {String} algorithm
- * @returns {Number}
+ * @param {string} algorithm algorithm
+ * @returns {number} bytes
*/
static _algorithmKeyLength(algorithm) {
if (!(this._algorithmKeyLengths[algorithm])) { // eslint-disable-line security/detect-object-injection
/**
*
- * @param {BigInt} count
- * @returns {Buffer}
+ * @param {bigint} count counter value
+ * @returns {Buffer} hmac
*/
_hmac(count) {
const counterBuffer = Buffer.alloc(8);
/**
*
- * @param {BigInt} count
- * @returns {Number}
+ * @param {bigint} count counter value
+ * @returns {number} partial extracted hmac
*/
_truncate(count) {
const digest = this._hmac(count);
/**
*
- * @param {BigInt=} count
- * @returns {String}
+ * @param {bigint=} count counter value
+ * @returns {string} code
*/
generate(count) {
const code = this._truncate(count ?? this.counter);
/**
* Check a code against expected.
- * @param {String} hotp
- * @param {BigInt=} count
- * @returns {Boolean}
+ * @param {string} hotp code to check
+ * @param {bigint=} count counter value
+ * @returns {boolean} is valid
*/
validate(hotp, count) {
const codeString = this.generate(count);
/**
* Make a new key, of the assigned encoding.
- * @param {String=} encoding
- * @param {String=} algorithm
- * @returns {Promise<String|Buffer>}
+ * @param {string=} algorithm algorithm
+ * @param {string=} encoding encoding
+ * @returns {Promise<string|Buffer>} key
*/
static async createKey(algorithm = 'sha1', encoding = 'hex') {
const key = await randomBytesAsync(this._algorithmKeyLength(algorithm));
}
/**
- * @typedef {Object} OtpAuthData
- * @property {String} secret
- * @property {String} svg
- * @property {String} uri
+ * @typedef {object} OtpAuthData
+ * @property {string} secret secret
+ * @property {string} svg svg of qr otpauth uri
+ * @property {string} uri uri
*/
/**
* Given a key, return data suitable for an authenticator client to ingest
* it, as a qrcode SVG, the otpauth uri encoded in the qrcode SVG, and the
* secret key encoded as base32.
- * @param {Object} options
- * @param {String} options.accountname
- * @param {BigInt=} options.counter
- * @param {String=} options.issuer
- * @param {String=} options.scheme
- * @param {String=} options.type
- * @param {String=} options.algorithm
- * @param {String=} options.digits
- * @param {Number=} options.svgPadding
- * @param {Number=} options.svgWidth
- * @param {Number=} options.svgHeight
- * @param {String=} options.svgFg
- * @param {String=} options.svgBg
- * @param {String=} options.svgEcl
- * @param {Boolean=} options.join
- * @param {Boolean=} options.xmlDeclaration
- * @param {String=} options.container
- * @param {String|Buffer} key
- * @param {String=} keyEncoding
- * @returns {OtpAuthData}
+ * @param {object} options options
+ * @param {string} options.accountname descriptive account name to include in uri
+ * @param {bigint=} options.counter initial counter value
+ * @param {string=} options.issuer issuer
+ * @param {string=} options.scheme scheme
+ * @param {string=} options.type type
+ * @param {string=} options.algorithm algorithm
+ * @param {string=} options.digits digits in code
+ * @param {number=} options.svgPadding qr svg padding
+ * @param {number=} options.svgWidth qr svg width
+ * @param {number=} options.svgHeight qr svg height
+ * @param {string=} options.svgFg qr svg foreground
+ * @param {string=} options.svgBg qr svg background
+ * @param {string=} options.svgEcl qr svg encoding resiliancy
+ * @param {boolean=} options.join qr svg construction option
+ * @param {boolean=} options.xmlDeclaration qr svg option
+ * @param {string=} options.container qr svg option
+ * @param {string|Buffer} key secret key
+ * @param {string=} keyEncoding secret key encoding
+ * @returns {OtpAuthData} otp auth
*/
static createKeySVG(options, key, keyEncoding = 'hex') {
// Normalize key to base32 ABCDEFGHIJKLMNOPQRSTUVWXYZ234567 string (rfc4648)
/**
* Render parameters as an otpauth URI.
- * @param {Object} options
- * @param {String} options.accountname
- * @param {String} options.secret base32
- * @param {BigInt=} options.counter
- * @param {String=} options.issuer
- * @param {String=} options.scheme
- * @param {String=} options.type
- * @param {String=} options.algorithm
- * @param {String=} options.digits
+ * @param {object} options options
+ * @param {string} options.accountname account name
+ * @param {string} options.secret base32 encoded secret
+ * @param {bigint=} options.counter counter value
+ * @param {string=} options.issuer issuer
+ * @param {string=} options.scheme scheme
+ * @param {string=} options.type otp auth type
+ * @param {string=} options.algorithm algorithm
+ * @param {string=} options.digits digits in code
+ * @returns {string} url
*/
static _qrURI(options) {
const {
static get _qrURIDefaultOptions() {
return {
+ issuer: '',
scheme: 'otpauth',
type: this._type,
};