UNPKG

@crpdo/time

Version:

Provides methods for performing time-based operations including token verification, hash generation, and NTP calculations. It primarily deals with time-based one-time password (TOTP) functions

151 lines (140 loc) 5.33 kB
/** * Time utility module providing methods for performing time-based operations * including token verification, hash generation, and NTP calculations. * It primarily deals with time-based one-time password (TOTP) functions. * * @module Time * @requires 'basd' * @requires '@crpdo/crypto' */ const { _, log } = require('basd') const Crypto = require('@crpdo/crypto') const TOTP_GAP = 30 /** * @class Time */ class Time { /** * Generates a time-based one-time password (TOTP). * * @param {string} secret - The secret key used to generate the OTP. * @param {Object} [opts={}] - Optional parameters for OTP generation. * @param {number} [opts.totpGap=TOTP_GAP] - The time duration for OTP generation (in seconds). * @param {number} [opts.now] - The current timestamp. * @returns {string} - The generated OTP. */ static code(secret, opts = {}) { const { now, totpGap } = _.defaults(opts, { totpGap: TOTP_GAP, now: Date.now(), }) // Initialize a hex string of zeroes const hexZeroes = Array(16).fill(0).join('') // Calculate the timecode and convert to hexadecimal const timeCode = Math.floor(Math.round(now / 1000) / totpGap) const hexTimeCode = (hexZeroes + timeCode).toString(16) // Take the last 16 characters of the timecode hex string const timeCodePayload = hexTimeCode.slice(-16) // Convert the payload to a Buffer const payloadBuffer = _.toBuffer(timeCodePayload, 'hex') // Concatenate the secret and payload into a single buffer const concatenatedBuffer = Buffer.concat([_.toBuffer(secret), payloadBuffer]) // Hash the buffer and convert it to a hexadecimal string const hashedBuffer = _.hash(concatenatedBuffer, 32, false).toString('hex') // Take a subset of the hashed buffer determined by the last character const hashedSubset = hashedBuffer.substr(parseInt(hashedBuffer.slice(-1), 16) * 2, 8) // Convert the subset to an integer and mask it const maskedInteger = parseInt(hashedSubset, 16) & 2147483647 // Convert the masked integer to a string const maskedString = maskedInteger.toString() // Take the last 6 digits of the string const finalCode = maskedString.slice(-6) return finalCode } /** * Generates a hashed time-based code. * * @param {string} secret - The secret key used to generate the OTP. * @param {Object} [opts={}] - Optional parameters for OTP generation. * @returns {string} - The hashed time-based code. */ static hash(secret, opts = {}) { const timeCode = this.code(secret, opts) return _.hash(timeCode) } /** * Verifies a given token. * * @param {string} token - The token to be verified. * @param {string} secret - The secret key used for verification. * @param {Object} [opts={}] - Optional parameters for OTP verification. * @param {number} [opts.now] - The current timestamp. * @param {number} [opts.totpGap=TOTP_GAP] - The time duration for OTP verification (in seconds). * @param {boolean} [opts.hashed=false] - Indicates whether the token is hashed or not. * @param {number} [opts.range=1] - The range for token verification. * @returns {boolean} - `true` if the token is verified successfully, otherwise `false`. */ static verify(token, secret, opts = {}) { const { now, totpGap, hashed, range } = _.defaults(opts, { totpGap: TOTP_GAP, now: Date.now(), hashed: false, range: 1, }) const nows = [ now, (now - (totpGap * range * 1000)), (now + (totpGap * range * 1000)) ] let timecode const createToken = hashed ? this.hash.bind(this) : this.code.bind(this) for (let ii in nows) { timecode = createToken(secret, { now: nows[ii], totpGap }) if (Crypto.verify(token, timecode)) return true } return false } /** * Performs Network Time Protocol (NTP) calculations. * * @param {number} t0 - The time at which the request was transmitted. * @param {number} t1 - The time at which the request was received. * @param {number} t2 - The time at which the response was transmitted. * @param {number} t3 - The time at which the response was received. * @returns {Object} - An object containing the round trip time (`rtt`), * offset (`offset`), and delay (`delay`). */ static ntp(t0, t1, t2, t3) { const rtt = (t3 - t0) - (t2 - t1) const offset = ((t1 - t0) + (t2 - t3)) / 2 const delay = Math.round(rtt / 2) return { rtt, offset, delay, } } /** * Calculates the average round trip time (`rtt`), offset (`offset`), * and delay (`delay`) from an array of results. * * @param {Array<Object>} [results=[]] - An array of result objects containing * `rtt`, `offset`, and `delay` properties. * @returns {Object} - An object containing the average `rtt`, `offset`, and `delay`. */ static sync(results = []) { let len = results.length let rtt = 0 let off = 0 for (let res of results) { rtt += res.rtt off += res.offset } rtt = Math.round(rtt / len) off = Math.round(off / len) let delay = Math.round(rtt / 2) return { rtt, offset: !!off ? off : 0, delay, } } } module.exports = Time