a305193e7dde085bc4ba7a76bef1814e1fa9acf6
[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 get _algorithmKeyLengths() {
29 return {
30 ...super._algorithmKeyLengths,
31 'sha256': 32,
32 'sha512': 64,
33 };
34 }
35
36 get counter() {
37 const epoch = Math.floor(Date.now() / 1000);
38 return BigInt(Math.floor((epoch - this.timeStepStartSeconds) / this.timeStepSeconds));
39 }
40
41 set counter(_) { /* */ }
42
43 get _defaultOptions() {
44 const options = Object.assign(super._defaultOptions, {
45 timeStepSeconds: 30,
46 timeStepStartSeconds: 0,
47 driftForward: 1,
48 driftBackward: 1,
49 });
50 delete options.counter;
51 return options;
52 }
53
54 generate(count = this.counter) {
55 return super.generate(count);
56 }
57
58 /**
59 *
60 * @param {String} hotp
61 * @param {BigInt=} count
62 * @returns {Boolean}
63 */
64 validate(hotp, count) {
65 const counter = count ?? this.counter;
66 for (const offset of this.driftOffsets) {
67 const codeString = this.generate(counter + offset);
68 if (codeString === hotp.trim()) {
69 return true;
70 }
71 }
72 return false;
73 }
74
75 }
76
77 module.exports = TimeBasedOneTimePassword;