@epic-web/totp
Advanced tools
Comparing version 1.1.3 to 2.0.0
@@ -15,4 +15,4 @@ /** | ||
* base32 encoded (you can use https://npm.im/thirty-two). Defaults to a random | ||
* secret: base32Encode(crypto.randomBytes(10), 'RFC4648'). | ||
* @returns {{otp: string, secret: string, period: number, digits: number, algorithm: string, charSet: string}} | ||
* secret: base32Encode(crypto.getRandomValues(new Uint8Array(10)), 'RFC4648'). | ||
* @returns {Promise<{otp: string, secret: string, period: number, digits: number, algorithm: string, charSet: string}>} | ||
* The OTP, secret, and config options used to generate the OTP. | ||
@@ -26,3 +26,3 @@ */ | ||
secret?: string; | ||
}): { | ||
}): Promise<{ | ||
otp: string; | ||
@@ -34,3 +34,3 @@ secret: string; | ||
charSet: string; | ||
}; | ||
}>; | ||
/** | ||
@@ -74,3 +74,3 @@ * Generates a otpauth:// URI which you can use to generate a QR code or users | ||
* | ||
* @returns {{delta: number}|null} an object with "delta" which is the delta | ||
* @returns {Promise<{delta: number}|null>} an object with "delta" which is the delta | ||
* between the current OTP and the OTP that was verified, or null if the OTP is | ||
@@ -87,4 +87,4 @@ * invalid. | ||
window?: number; | ||
}): { | ||
}): Promise<{ | ||
delta: number; | ||
} | null; | ||
} | null>; |
71
index.js
/** | ||
* This was copy/paste/modified/tested from https://npm.im/notp (MIT) | ||
*/ | ||
import * as crypto from 'node:crypto' | ||
import base32Encode from 'base32-encode' | ||
@@ -14,3 +12,3 @@ import base32Decode from 'base32-decode' | ||
// Learn more: https://www.rfc-editor.org/rfc/rfc4226#page-25 (B.1. SHA-1 Status) | ||
const DEFAULT_ALGORITHM = 'SHA1' | ||
const DEFAULT_ALGORITHM = 'SHA-1' | ||
const DEFAULT_CHAR_SET = '0123456789' | ||
@@ -34,5 +32,5 @@ const DEFAULT_DIGITS = 6 | ||
* @param {string} [options.charSet='0123456789'] - The character set to use, defaults to the numbers 0-9. | ||
* @returns {string} The generated HOTP. | ||
* @returns {Promise<string>} The generated HOTP. | ||
*/ | ||
function generateHOTP( | ||
async function generateHOTP( | ||
secret, | ||
@@ -46,7 +44,12 @@ { | ||
) { | ||
const byteCounter = Buffer.from(intToBytes(counter)) | ||
const secretBuffer = Buffer.from(secret) | ||
const hmac = crypto.createHmac(algorithm, secretBuffer) | ||
const digest = hmac.update(byteCounter).digest('hex') | ||
const hashBytes = hexToBytes(digest) | ||
const byteCounter = intToBytes(counter) | ||
const key = await crypto.subtle.importKey( | ||
'raw', | ||
secret, | ||
{ name: 'HMAC', hash: algorithm }, | ||
false, | ||
['sign'] | ||
) | ||
const signature = await crypto.subtle.sign('HMAC', key, byteCounter) | ||
const hashBytes = new Uint8Array(signature) | ||
const offset = hashBytes[19] & 0xf | ||
@@ -73,3 +76,3 @@ let hotpVal = | ||
* @param {string} otp - The OTP to verify. | ||
* @param {Buffer} secret - The secret used to generate the HOTP. | ||
* @param {ArrayBuffer} secret - The secret used to generate the HOTP. | ||
* @param {Object} options - The configuration options for the HOTP. | ||
@@ -85,7 +88,7 @@ * @param {number} [options.counter=0] - The counter value to use for the HOTP. | ||
* before and after the current counter value. Defaults to 1. | ||
* @returns {{delta: number}|null} An object with the `delta` property | ||
* @returns {Promise<{delta: number}|null>} An object with the `delta` property | ||
* indicating the number of counter values between the current counter value and | ||
* the verified counter value, or `null` if the OTP could not be verified. | ||
*/ | ||
function verifyHOTP( | ||
async function verifyHOTP( | ||
otp, | ||
@@ -103,3 +106,3 @@ secret, | ||
if ( | ||
generateHOTP(secret, { counter: i, digits, algorithm, charSet }) === otp | ||
await generateHOTP(secret, { counter: i, digits, algorithm, charSet }) === otp | ||
) { | ||
@@ -126,14 +129,14 @@ return { delta: i - counter } | ||
* base32 encoded (you can use https://npm.im/thirty-two). Defaults to a random | ||
* secret: base32Encode(crypto.randomBytes(10), 'RFC4648'). | ||
* @returns {{otp: string, secret: string, period: number, digits: number, algorithm: string, charSet: string}} | ||
* secret: base32Encode(crypto.getRandomValues(new Uint8Array(10)), 'RFC4648'). | ||
* @returns {Promise<{otp: string, secret: string, period: number, digits: number, algorithm: string, charSet: string}>} | ||
* The OTP, secret, and config options used to generate the OTP. | ||
*/ | ||
export function generateTOTP({ | ||
export async function generateTOTP({ | ||
period = DEFAULT_PERIOD, | ||
digits = DEFAULT_DIGITS, | ||
algorithm = DEFAULT_ALGORITHM, | ||
secret = base32Encode(crypto.randomBytes(10), 'RFC4648'), | ||
secret = base32Encode(crypto.getRandomValues(new Uint8Array(10)), 'RFC4648'), | ||
charSet = DEFAULT_CHAR_SET, | ||
} = {}) { | ||
const otp = generateHOTP(base32Decode(secret, 'RFC4648'), { | ||
const otp = await generateHOTP(base32Decode(secret, 'RFC4648'), { | ||
counter: getCounter(period), | ||
@@ -201,7 +204,7 @@ digits, | ||
* | ||
* @returns {{delta: number}|null} an object with "delta" which is the delta | ||
* @returns {Promise<{delta: number}|null>} an object with "delta" which is the delta | ||
* between the current OTP and the OTP that was verified, or null if the OTP is | ||
* invalid. | ||
*/ | ||
export function verifyTOTP({ | ||
export async function verifyTOTP({ | ||
otp, | ||
@@ -223,3 +226,3 @@ secret, | ||
return verifyHOTP(otp, Buffer.from(decodedSecret), { | ||
return verifyHOTP(otp, new Uint8Array(decodedSecret), { | ||
counter: getCounter(period), | ||
@@ -237,22 +240,14 @@ digits, | ||
* @param {number} num The number to convert to a byte array. | ||
* @returns {number[]} The byte array representation of the number. | ||
* @returns {Uint8Array} The byte array representation of the number. | ||
*/ | ||
function intToBytes(num) { | ||
const buffer = Buffer.alloc(8) | ||
// eslint-disable-next-line no-undef | ||
buffer.writeBigInt64BE(BigInt(num)) | ||
return [...buffer] | ||
const arr = new Uint8Array(8) | ||
for (let i = 7; i >= 0; i--) { | ||
arr[i] = num & 0xff | ||
num = num >> 8 | ||
} | ||
return arr | ||
} | ||
/** | ||
* Converts a hexadecimal string to a byte array. | ||
* | ||
* @param {string} hex The hexadecimal string to convert to a byte array. | ||
* @returns {number[]} The byte array representation of the hexadecimal string. | ||
*/ | ||
function hexToBytes(hex) { | ||
return [...Buffer.from(hex, 'hex')] | ||
} | ||
/** | ||
* Calculates the current counter value for the TOTP based on the current time | ||
@@ -268,2 +263,2 @@ * and the specified period. | ||
return counter | ||
} | ||
} |
@@ -14,3 +14,3 @@ { | ||
}, | ||
"version": "1.1.3", | ||
"version": "2.0.0", | ||
"description": "Create and verify cryptographically secure Time-based One-time Passwords (TOTP) using the HMAC-based One-time Password (HOTP) algorithm.", | ||
@@ -42,3 +42,3 @@ "main": "index.js", | ||
"engines": { | ||
"node": ">=18" | ||
"node": ">=20" | ||
}, | ||
@@ -45,0 +45,0 @@ "prettier": { |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
24440
323