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 options
9 * @param {number} options.codeLength digits in code
10 * @param {Buffer|string} options.key secret key
11 * @param {string} options.keyEncoding secret key encoding
12 * @param {string} options.algorithm algorithm
13 * @param {number} options.timeStepSeconds seconds per increment
14 * @param {number} options.timeStepStartSeconds seconds offset
15 * @param {number} options.driftForward allowed future steps to check
16 * @param {number} options.driftBackward allowed past steps to check
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 * @returns {string} otp auth type
39 */
40 static get _type() {
41 return 'totp';
42 }
43
44 /**
45 * Derive counter from epoch.
46 * @returns {bigint} time based counter
47 */
48 get counter() {
49 const epoch = Math.floor(Date.now() / 1000);
50 return BigInt(Math.floor((epoch - this.timeStepStartSeconds) / this.timeStepSeconds));
51 }
52
53 set counter(_) { /* Ignore assignment */ } // eslint-disable-line class-methods-use-this
54
55 static get _defaultOptions() {
56 const options = Object.assign(super._defaultOptions, {
57 timeStepSeconds: 30,
58 timeStepStartSeconds: 0,
59 driftForward: 1,
60 driftBackward: 1,
61 });
62 delete options.counter;
63 return options;
64 }
65
66 /**
67 *
68 * @param {bigint=} count counter value
69 * @returns {string} code
70 */
71 generate(count = this.counter) {
72 return super.generate(count);
73 }
74
75 /**
76 *
77 * @param {string} hotp code
78 * @param {bigint=} count counter value
79 * @returns {boolean} is valid
80 */
81 validate(hotp, count) {
82 const counter = count ?? this.counter;
83 for (const offset of this.driftOffsets) {
84 const codeString = this.generate(counter + offset);
85 if (codeString === hotp.trim()) {
86 return true;
87 }
88 }
89 return false;
90 }
91
92 }
93
94 module.exports = TimeBasedOneTimePassword;