123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808 |
- //! otpauth 9.3.1 | (c) Héctor Molinero Fernández | MIT | https://github.com/hectorm/otpauth
- /// <reference types="./otpauth.d.ts" />
- // @ts-nocheck
- 'use strict';
- var crypto = require('node:crypto');
- function _interopNamespaceDefault(e) {
- var n = Object.create(null);
- if (e) {
- Object.keys(e).forEach(function (k) {
- if (k !== 'default') {
- var d = Object.getOwnPropertyDescriptor(e, k);
- Object.defineProperty(n, k, d.get ? d : {
- enumerable: true,
- get: function () { return e[k]; }
- });
- }
- });
- }
- n.default = e;
- return Object.freeze(n);
- }
- var crypto__namespace = /*#__PURE__*/_interopNamespaceDefault(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.<string, *>}
- */ 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__namespace?.createHmac) {
- const hmac = crypto__namespace.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__namespace?.randomBytes) {
- return crypto__namespace.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__namespace?.timingSafeEqual) {
- return crypto__namespace.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.<string, string>} */ 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.<string, string>} */ 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";
- exports.HOTP = HOTP;
- exports.Secret = Secret;
- exports.TOTP = TOTP;
- exports.URI = URI;
- exports.version = version;
|