@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
JavaScript
/**
* 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