//! otpauth 9.3.1 | (c) Héctor Molinero Fernández | MIT | https://github.com/hectorm/otpauth
///
// @ts-nocheck
import * as crypto from 'node:crypto';
/**
* Converts an integer to an Uint8Array.
* @param {number} num Integer.
* @returns {Uint8Array} Uint8Array.
*/ const uintDecode = (num)=>{
const buf = new ArrayBuffer(8);
const arr = new Uint8Array(buf);
let acc = num;
for(let i = 7; i >= 0; i--){
if (acc === 0) break;
arr[i] = acc & 255;
acc -= arr[i];
acc /= 256;
}
return arr;
};
/**
* "globalThis" ponyfill.
* @see [A horrifying globalThis polyfill in universal JavaScript](https://mathiasbynens.be/notes/globalthis)
* @type {Object.}
*/ const globalScope = (()=>{
if (typeof globalThis === "object") return globalThis;
else {
Object.defineProperty(Object.prototype, "__GLOBALTHIS__", {
get () {
return this;
},
configurable: true
});
try {
// @ts-ignore
// eslint-disable-next-line no-undef
if (typeof __GLOBALTHIS__ !== "undefined") return __GLOBALTHIS__;
} finally{
// @ts-ignore
delete Object.prototype.__GLOBALTHIS__;
}
}
// Still unable to determine "globalThis", fall back to a naive method.
if (typeof self !== "undefined") return self;
else if (typeof window !== "undefined") return window;
else if (typeof global !== "undefined") return global;
return undefined;
})();
/**
* Calculates an HMAC digest.
* In Node.js, the command "openssl list -digest-algorithms" displays the available digest algorithms.
* @param {string} algorithm Algorithm.
* @param {Uint8Array} key Key.
* @param {Uint8Array} message Message.
* @returns {Uint8Array} Digest.
*/ const hmacDigest = (algorithm, key, message)=>{
if (crypto?.createHmac) {
const hmac = crypto.createHmac(algorithm, globalScope.Buffer.from(key));
hmac.update(globalScope.Buffer.from(message));
return hmac.digest();
} else {
throw new Error("Missing HMAC function");
}
};
/**
* RFC 4648 base32 alphabet without pad.
* @type {string}
*/ const ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
/**
* Converts a base32 string to an Uint8Array (RFC 4648).
* @see [LinusU/base32-decode](https://github.com/LinusU/base32-decode)
* @param {string} str Base32 string.
* @returns {Uint8Array} Uint8Array.
*/ const base32Decode = (str)=>{
// Canonicalize to all upper case and remove padding if it exists.
let end = str.length;
while(str[end - 1] === "=")--end;
const cstr = (end < str.length ? str.substring(0, end) : str).toUpperCase();
const buf = new ArrayBuffer(cstr.length * 5 / 8 | 0);
const arr = new Uint8Array(buf);
let bits = 0;
let value = 0;
let index = 0;
for(let i = 0; i < cstr.length; i++){
const idx = ALPHABET.indexOf(cstr[i]);
if (idx === -1) throw new TypeError(`Invalid character found: ${cstr[i]}`);
value = value << 5 | idx;
bits += 5;
if (bits >= 8) {
bits -= 8;
arr[index++] = value >>> bits;
}
}
return arr;
};
/**
* Converts an Uint8Array to a base32 string (RFC 4648).
* @see [LinusU/base32-encode](https://github.com/LinusU/base32-encode)
* @param {Uint8Array} arr Uint8Array.
* @returns {string} Base32 string.
*/ const base32Encode = (arr)=>{
let bits = 0;
let value = 0;
let str = "";
for(let i = 0; i < arr.length; i++){
value = value << 8 | arr[i];
bits += 8;
while(bits >= 5){
str += ALPHABET[value >>> bits - 5 & 31];
bits -= 5;
}
}
if (bits > 0) {
str += ALPHABET[value << 5 - bits & 31];
}
return str;
};
/**
* Converts a hexadecimal string to an Uint8Array.
* @param {string} str Hexadecimal string.
* @returns {Uint8Array} Uint8Array.
*/ const hexDecode = (str)=>{
const buf = new ArrayBuffer(str.length / 2);
const arr = new Uint8Array(buf);
for(let i = 0; i < str.length; i += 2){
arr[i / 2] = parseInt(str.substring(i, i + 2), 16);
}
return arr;
};
/**
* Converts an Uint8Array to a hexadecimal string.
* @param {Uint8Array} arr Uint8Array.
* @returns {string} Hexadecimal string.
*/ const hexEncode = (arr)=>{
let str = "";
for(let i = 0; i < arr.length; i++){
const hex = arr[i].toString(16);
if (hex.length === 1) str += "0";
str += hex;
}
return str.toUpperCase();
};
/**
* Converts a Latin-1 string to an Uint8Array.
* @param {string} str Latin-1 string.
* @returns {Uint8Array} Uint8Array.
*/ const latin1Decode = (str)=>{
const buf = new ArrayBuffer(str.length);
const arr = new Uint8Array(buf);
for(let i = 0; i < str.length; i++){
arr[i] = str.charCodeAt(i) & 0xff;
}
return arr;
};
/**
* Converts an Uint8Array to a Latin-1 string.
* @param {Uint8Array} arr Uint8Array.
* @returns {string} Latin-1 string.
*/ const latin1Encode = (arr)=>{
let str = "";
for(let i = 0; i < arr.length; i++){
str += String.fromCharCode(arr[i]);
}
return str;
};
/**
* TextEncoder instance.
* @type {TextEncoder|null}
*/ const ENCODER = globalScope.TextEncoder ? new globalScope.TextEncoder() : null;
/**
* TextDecoder instance.
* @type {TextDecoder|null}
*/ const DECODER = globalScope.TextDecoder ? new globalScope.TextDecoder() : null;
/**
* Converts an UTF-8 string to an Uint8Array.
* @param {string} str String.
* @returns {Uint8Array} Uint8Array.
*/ const utf8Decode = (str)=>{
if (!ENCODER) {
throw new Error("Encoding API not available");
}
return ENCODER.encode(str);
};
/**
* Converts an Uint8Array to an UTF-8 string.
* @param {Uint8Array} arr Uint8Array.
* @returns {string} String.
*/ const utf8Encode = (arr)=>{
if (!DECODER) {
throw new Error("Encoding API not available");
}
return DECODER.decode(arr);
};
/**
* Returns random bytes.
* @param {number} size Size.
* @returns {Uint8Array} Random bytes.
*/ const randomBytes = (size)=>{
if (crypto?.randomBytes) {
return crypto.randomBytes(size);
} else {
if (!globalScope.crypto?.getRandomValues) {
throw new Error("Cryptography API not available");
}
return globalScope.crypto.getRandomValues(new Uint8Array(size));
}
};
/**
* OTP secret key.
*/ class Secret {
/**
* Converts a Latin-1 string to a Secret object.
* @param {string} str Latin-1 string.
* @returns {Secret} Secret object.
*/ static fromLatin1(str) {
return new Secret({
buffer: latin1Decode(str).buffer
});
}
/**
* Converts an UTF-8 string to a Secret object.
* @param {string} str UTF-8 string.
* @returns {Secret} Secret object.
*/ static fromUTF8(str) {
return new Secret({
buffer: utf8Decode(str).buffer
});
}
/**
* Converts a base32 string to a Secret object.
* @param {string} str Base32 string.
* @returns {Secret} Secret object.
*/ static fromBase32(str) {
return new Secret({
buffer: base32Decode(str).buffer
});
}
/**
* Converts a hexadecimal string to a Secret object.
* @param {string} str Hexadecimal string.
* @returns {Secret} Secret object.
*/ static fromHex(str) {
return new Secret({
buffer: hexDecode(str).buffer
});
}
/**
* Secret key buffer.
* @deprecated For backward compatibility, the "bytes" property should be used instead.
* @type {ArrayBufferLike}
*/ get buffer() {
return this.bytes.buffer;
}
/**
* Latin-1 string representation of secret key.
* @type {string}
*/ get latin1() {
Object.defineProperty(this, "latin1", {
enumerable: true,
writable: false,
configurable: false,
value: latin1Encode(this.bytes)
});
return this.latin1;
}
/**
* UTF-8 string representation of secret key.
* @type {string}
*/ get utf8() {
Object.defineProperty(this, "utf8", {
enumerable: true,
writable: false,
configurable: false,
value: utf8Encode(this.bytes)
});
return this.utf8;
}
/**
* Base32 string representation of secret key.
* @type {string}
*/ get base32() {
Object.defineProperty(this, "base32", {
enumerable: true,
writable: false,
configurable: false,
value: base32Encode(this.bytes)
});
return this.base32;
}
/**
* Hexadecimal string representation of secret key.
* @type {string}
*/ get hex() {
Object.defineProperty(this, "hex", {
enumerable: true,
writable: false,
configurable: false,
value: hexEncode(this.bytes)
});
return this.hex;
}
/**
* Creates a secret key object.
* @param {Object} [config] Configuration options.
* @param {ArrayBufferLike} [config.buffer] Secret key buffer.
* @param {number} [config.size=20] Number of random bytes to generate, ignored if 'buffer' is provided.
*/ constructor({ buffer, size = 20 } = {}){
/**
* Secret key.
* @type {Uint8Array}
* @readonly
*/ this.bytes = typeof buffer === "undefined" ? randomBytes(size) : new Uint8Array(buffer);
// Prevent the "bytes" property from being modified.
Object.defineProperty(this, "bytes", {
enumerable: true,
writable: false,
configurable: false,
value: this.bytes
});
}
}
/**
* Returns true if a is equal to b, without leaking timing information that would allow an attacker to guess one of the values.
* @param {string} a String a.
* @param {string} b String b.
* @returns {boolean} Equality result.
*/ const timingSafeEqual = (a, b)=>{
if (crypto?.timingSafeEqual) {
return crypto.timingSafeEqual(globalScope.Buffer.from(a), globalScope.Buffer.from(b));
} else {
if (a.length !== b.length) {
throw new TypeError("Input strings must have the same length");
}
let i = -1;
let out = 0;
while(++i < a.length){
out |= a.charCodeAt(i) ^ b.charCodeAt(i);
}
return out === 0;
}
};
/**
* HOTP: An HMAC-based One-time Password Algorithm.
* @see [RFC 4226](https://tools.ietf.org/html/rfc4226)
*/ class HOTP {
/**
* Default configuration.
* @type {{
* issuer: string,
* label: string,
* issuerInLabel: boolean,
* algorithm: string,
* digits: number,
* counter: number
* window: number
* }}
*/ static get defaults() {
return {
issuer: "",
label: "OTPAuth",
issuerInLabel: true,
algorithm: "SHA1",
digits: 6,
counter: 0,
window: 1
};
}
/**
* Generates an HOTP token.
* @param {Object} config Configuration options.
* @param {Secret} config.secret Secret key.
* @param {string} [config.algorithm='SHA1'] HMAC hashing algorithm.
* @param {number} [config.digits=6] Token length.
* @param {number} [config.counter=0] Counter value.
* @returns {string} Token.
*/ static generate({ secret, algorithm = HOTP.defaults.algorithm, digits = HOTP.defaults.digits, counter = HOTP.defaults.counter }) {
const digest = hmacDigest(algorithm, secret.bytes, uintDecode(counter));
const offset = digest[digest.byteLength - 1] & 15;
const otp = ((digest[offset] & 127) << 24 | (digest[offset + 1] & 255) << 16 | (digest[offset + 2] & 255) << 8 | digest[offset + 3] & 255) % 10 ** digits;
return otp.toString().padStart(digits, "0");
}
/**
* Generates an HOTP token.
* @param {Object} [config] Configuration options.
* @param {number} [config.counter=this.counter++] Counter value.
* @returns {string} Token.
*/ generate({ counter = this.counter++ } = {}) {
return HOTP.generate({
secret: this.secret,
algorithm: this.algorithm,
digits: this.digits,
counter
});
}
/**
* Validates an HOTP token.
* @param {Object} config Configuration options.
* @param {string} config.token Token value.
* @param {Secret} config.secret Secret key.
* @param {string} [config.algorithm='SHA1'] HMAC hashing algorithm.
* @param {number} config.digits Token length.
* @param {number} [config.counter=0] Counter value.
* @param {number} [config.window=1] Window of counter values to test.
* @returns {number|null} Token delta or null if it is not found in the search window, in which case it should be considered invalid.
*/ static validate({ token, secret, algorithm, digits, counter = HOTP.defaults.counter, window = HOTP.defaults.window }) {
// Return early if the token length does not match the digit number.
if (token.length !== digits) return null;
let delta = null;
const check = (/** @type {number} */ i)=>{
const generatedToken = HOTP.generate({
secret,
algorithm,
digits,
counter: i
});
if (timingSafeEqual(token, generatedToken)) {
delta = i - counter;
}
};
check(counter);
for(let i = 1; i <= window && delta === null; ++i){
check(counter - i);
if (delta !== null) break;
check(counter + i);
if (delta !== null) break;
}
return delta;
}
/**
* Validates an HOTP token.
* @param {Object} config Configuration options.
* @param {string} config.token Token value.
* @param {number} [config.counter=this.counter] Counter value.
* @param {number} [config.window=1] Window of counter values to test.
* @returns {number|null} Token delta or null if it is not found in the search window, in which case it should be considered invalid.
*/ validate({ token, counter = this.counter, window }) {
return HOTP.validate({
token,
secret: this.secret,
algorithm: this.algorithm,
digits: this.digits,
counter,
window
});
}
/**
* Returns a Google Authenticator key URI.
* @returns {string} URI.
*/ toString() {
const e = encodeURIComponent;
return "otpauth://hotp/" + `${this.issuer.length > 0 ? this.issuerInLabel ? `${e(this.issuer)}:${e(this.label)}?issuer=${e(this.issuer)}&` : `${e(this.label)}?issuer=${e(this.issuer)}&` : `${e(this.label)}?`}` + `secret=${e(this.secret.base32)}&` + `algorithm=${e(this.algorithm)}&` + `digits=${e(this.digits)}&` + `counter=${e(this.counter)}`;
}
/**
* Creates an HOTP object.
* @param {Object} [config] Configuration options.
* @param {string} [config.issuer=''] Account provider.
* @param {string} [config.label='OTPAuth'] Account label.
* @param {boolean} [config.issuerInLabel=true] Include issuer prefix in label.
* @param {Secret|string} [config.secret=Secret] Secret key.
* @param {string} [config.algorithm='SHA1'] HMAC hashing algorithm.
* @param {number} [config.digits=6] Token length.
* @param {number} [config.counter=0] Initial counter value.
*/ constructor({ issuer = HOTP.defaults.issuer, label = HOTP.defaults.label, issuerInLabel = HOTP.defaults.issuerInLabel, secret = new Secret(), algorithm = HOTP.defaults.algorithm, digits = HOTP.defaults.digits, counter = HOTP.defaults.counter } = {}){
/**
* Account provider.
* @type {string}
*/ this.issuer = issuer;
/**
* Account label.
* @type {string}
*/ this.label = label;
/**
* Include issuer prefix in label.
* @type {boolean}
*/ this.issuerInLabel = issuerInLabel;
/**
* Secret key.
* @type {Secret}
*/ this.secret = typeof secret === "string" ? Secret.fromBase32(secret) : secret;
/**
* HMAC hashing algorithm.
* @type {string}
*/ this.algorithm = algorithm.toUpperCase();
/**
* Token length.
* @type {number}
*/ this.digits = digits;
/**
* Initial counter value.
* @type {number}
*/ this.counter = counter;
}
}
/**
* TOTP: Time-Based One-Time Password Algorithm.
* @see [RFC 6238](https://tools.ietf.org/html/rfc6238)
*/ class TOTP {
/**
* Default configuration.
* @type {{
* issuer: string,
* label: string,
* issuerInLabel: boolean,
* algorithm: string,
* digits: number,
* period: number
* window: number
* }}
*/ static get defaults() {
return {
issuer: "",
label: "OTPAuth",
issuerInLabel: true,
algorithm: "SHA1",
digits: 6,
period: 30,
window: 1
};
}
/**
* Generates a TOTP token.
* @param {Object} config Configuration options.
* @param {Secret} config.secret Secret key.
* @param {string} [config.algorithm='SHA1'] HMAC hashing algorithm.
* @param {number} [config.digits=6] Token length.
* @param {number} [config.period=30] Token time-step duration.
* @param {number} [config.timestamp=Date.now] Timestamp value in milliseconds.
* @returns {string} Token.
*/ static generate({ secret, algorithm, digits, period = TOTP.defaults.period, timestamp = Date.now() }) {
return HOTP.generate({
secret,
algorithm,
digits,
counter: Math.floor(timestamp / 1000 / period)
});
}
/**
* Generates a TOTP token.
* @param {Object} [config] Configuration options.
* @param {number} [config.timestamp=Date.now] Timestamp value in milliseconds.
* @returns {string} Token.
*/ generate({ timestamp = Date.now() } = {}) {
return TOTP.generate({
secret: this.secret,
algorithm: this.algorithm,
digits: this.digits,
period: this.period,
timestamp
});
}
/**
* Validates a TOTP token.
* @param {Object} config Configuration options.
* @param {string} config.token Token value.
* @param {Secret} config.secret Secret key.
* @param {string} [config.algorithm='SHA1'] HMAC hashing algorithm.
* @param {number} config.digits Token length.
* @param {number} [config.period=30] Token time-step duration.
* @param {number} [config.timestamp=Date.now] Timestamp value in milliseconds.
* @param {number} [config.window=1] Window of counter values to test.
* @returns {number|null} Token delta or null if it is not found in the search window, in which case it should be considered invalid.
*/ static validate({ token, secret, algorithm, digits, period = TOTP.defaults.period, timestamp = Date.now(), window }) {
return HOTP.validate({
token,
secret,
algorithm,
digits,
counter: Math.floor(timestamp / 1000 / period),
window
});
}
/**
* Validates a TOTP token.
* @param {Object} config Configuration options.
* @param {string} config.token Token value.
* @param {number} [config.timestamp=Date.now] Timestamp value in milliseconds.
* @param {number} [config.window=1] Window of counter values to test.
* @returns {number|null} Token delta or null if it is not found in the search window, in which case it should be considered invalid.
*/ validate({ token, timestamp, window }) {
return TOTP.validate({
token,
secret: this.secret,
algorithm: this.algorithm,
digits: this.digits,
period: this.period,
timestamp,
window
});
}
/**
* Returns a Google Authenticator key URI.
* @returns {string} URI.
*/ toString() {
const e = encodeURIComponent;
return "otpauth://totp/" + `${this.issuer.length > 0 ? this.issuerInLabel ? `${e(this.issuer)}:${e(this.label)}?issuer=${e(this.issuer)}&` : `${e(this.label)}?issuer=${e(this.issuer)}&` : `${e(this.label)}?`}` + `secret=${e(this.secret.base32)}&` + `algorithm=${e(this.algorithm)}&` + `digits=${e(this.digits)}&` + `period=${e(this.period)}`;
}
/**
* Creates a TOTP object.
* @param {Object} [config] Configuration options.
* @param {string} [config.issuer=''] Account provider.
* @param {string} [config.label='OTPAuth'] Account label.
* @param {boolean} [config.issuerInLabel=true] Include issuer prefix in label.
* @param {Secret|string} [config.secret=Secret] Secret key.
* @param {string} [config.algorithm='SHA1'] HMAC hashing algorithm.
* @param {number} [config.digits=6] Token length.
* @param {number} [config.period=30] Token time-step duration.
*/ constructor({ issuer = TOTP.defaults.issuer, label = TOTP.defaults.label, issuerInLabel = TOTP.defaults.issuerInLabel, secret = new Secret(), algorithm = TOTP.defaults.algorithm, digits = TOTP.defaults.digits, period = TOTP.defaults.period } = {}){
/**
* Account provider.
* @type {string}
*/ this.issuer = issuer;
/**
* Account label.
* @type {string}
*/ this.label = label;
/**
* Include issuer prefix in label.
* @type {boolean}
*/ this.issuerInLabel = issuerInLabel;
/**
* Secret key.
* @type {Secret}
*/ this.secret = typeof secret === "string" ? Secret.fromBase32(secret) : secret;
/**
* HMAC hashing algorithm.
* @type {string}
*/ this.algorithm = algorithm.toUpperCase();
/**
* Token length.
* @type {number}
*/ this.digits = digits;
/**
* Token time-step duration.
* @type {number}
*/ this.period = period;
}
}
/**
* Key URI regex (otpauth://TYPE/[ISSUER:]LABEL?PARAMETERS).
* @type {RegExp}
*/ const OTPURI_REGEX = /^otpauth:\/\/([ht]otp)\/(.+)\?([A-Z0-9.~_-]+=[^?&]*(?:&[A-Z0-9.~_-]+=[^?&]*)*)$/i;
/**
* RFC 4648 base32 alphabet with pad.
* @type {RegExp}
*/ const SECRET_REGEX = /^[2-7A-Z]+=*$/i;
/**
* Regex for supported algorithms.
* @type {RegExp}
*/ const ALGORITHM_REGEX = /^SHA(?:1|224|256|384|512|3-224|3-256|3-384|3-512)$/i;
/**
* Integer regex.
* @type {RegExp}
*/ const INTEGER_REGEX = /^[+-]?\d+$/;
/**
* Positive integer regex.
* @type {RegExp}
*/ const POSITIVE_INTEGER_REGEX = /^\+?[1-9]\d*$/;
/**
* HOTP/TOTP object/string conversion.
* @see [Key URI Format](https://github.com/google/google-authenticator/wiki/Key-Uri-Format)
*/ class URI {
/**
* Parses a Google Authenticator key URI and returns an HOTP/TOTP object.
* @param {string} uri Google Authenticator Key URI.
* @returns {HOTP|TOTP} HOTP/TOTP object.
*/ static parse(uri) {
let uriGroups;
try {
uriGroups = uri.match(OTPURI_REGEX);
// eslint-disable-next-line no-unused-vars
} catch (_) {
/* Handled below */ }
if (!Array.isArray(uriGroups)) {
throw new URIError("Invalid URI format");
}
// Extract URI groups.
const uriType = uriGroups[1].toLowerCase();
const uriLabel = uriGroups[2].split(/(?::|%3A) *(.+)/i, 2).map(decodeURIComponent);
/** @type {Object.} */ const uriParams = uriGroups[3].split("&").reduce((acc, cur)=>{
const pairArr = cur.split(/=(.*)/, 2).map(decodeURIComponent);
const pairKey = pairArr[0].toLowerCase();
const pairVal = pairArr[1];
/** @type {Object.} */ const pairAcc = acc;
pairAcc[pairKey] = pairVal;
return pairAcc;
}, {});
// 'OTP' will be instantiated with 'config' argument.
let OTP;
const config = {};
if (uriType === "hotp") {
OTP = HOTP;
// Counter: required
if (typeof uriParams.counter !== "undefined" && INTEGER_REGEX.test(uriParams.counter)) {
config.counter = parseInt(uriParams.counter, 10);
} else {
throw new TypeError("Missing or invalid 'counter' parameter");
}
} else if (uriType === "totp") {
OTP = TOTP;
// Period: optional
if (typeof uriParams.period !== "undefined") {
if (POSITIVE_INTEGER_REGEX.test(uriParams.period)) {
config.period = parseInt(uriParams.period, 10);
} else {
throw new TypeError("Invalid 'period' parameter");
}
}
} else {
throw new TypeError("Unknown OTP type");
}
// Label: required
// Issuer: optional
if (typeof uriParams.issuer !== "undefined") {
config.issuer = uriParams.issuer;
}
if (uriLabel.length === 2) {
config.label = uriLabel[1];
if (typeof config.issuer === "undefined" || config.issuer === "") {
config.issuer = uriLabel[0];
} else if (uriLabel[0] === "") {
config.issuerInLabel = false;
}
} else {
config.label = uriLabel[0];
if (typeof config.issuer !== "undefined" && config.issuer !== "") {
config.issuerInLabel = false;
}
}
// Secret: required
if (typeof uriParams.secret !== "undefined" && SECRET_REGEX.test(uriParams.secret)) {
config.secret = uriParams.secret;
} else {
throw new TypeError("Missing or invalid 'secret' parameter");
}
// Algorithm: optional
if (typeof uriParams.algorithm !== "undefined") {
if (ALGORITHM_REGEX.test(uriParams.algorithm)) {
config.algorithm = uriParams.algorithm;
} else {
throw new TypeError("Invalid 'algorithm' parameter");
}
}
// Digits: optional
if (typeof uriParams.digits !== "undefined") {
if (POSITIVE_INTEGER_REGEX.test(uriParams.digits)) {
config.digits = parseInt(uriParams.digits, 10);
} else {
throw new TypeError("Invalid 'digits' parameter");
}
}
return new OTP(config);
}
/**
* Converts an HOTP/TOTP object to a Google Authenticator key URI.
* @param {HOTP|TOTP} otp HOTP/TOTP object.
* @returns {string} Google Authenticator Key URI.
*/ static stringify(otp) {
if (otp instanceof HOTP || otp instanceof TOTP) {
return otp.toString();
}
throw new TypeError("Invalid 'HOTP/TOTP' object");
}
}
/**
* Library version.
* @type {string}
*/ const version = "9.3.1";
export { HOTP, Secret, TOTP, URI, version };