otpauth.node.cjs 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808
  1. //! otpauth 9.3.1 | (c) Héctor Molinero Fernández | MIT | https://github.com/hectorm/otpauth
  2. /// <reference types="./otpauth.d.ts" />
  3. // @ts-nocheck
  4. 'use strict';
  5. var crypto = require('node:crypto');
  6. function _interopNamespaceDefault(e) {
  7. var n = Object.create(null);
  8. if (e) {
  9. Object.keys(e).forEach(function (k) {
  10. if (k !== 'default') {
  11. var d = Object.getOwnPropertyDescriptor(e, k);
  12. Object.defineProperty(n, k, d.get ? d : {
  13. enumerable: true,
  14. get: function () { return e[k]; }
  15. });
  16. }
  17. });
  18. }
  19. n.default = e;
  20. return Object.freeze(n);
  21. }
  22. var crypto__namespace = /*#__PURE__*/_interopNamespaceDefault(crypto);
  23. /**
  24. * Converts an integer to an Uint8Array.
  25. * @param {number} num Integer.
  26. * @returns {Uint8Array} Uint8Array.
  27. */ const uintDecode = (num)=>{
  28. const buf = new ArrayBuffer(8);
  29. const arr = new Uint8Array(buf);
  30. let acc = num;
  31. for(let i = 7; i >= 0; i--){
  32. if (acc === 0) break;
  33. arr[i] = acc & 255;
  34. acc -= arr[i];
  35. acc /= 256;
  36. }
  37. return arr;
  38. };
  39. /**
  40. * "globalThis" ponyfill.
  41. * @see [A horrifying globalThis polyfill in universal JavaScript](https://mathiasbynens.be/notes/globalthis)
  42. * @type {Object.<string, *>}
  43. */ const globalScope = (()=>{
  44. if (typeof globalThis === "object") return globalThis;
  45. else {
  46. Object.defineProperty(Object.prototype, "__GLOBALTHIS__", {
  47. get () {
  48. return this;
  49. },
  50. configurable: true
  51. });
  52. try {
  53. // @ts-ignore
  54. // eslint-disable-next-line no-undef
  55. if (typeof __GLOBALTHIS__ !== "undefined") return __GLOBALTHIS__;
  56. } finally{
  57. // @ts-ignore
  58. delete Object.prototype.__GLOBALTHIS__;
  59. }
  60. }
  61. // Still unable to determine "globalThis", fall back to a naive method.
  62. if (typeof self !== "undefined") return self;
  63. else if (typeof window !== "undefined") return window;
  64. else if (typeof global !== "undefined") return global;
  65. return undefined;
  66. })();
  67. /**
  68. * Calculates an HMAC digest.
  69. * In Node.js, the command "openssl list -digest-algorithms" displays the available digest algorithms.
  70. * @param {string} algorithm Algorithm.
  71. * @param {Uint8Array} key Key.
  72. * @param {Uint8Array} message Message.
  73. * @returns {Uint8Array} Digest.
  74. */ const hmacDigest = (algorithm, key, message)=>{
  75. if (crypto__namespace?.createHmac) {
  76. const hmac = crypto__namespace.createHmac(algorithm, globalScope.Buffer.from(key));
  77. hmac.update(globalScope.Buffer.from(message));
  78. return hmac.digest();
  79. } else {
  80. throw new Error("Missing HMAC function");
  81. }
  82. };
  83. /**
  84. * RFC 4648 base32 alphabet without pad.
  85. * @type {string}
  86. */ const ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
  87. /**
  88. * Converts a base32 string to an Uint8Array (RFC 4648).
  89. * @see [LinusU/base32-decode](https://github.com/LinusU/base32-decode)
  90. * @param {string} str Base32 string.
  91. * @returns {Uint8Array} Uint8Array.
  92. */ const base32Decode = (str)=>{
  93. // Canonicalize to all upper case and remove padding if it exists.
  94. let end = str.length;
  95. while(str[end - 1] === "=")--end;
  96. const cstr = (end < str.length ? str.substring(0, end) : str).toUpperCase();
  97. const buf = new ArrayBuffer(cstr.length * 5 / 8 | 0);
  98. const arr = new Uint8Array(buf);
  99. let bits = 0;
  100. let value = 0;
  101. let index = 0;
  102. for(let i = 0; i < cstr.length; i++){
  103. const idx = ALPHABET.indexOf(cstr[i]);
  104. if (idx === -1) throw new TypeError(`Invalid character found: ${cstr[i]}`);
  105. value = value << 5 | idx;
  106. bits += 5;
  107. if (bits >= 8) {
  108. bits -= 8;
  109. arr[index++] = value >>> bits;
  110. }
  111. }
  112. return arr;
  113. };
  114. /**
  115. * Converts an Uint8Array to a base32 string (RFC 4648).
  116. * @see [LinusU/base32-encode](https://github.com/LinusU/base32-encode)
  117. * @param {Uint8Array} arr Uint8Array.
  118. * @returns {string} Base32 string.
  119. */ const base32Encode = (arr)=>{
  120. let bits = 0;
  121. let value = 0;
  122. let str = "";
  123. for(let i = 0; i < arr.length; i++){
  124. value = value << 8 | arr[i];
  125. bits += 8;
  126. while(bits >= 5){
  127. str += ALPHABET[value >>> bits - 5 & 31];
  128. bits -= 5;
  129. }
  130. }
  131. if (bits > 0) {
  132. str += ALPHABET[value << 5 - bits & 31];
  133. }
  134. return str;
  135. };
  136. /**
  137. * Converts a hexadecimal string to an Uint8Array.
  138. * @param {string} str Hexadecimal string.
  139. * @returns {Uint8Array} Uint8Array.
  140. */ const hexDecode = (str)=>{
  141. const buf = new ArrayBuffer(str.length / 2);
  142. const arr = new Uint8Array(buf);
  143. for(let i = 0; i < str.length; i += 2){
  144. arr[i / 2] = parseInt(str.substring(i, i + 2), 16);
  145. }
  146. return arr;
  147. };
  148. /**
  149. * Converts an Uint8Array to a hexadecimal string.
  150. * @param {Uint8Array} arr Uint8Array.
  151. * @returns {string} Hexadecimal string.
  152. */ const hexEncode = (arr)=>{
  153. let str = "";
  154. for(let i = 0; i < arr.length; i++){
  155. const hex = arr[i].toString(16);
  156. if (hex.length === 1) str += "0";
  157. str += hex;
  158. }
  159. return str.toUpperCase();
  160. };
  161. /**
  162. * Converts a Latin-1 string to an Uint8Array.
  163. * @param {string} str Latin-1 string.
  164. * @returns {Uint8Array} Uint8Array.
  165. */ const latin1Decode = (str)=>{
  166. const buf = new ArrayBuffer(str.length);
  167. const arr = new Uint8Array(buf);
  168. for(let i = 0; i < str.length; i++){
  169. arr[i] = str.charCodeAt(i) & 0xff;
  170. }
  171. return arr;
  172. };
  173. /**
  174. * Converts an Uint8Array to a Latin-1 string.
  175. * @param {Uint8Array} arr Uint8Array.
  176. * @returns {string} Latin-1 string.
  177. */ const latin1Encode = (arr)=>{
  178. let str = "";
  179. for(let i = 0; i < arr.length; i++){
  180. str += String.fromCharCode(arr[i]);
  181. }
  182. return str;
  183. };
  184. /**
  185. * TextEncoder instance.
  186. * @type {TextEncoder|null}
  187. */ const ENCODER = globalScope.TextEncoder ? new globalScope.TextEncoder() : null;
  188. /**
  189. * TextDecoder instance.
  190. * @type {TextDecoder|null}
  191. */ const DECODER = globalScope.TextDecoder ? new globalScope.TextDecoder() : null;
  192. /**
  193. * Converts an UTF-8 string to an Uint8Array.
  194. * @param {string} str String.
  195. * @returns {Uint8Array} Uint8Array.
  196. */ const utf8Decode = (str)=>{
  197. if (!ENCODER) {
  198. throw new Error("Encoding API not available");
  199. }
  200. return ENCODER.encode(str);
  201. };
  202. /**
  203. * Converts an Uint8Array to an UTF-8 string.
  204. * @param {Uint8Array} arr Uint8Array.
  205. * @returns {string} String.
  206. */ const utf8Encode = (arr)=>{
  207. if (!DECODER) {
  208. throw new Error("Encoding API not available");
  209. }
  210. return DECODER.decode(arr);
  211. };
  212. /**
  213. * Returns random bytes.
  214. * @param {number} size Size.
  215. * @returns {Uint8Array} Random bytes.
  216. */ const randomBytes = (size)=>{
  217. if (crypto__namespace?.randomBytes) {
  218. return crypto__namespace.randomBytes(size);
  219. } else {
  220. if (!globalScope.crypto?.getRandomValues) {
  221. throw new Error("Cryptography API not available");
  222. }
  223. return globalScope.crypto.getRandomValues(new Uint8Array(size));
  224. }
  225. };
  226. /**
  227. * OTP secret key.
  228. */ class Secret {
  229. /**
  230. * Converts a Latin-1 string to a Secret object.
  231. * @param {string} str Latin-1 string.
  232. * @returns {Secret} Secret object.
  233. */ static fromLatin1(str) {
  234. return new Secret({
  235. buffer: latin1Decode(str).buffer
  236. });
  237. }
  238. /**
  239. * Converts an UTF-8 string to a Secret object.
  240. * @param {string} str UTF-8 string.
  241. * @returns {Secret} Secret object.
  242. */ static fromUTF8(str) {
  243. return new Secret({
  244. buffer: utf8Decode(str).buffer
  245. });
  246. }
  247. /**
  248. * Converts a base32 string to a Secret object.
  249. * @param {string} str Base32 string.
  250. * @returns {Secret} Secret object.
  251. */ static fromBase32(str) {
  252. return new Secret({
  253. buffer: base32Decode(str).buffer
  254. });
  255. }
  256. /**
  257. * Converts a hexadecimal string to a Secret object.
  258. * @param {string} str Hexadecimal string.
  259. * @returns {Secret} Secret object.
  260. */ static fromHex(str) {
  261. return new Secret({
  262. buffer: hexDecode(str).buffer
  263. });
  264. }
  265. /**
  266. * Secret key buffer.
  267. * @deprecated For backward compatibility, the "bytes" property should be used instead.
  268. * @type {ArrayBufferLike}
  269. */ get buffer() {
  270. return this.bytes.buffer;
  271. }
  272. /**
  273. * Latin-1 string representation of secret key.
  274. * @type {string}
  275. */ get latin1() {
  276. Object.defineProperty(this, "latin1", {
  277. enumerable: true,
  278. writable: false,
  279. configurable: false,
  280. value: latin1Encode(this.bytes)
  281. });
  282. return this.latin1;
  283. }
  284. /**
  285. * UTF-8 string representation of secret key.
  286. * @type {string}
  287. */ get utf8() {
  288. Object.defineProperty(this, "utf8", {
  289. enumerable: true,
  290. writable: false,
  291. configurable: false,
  292. value: utf8Encode(this.bytes)
  293. });
  294. return this.utf8;
  295. }
  296. /**
  297. * Base32 string representation of secret key.
  298. * @type {string}
  299. */ get base32() {
  300. Object.defineProperty(this, "base32", {
  301. enumerable: true,
  302. writable: false,
  303. configurable: false,
  304. value: base32Encode(this.bytes)
  305. });
  306. return this.base32;
  307. }
  308. /**
  309. * Hexadecimal string representation of secret key.
  310. * @type {string}
  311. */ get hex() {
  312. Object.defineProperty(this, "hex", {
  313. enumerable: true,
  314. writable: false,
  315. configurable: false,
  316. value: hexEncode(this.bytes)
  317. });
  318. return this.hex;
  319. }
  320. /**
  321. * Creates a secret key object.
  322. * @param {Object} [config] Configuration options.
  323. * @param {ArrayBufferLike} [config.buffer] Secret key buffer.
  324. * @param {number} [config.size=20] Number of random bytes to generate, ignored if 'buffer' is provided.
  325. */ constructor({ buffer, size = 20 } = {}){
  326. /**
  327. * Secret key.
  328. * @type {Uint8Array}
  329. * @readonly
  330. */ this.bytes = typeof buffer === "undefined" ? randomBytes(size) : new Uint8Array(buffer);
  331. // Prevent the "bytes" property from being modified.
  332. Object.defineProperty(this, "bytes", {
  333. enumerable: true,
  334. writable: false,
  335. configurable: false,
  336. value: this.bytes
  337. });
  338. }
  339. }
  340. /**
  341. * Returns true if a is equal to b, without leaking timing information that would allow an attacker to guess one of the values.
  342. * @param {string} a String a.
  343. * @param {string} b String b.
  344. * @returns {boolean} Equality result.
  345. */ const timingSafeEqual = (a, b)=>{
  346. if (crypto__namespace?.timingSafeEqual) {
  347. return crypto__namespace.timingSafeEqual(globalScope.Buffer.from(a), globalScope.Buffer.from(b));
  348. } else {
  349. if (a.length !== b.length) {
  350. throw new TypeError("Input strings must have the same length");
  351. }
  352. let i = -1;
  353. let out = 0;
  354. while(++i < a.length){
  355. out |= a.charCodeAt(i) ^ b.charCodeAt(i);
  356. }
  357. return out === 0;
  358. }
  359. };
  360. /**
  361. * HOTP: An HMAC-based One-time Password Algorithm.
  362. * @see [RFC 4226](https://tools.ietf.org/html/rfc4226)
  363. */ class HOTP {
  364. /**
  365. * Default configuration.
  366. * @type {{
  367. * issuer: string,
  368. * label: string,
  369. * issuerInLabel: boolean,
  370. * algorithm: string,
  371. * digits: number,
  372. * counter: number
  373. * window: number
  374. * }}
  375. */ static get defaults() {
  376. return {
  377. issuer: "",
  378. label: "OTPAuth",
  379. issuerInLabel: true,
  380. algorithm: "SHA1",
  381. digits: 6,
  382. counter: 0,
  383. window: 1
  384. };
  385. }
  386. /**
  387. * Generates an HOTP token.
  388. * @param {Object} config Configuration options.
  389. * @param {Secret} config.secret Secret key.
  390. * @param {string} [config.algorithm='SHA1'] HMAC hashing algorithm.
  391. * @param {number} [config.digits=6] Token length.
  392. * @param {number} [config.counter=0] Counter value.
  393. * @returns {string} Token.
  394. */ static generate({ secret, algorithm = HOTP.defaults.algorithm, digits = HOTP.defaults.digits, counter = HOTP.defaults.counter }) {
  395. const digest = hmacDigest(algorithm, secret.bytes, uintDecode(counter));
  396. const offset = digest[digest.byteLength - 1] & 15;
  397. const otp = ((digest[offset] & 127) << 24 | (digest[offset + 1] & 255) << 16 | (digest[offset + 2] & 255) << 8 | digest[offset + 3] & 255) % 10 ** digits;
  398. return otp.toString().padStart(digits, "0");
  399. }
  400. /**
  401. * Generates an HOTP token.
  402. * @param {Object} [config] Configuration options.
  403. * @param {number} [config.counter=this.counter++] Counter value.
  404. * @returns {string} Token.
  405. */ generate({ counter = this.counter++ } = {}) {
  406. return HOTP.generate({
  407. secret: this.secret,
  408. algorithm: this.algorithm,
  409. digits: this.digits,
  410. counter
  411. });
  412. }
  413. /**
  414. * Validates an HOTP token.
  415. * @param {Object} config Configuration options.
  416. * @param {string} config.token Token value.
  417. * @param {Secret} config.secret Secret key.
  418. * @param {string} [config.algorithm='SHA1'] HMAC hashing algorithm.
  419. * @param {number} config.digits Token length.
  420. * @param {number} [config.counter=0] Counter value.
  421. * @param {number} [config.window=1] Window of counter values to test.
  422. * @returns {number|null} Token delta or null if it is not found in the search window, in which case it should be considered invalid.
  423. */ static validate({ token, secret, algorithm, digits, counter = HOTP.defaults.counter, window = HOTP.defaults.window }) {
  424. // Return early if the token length does not match the digit number.
  425. if (token.length !== digits) return null;
  426. let delta = null;
  427. const check = (/** @type {number} */ i)=>{
  428. const generatedToken = HOTP.generate({
  429. secret,
  430. algorithm,
  431. digits,
  432. counter: i
  433. });
  434. if (timingSafeEqual(token, generatedToken)) {
  435. delta = i - counter;
  436. }
  437. };
  438. check(counter);
  439. for(let i = 1; i <= window && delta === null; ++i){
  440. check(counter - i);
  441. if (delta !== null) break;
  442. check(counter + i);
  443. if (delta !== null) break;
  444. }
  445. return delta;
  446. }
  447. /**
  448. * Validates an HOTP token.
  449. * @param {Object} config Configuration options.
  450. * @param {string} config.token Token value.
  451. * @param {number} [config.counter=this.counter] Counter value.
  452. * @param {number} [config.window=1] Window of counter values to test.
  453. * @returns {number|null} Token delta or null if it is not found in the search window, in which case it should be considered invalid.
  454. */ validate({ token, counter = this.counter, window }) {
  455. return HOTP.validate({
  456. token,
  457. secret: this.secret,
  458. algorithm: this.algorithm,
  459. digits: this.digits,
  460. counter,
  461. window
  462. });
  463. }
  464. /**
  465. * Returns a Google Authenticator key URI.
  466. * @returns {string} URI.
  467. */ toString() {
  468. const e = encodeURIComponent;
  469. 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)}`;
  470. }
  471. /**
  472. * Creates an HOTP object.
  473. * @param {Object} [config] Configuration options.
  474. * @param {string} [config.issuer=''] Account provider.
  475. * @param {string} [config.label='OTPAuth'] Account label.
  476. * @param {boolean} [config.issuerInLabel=true] Include issuer prefix in label.
  477. * @param {Secret|string} [config.secret=Secret] Secret key.
  478. * @param {string} [config.algorithm='SHA1'] HMAC hashing algorithm.
  479. * @param {number} [config.digits=6] Token length.
  480. * @param {number} [config.counter=0] Initial counter value.
  481. */ 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 } = {}){
  482. /**
  483. * Account provider.
  484. * @type {string}
  485. */ this.issuer = issuer;
  486. /**
  487. * Account label.
  488. * @type {string}
  489. */ this.label = label;
  490. /**
  491. * Include issuer prefix in label.
  492. * @type {boolean}
  493. */ this.issuerInLabel = issuerInLabel;
  494. /**
  495. * Secret key.
  496. * @type {Secret}
  497. */ this.secret = typeof secret === "string" ? Secret.fromBase32(secret) : secret;
  498. /**
  499. * HMAC hashing algorithm.
  500. * @type {string}
  501. */ this.algorithm = algorithm.toUpperCase();
  502. /**
  503. * Token length.
  504. * @type {number}
  505. */ this.digits = digits;
  506. /**
  507. * Initial counter value.
  508. * @type {number}
  509. */ this.counter = counter;
  510. }
  511. }
  512. /**
  513. * TOTP: Time-Based One-Time Password Algorithm.
  514. * @see [RFC 6238](https://tools.ietf.org/html/rfc6238)
  515. */ class TOTP {
  516. /**
  517. * Default configuration.
  518. * @type {{
  519. * issuer: string,
  520. * label: string,
  521. * issuerInLabel: boolean,
  522. * algorithm: string,
  523. * digits: number,
  524. * period: number
  525. * window: number
  526. * }}
  527. */ static get defaults() {
  528. return {
  529. issuer: "",
  530. label: "OTPAuth",
  531. issuerInLabel: true,
  532. algorithm: "SHA1",
  533. digits: 6,
  534. period: 30,
  535. window: 1
  536. };
  537. }
  538. /**
  539. * Generates a TOTP token.
  540. * @param {Object} config Configuration options.
  541. * @param {Secret} config.secret Secret key.
  542. * @param {string} [config.algorithm='SHA1'] HMAC hashing algorithm.
  543. * @param {number} [config.digits=6] Token length.
  544. * @param {number} [config.period=30] Token time-step duration.
  545. * @param {number} [config.timestamp=Date.now] Timestamp value in milliseconds.
  546. * @returns {string} Token.
  547. */ static generate({ secret, algorithm, digits, period = TOTP.defaults.period, timestamp = Date.now() }) {
  548. return HOTP.generate({
  549. secret,
  550. algorithm,
  551. digits,
  552. counter: Math.floor(timestamp / 1000 / period)
  553. });
  554. }
  555. /**
  556. * Generates a TOTP token.
  557. * @param {Object} [config] Configuration options.
  558. * @param {number} [config.timestamp=Date.now] Timestamp value in milliseconds.
  559. * @returns {string} Token.
  560. */ generate({ timestamp = Date.now() } = {}) {
  561. return TOTP.generate({
  562. secret: this.secret,
  563. algorithm: this.algorithm,
  564. digits: this.digits,
  565. period: this.period,
  566. timestamp
  567. });
  568. }
  569. /**
  570. * Validates a TOTP token.
  571. * @param {Object} config Configuration options.
  572. * @param {string} config.token Token value.
  573. * @param {Secret} config.secret Secret key.
  574. * @param {string} [config.algorithm='SHA1'] HMAC hashing algorithm.
  575. * @param {number} config.digits Token length.
  576. * @param {number} [config.period=30] Token time-step duration.
  577. * @param {number} [config.timestamp=Date.now] Timestamp value in milliseconds.
  578. * @param {number} [config.window=1] Window of counter values to test.
  579. * @returns {number|null} Token delta or null if it is not found in the search window, in which case it should be considered invalid.
  580. */ static validate({ token, secret, algorithm, digits, period = TOTP.defaults.period, timestamp = Date.now(), window }) {
  581. return HOTP.validate({
  582. token,
  583. secret,
  584. algorithm,
  585. digits,
  586. counter: Math.floor(timestamp / 1000 / period),
  587. window
  588. });
  589. }
  590. /**
  591. * Validates a TOTP token.
  592. * @param {Object} config Configuration options.
  593. * @param {string} config.token Token value.
  594. * @param {number} [config.timestamp=Date.now] Timestamp value in milliseconds.
  595. * @param {number} [config.window=1] Window of counter values to test.
  596. * @returns {number|null} Token delta or null if it is not found in the search window, in which case it should be considered invalid.
  597. */ validate({ token, timestamp, window }) {
  598. return TOTP.validate({
  599. token,
  600. secret: this.secret,
  601. algorithm: this.algorithm,
  602. digits: this.digits,
  603. period: this.period,
  604. timestamp,
  605. window
  606. });
  607. }
  608. /**
  609. * Returns a Google Authenticator key URI.
  610. * @returns {string} URI.
  611. */ toString() {
  612. const e = encodeURIComponent;
  613. 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)}`;
  614. }
  615. /**
  616. * Creates a TOTP object.
  617. * @param {Object} [config] Configuration options.
  618. * @param {string} [config.issuer=''] Account provider.
  619. * @param {string} [config.label='OTPAuth'] Account label.
  620. * @param {boolean} [config.issuerInLabel=true] Include issuer prefix in label.
  621. * @param {Secret|string} [config.secret=Secret] Secret key.
  622. * @param {string} [config.algorithm='SHA1'] HMAC hashing algorithm.
  623. * @param {number} [config.digits=6] Token length.
  624. * @param {number} [config.period=30] Token time-step duration.
  625. */ 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 } = {}){
  626. /**
  627. * Account provider.
  628. * @type {string}
  629. */ this.issuer = issuer;
  630. /**
  631. * Account label.
  632. * @type {string}
  633. */ this.label = label;
  634. /**
  635. * Include issuer prefix in label.
  636. * @type {boolean}
  637. */ this.issuerInLabel = issuerInLabel;
  638. /**
  639. * Secret key.
  640. * @type {Secret}
  641. */ this.secret = typeof secret === "string" ? Secret.fromBase32(secret) : secret;
  642. /**
  643. * HMAC hashing algorithm.
  644. * @type {string}
  645. */ this.algorithm = algorithm.toUpperCase();
  646. /**
  647. * Token length.
  648. * @type {number}
  649. */ this.digits = digits;
  650. /**
  651. * Token time-step duration.
  652. * @type {number}
  653. */ this.period = period;
  654. }
  655. }
  656. /**
  657. * Key URI regex (otpauth://TYPE/[ISSUER:]LABEL?PARAMETERS).
  658. * @type {RegExp}
  659. */ const OTPURI_REGEX = /^otpauth:\/\/([ht]otp)\/(.+)\?([A-Z0-9.~_-]+=[^?&]*(?:&[A-Z0-9.~_-]+=[^?&]*)*)$/i;
  660. /**
  661. * RFC 4648 base32 alphabet with pad.
  662. * @type {RegExp}
  663. */ const SECRET_REGEX = /^[2-7A-Z]+=*$/i;
  664. /**
  665. * Regex for supported algorithms.
  666. * @type {RegExp}
  667. */ const ALGORITHM_REGEX = /^SHA(?:1|224|256|384|512|3-224|3-256|3-384|3-512)$/i;
  668. /**
  669. * Integer regex.
  670. * @type {RegExp}
  671. */ const INTEGER_REGEX = /^[+-]?\d+$/;
  672. /**
  673. * Positive integer regex.
  674. * @type {RegExp}
  675. */ const POSITIVE_INTEGER_REGEX = /^\+?[1-9]\d*$/;
  676. /**
  677. * HOTP/TOTP object/string conversion.
  678. * @see [Key URI Format](https://github.com/google/google-authenticator/wiki/Key-Uri-Format)
  679. */ class URI {
  680. /**
  681. * Parses a Google Authenticator key URI and returns an HOTP/TOTP object.
  682. * @param {string} uri Google Authenticator Key URI.
  683. * @returns {HOTP|TOTP} HOTP/TOTP object.
  684. */ static parse(uri) {
  685. let uriGroups;
  686. try {
  687. uriGroups = uri.match(OTPURI_REGEX);
  688. // eslint-disable-next-line no-unused-vars
  689. } catch (_) {
  690. /* Handled below */ }
  691. if (!Array.isArray(uriGroups)) {
  692. throw new URIError("Invalid URI format");
  693. }
  694. // Extract URI groups.
  695. const uriType = uriGroups[1].toLowerCase();
  696. const uriLabel = uriGroups[2].split(/(?::|%3A) *(.+)/i, 2).map(decodeURIComponent);
  697. /** @type {Object.<string, string>} */ const uriParams = uriGroups[3].split("&").reduce((acc, cur)=>{
  698. const pairArr = cur.split(/=(.*)/, 2).map(decodeURIComponent);
  699. const pairKey = pairArr[0].toLowerCase();
  700. const pairVal = pairArr[1];
  701. /** @type {Object.<string, string>} */ const pairAcc = acc;
  702. pairAcc[pairKey] = pairVal;
  703. return pairAcc;
  704. }, {});
  705. // 'OTP' will be instantiated with 'config' argument.
  706. let OTP;
  707. const config = {};
  708. if (uriType === "hotp") {
  709. OTP = HOTP;
  710. // Counter: required
  711. if (typeof uriParams.counter !== "undefined" && INTEGER_REGEX.test(uriParams.counter)) {
  712. config.counter = parseInt(uriParams.counter, 10);
  713. } else {
  714. throw new TypeError("Missing or invalid 'counter' parameter");
  715. }
  716. } else if (uriType === "totp") {
  717. OTP = TOTP;
  718. // Period: optional
  719. if (typeof uriParams.period !== "undefined") {
  720. if (POSITIVE_INTEGER_REGEX.test(uriParams.period)) {
  721. config.period = parseInt(uriParams.period, 10);
  722. } else {
  723. throw new TypeError("Invalid 'period' parameter");
  724. }
  725. }
  726. } else {
  727. throw new TypeError("Unknown OTP type");
  728. }
  729. // Label: required
  730. // Issuer: optional
  731. if (typeof uriParams.issuer !== "undefined") {
  732. config.issuer = uriParams.issuer;
  733. }
  734. if (uriLabel.length === 2) {
  735. config.label = uriLabel[1];
  736. if (typeof config.issuer === "undefined" || config.issuer === "") {
  737. config.issuer = uriLabel[0];
  738. } else if (uriLabel[0] === "") {
  739. config.issuerInLabel = false;
  740. }
  741. } else {
  742. config.label = uriLabel[0];
  743. if (typeof config.issuer !== "undefined" && config.issuer !== "") {
  744. config.issuerInLabel = false;
  745. }
  746. }
  747. // Secret: required
  748. if (typeof uriParams.secret !== "undefined" && SECRET_REGEX.test(uriParams.secret)) {
  749. config.secret = uriParams.secret;
  750. } else {
  751. throw new TypeError("Missing or invalid 'secret' parameter");
  752. }
  753. // Algorithm: optional
  754. if (typeof uriParams.algorithm !== "undefined") {
  755. if (ALGORITHM_REGEX.test(uriParams.algorithm)) {
  756. config.algorithm = uriParams.algorithm;
  757. } else {
  758. throw new TypeError("Invalid 'algorithm' parameter");
  759. }
  760. }
  761. // Digits: optional
  762. if (typeof uriParams.digits !== "undefined") {
  763. if (POSITIVE_INTEGER_REGEX.test(uriParams.digits)) {
  764. config.digits = parseInt(uriParams.digits, 10);
  765. } else {
  766. throw new TypeError("Invalid 'digits' parameter");
  767. }
  768. }
  769. return new OTP(config);
  770. }
  771. /**
  772. * Converts an HOTP/TOTP object to a Google Authenticator key URI.
  773. * @param {HOTP|TOTP} otp HOTP/TOTP object.
  774. * @returns {string} Google Authenticator Key URI.
  775. */ static stringify(otp) {
  776. if (otp instanceof HOTP || otp instanceof TOTP) {
  777. return otp.toString();
  778. }
  779. throw new TypeError("Invalid 'HOTP/TOTP' object");
  780. }
  781. }
  782. /**
  783. * Library version.
  784. * @type {string}
  785. */ const version = "9.3.1";
  786. exports.HOTP = HOTP;
  787. exports.Secret = Secret;
  788. exports.TOTP = TOTP;
  789. exports.URI = URI;
  790. exports.version = version;