bump package version to 1.1.5
[squeep-totp] / lib / totp.js
1 'use strict';
2
3 const HOTP = require('./hotp');
4
5 class TimeBasedOneTimePassword extends HOTP {
6 /**
7 *
8 * @param {Object} options
9 * @param {Number} options.codeLength
10 * @param {Buffer|String} options.key
11 * @param {String} options.keyEncoding
12 * @param {String} options.algorithm
13 * @param {Number} options.timeStepSeconds
14 * @param {Number} options.timeStepStartSeconds
15 * @param {Number} options.driftForward
16 * @param {Number} options.driftBackward
17 */
18 constructor(options) {
19 const _options = { ...options };
20 super(_options);
21 this.driftOffsets = [
22 0n, // Check now first
23 ...Array.from({ length: this.driftBackward }, (v, k) => BigInt(-(k + 1))),
24 ...Array.from({ length: this.driftForward }, (v, k) => BigInt(k + 1)),
25 ];
26 }
27
28 static get _algorithmKeyLengths() {
29 return {
30 ...super._algorithmKeyLengths,
31 'sha256': 32,
32 'sha512': 64,
33 };
34 }
35
36 /**
37 * The type used when constructing the otpauth URI.
38 */
39 static get _type() {
40 return 'totp';
41 }
42
43 /**
44 * Derive counter from epoch.
45 */
46 get counter() {
47 const epoch = Math.floor(Date.now() / 1000);
48 return BigInt(Math.floor((epoch - this.timeStepStartSeconds) / this.timeStepSeconds));
49 }
50
51 set counter(_) { /* Ignore assignment */ } // eslint-disable-line class-methods-use-this
52
53 static get _defaultOptions() {
54 const options = Object.assign(super._defaultOptions, {
55 timeStepSeconds: 30,
56 timeStepStartSeconds: 0,
57 driftForward: 1,
58 driftBackward: 1,
59 });
60 delete options.counter;
61 return options;
62 }
63
64 /**
65 *
66 * @param {BigInt=} count
67 * @returns {String}
68 */
69 generate(count = this.counter) {
70 return super.generate(count);
71 }
72
73 /**
74 *
75 * @param {String} hotp
76 * @param {BigInt=} count
77 * @returns {Boolean}
78 */
79 validate(hotp, count) {
80 const counter = count ?? this.counter;
81 for (const offset of this.driftOffsets) {
82 const codeString = this.generate(counter + offset);
83 if (codeString === hotp.trim()) {
84 return true;
85 }
86 }
87 return false;
88 }
89
90 }
91
92 module.exports = TimeBasedOneTimePassword;