@epic-web/totp
Advanced tools
Comparing version 1.0.6 to 1.1.0
@@ -12,12 +12,14 @@ /** | ||
* SHA1. | ||
* @param {string} [options.charSet='0123456789'] - The character set to use, defaults to the numbers 0-9. | ||
* @param {string} [options.secret] The secret to use for the TOTP. It should be | ||
* base32 encoded (you can use https://npm.im/thirty-two). Defaults to a random | ||
* secret: base32.encode(crypto.randomBytes(10)).toString(). | ||
* @returns {{otp: string, secret: string, period: number, digits: number, algorithm: string}} | ||
* @returns {{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({ period, digits, algorithm, secret, }?: { | ||
export function generateTOTP({ period, digits, algorithm, secret, charSet, }?: { | ||
period?: number; | ||
digits?: number; | ||
algorithm?: string; | ||
charSet?: string; | ||
secret?: string; | ||
@@ -30,2 +32,3 @@ }): { | ||
algorithm: string; | ||
charSet: string; | ||
}; | ||
@@ -66,2 +69,3 @@ /** | ||
* @param {string} [options.algorithm] The algorithm to use. | ||
* @param {string} [options.charSet] - The character set to use, defaults to the numbers 0-9. | ||
* @param {number} [options.window] The number of OTPs to check before and after | ||
@@ -74,3 +78,3 @@ * the current OTP. Defaults to 1. | ||
*/ | ||
export function verifyTOTP({ otp, secret, period, digits, algorithm, window, }: { | ||
export function verifyTOTP({ otp, secret, period, digits, algorithm, charSet, window, }: { | ||
otp: string; | ||
@@ -81,2 +85,3 @@ secret: string; | ||
algorithm?: string; | ||
charSet?: string; | ||
window?: number; | ||
@@ -83,0 +88,0 @@ }): { |
45
index.js
@@ -17,2 +17,3 @@ /** | ||
const DEFAULT_ALGORITHM = 'SHA1' | ||
const DEFAULT_CHAR_SET = '0123456789' | ||
const DEFAULT_DIGITS = 6 | ||
@@ -34,2 +35,3 @@ const DEFAULT_WINDOW = 1 | ||
* HOTP. Defaults to 'SHA1'. | ||
* @param {string} [options.charSet='0123456789'] - The character set to use, defaults to the numbers 0-9. | ||
* @returns {string} The generated HOTP. | ||
@@ -39,3 +41,8 @@ */ | ||
secret, | ||
{ counter = 0, digits = DEFAULT_DIGITS, algorithm = DEFAULT_ALGORITHM } = {} | ||
{ | ||
counter = 0, | ||
digits = DEFAULT_DIGITS, | ||
algorithm = DEFAULT_ALGORITHM, | ||
charSet = DEFAULT_CHAR_SET, | ||
} = {} | ||
) { | ||
@@ -47,9 +54,15 @@ const byteCounter = Buffer.from(intToBytes(counter)) | ||
const offset = hashBytes[19] & 0xf | ||
let hotp = | ||
(((hashBytes[offset] & 0x7f) << 24) | | ||
((hashBytes[offset + 1] & 0xff) << 16) | | ||
((hashBytes[offset + 2] & 0xff) << 8) | | ||
(hashBytes[offset + 3] & 0xff)) + | ||
'' | ||
return hotp.slice(-digits) | ||
let hotpVal = | ||
((hashBytes[offset] & 0x7f) << 24) | | ||
((hashBytes[offset + 1] & 0xff) << 16) | | ||
((hashBytes[offset + 2] & 0xff) << 8) | | ||
(hashBytes[offset + 3] & 0xff) | ||
let hotp = '' | ||
for (let i = 0; i < digits; i++) { | ||
hotp += charSet.charAt(hotpVal % charSet.length) | ||
hotpVal = Math.floor(hotpVal / charSet.length) | ||
} | ||
return hotp | ||
} | ||
@@ -70,2 +83,3 @@ | ||
* HOTP. Defaults to 'SHA1'. | ||
* @param {string} [options.charSet='0123456789'] - The character set to use, defaults to the numbers 0-9. | ||
* @param {number} [options.window=1] - The number of counter values to check | ||
@@ -84,2 +98,3 @@ * before and after the current counter value. Defaults to 1. | ||
algorithm = DEFAULT_ALGORITHM, | ||
charSet = DEFAULT_CHAR_SET, | ||
window = DEFAULT_WINDOW, | ||
@@ -89,3 +104,5 @@ } = {} | ||
for (let i = counter - window; i <= counter + window; ++i) { | ||
if (generateHOTP(secret, { counter: i, digits, algorithm }) === otp) { | ||
if ( | ||
generateHOTP(secret, { counter: i, digits, algorithm, charSet }) === otp | ||
) { | ||
return { delta: i - counter } | ||
@@ -108,6 +125,7 @@ } | ||
* SHA1. | ||
* @param {string} [options.charSet='0123456789'] - The character set to use, defaults to the numbers 0-9. | ||
* @param {string} [options.secret] The secret to use for the TOTP. It should be | ||
* base32 encoded (you can use https://npm.im/thirty-two). Defaults to a random | ||
* secret: base32.encode(crypto.randomBytes(10)).toString(). | ||
* @returns {{otp: string, secret: string, period: number, digits: number, algorithm: string}} | ||
* @returns {{otp: string, secret: string, period: number, digits: number, algorithm: string, charSet: string}} | ||
* The OTP, secret, and config options used to generate the OTP. | ||
@@ -120,2 +138,3 @@ */ | ||
secret = base32.encode(crypto.randomBytes(10)).toString(), | ||
charSet = DEFAULT_CHAR_SET, | ||
} = {}) { | ||
@@ -126,5 +145,6 @@ const otp = generateHOTP(base32.decode(secret), { | ||
algorithm, | ||
charSet, | ||
}) | ||
return { otp, secret, period, digits, algorithm } | ||
return { otp, secret, period, digits, algorithm, charSet } | ||
} | ||
@@ -181,2 +201,3 @@ | ||
* @param {string} [options.algorithm] The algorithm to use. | ||
* @param {string} [options.charSet] - The character set to use, defaults to the numbers 0-9. | ||
* @param {number} [options.window] The number of OTPs to check before and after | ||
@@ -195,2 +216,3 @@ * the current OTP. Defaults to 1. | ||
algorithm, | ||
charSet, | ||
window = DEFAULT_WINDOW, | ||
@@ -203,2 +225,3 @@ }) { | ||
algorithm, | ||
charSet, | ||
}) | ||
@@ -205,0 +228,0 @@ } |
@@ -14,3 +14,3 @@ { | ||
}, | ||
"version": "1.0.6", | ||
"version": "1.1.0", | ||
"description": "Create and verify cryptographically secure Time-based One-time Passwords (TOTP) using the HMAC-based One-time Password (HOTP) algorithm.", | ||
@@ -17,0 +17,0 @@ "main": "index.js", |
@@ -32,3 +32,3 @@ <div> | ||
[![Build Status][build-badge]][build] | ||
[![GPL 3.0 License][license-badge]][license] | ||
[![MIT License][license-badge]][license] | ||
[![Code of Conduct][coc-badge]][coc] | ||
@@ -84,3 +84,3 @@ <!-- prettier-ignore-end --> | ||
const { secret, period, digits, algorithm } = generateTOTP() | ||
const uri = getTOTPAuthUri({ | ||
const otpUri = getTOTPAuthUri({ | ||
period, | ||
@@ -157,2 +157,55 @@ digits, | ||
## Customizable Character Set for Increased Security | ||
### Why Charset Matters | ||
When it comes to security, every bit of entropy counts. Entropy measures the | ||
unpredictability and in turn the security of your OTPs. The traditional TOTP | ||
setup often employs a 6-digit numerical code, providing a million (10^6) | ||
combinations. This is the default behaviour for this implementation. While that | ||
is robust, there's room for improvement. | ||
By introducing a customizable character set feature, you can exponentially | ||
increase the entropy of the OTPs, making them much more secure against | ||
brute-force attacks. For example, if you extend your character set to include 26 | ||
uppercase letters and 10 digits, a 6-character OTP would have 36^6 = 2.1 billion | ||
combinations. When paired with rate-limiting mechanisms, this configuration | ||
becomes practically impervious to brute-force attacks. | ||
### Potential for Main Form of Authentication | ||
With this added complexity, TOTPs can, in theory, be used as the primary form of | ||
authentication, rather than just a second factor. This is particularly useful | ||
for applications requiring heightened security. | ||
### Usage with Custom Character Set | ||
In addition to the existing options, you can specify a charSet in both | ||
`generateTOTP` and `verifyTOTP`. | ||
Here's how you can generate an OTP with a custom character set: | ||
```js | ||
import { generateTOTP, verifyTOTP } from '@epic-web/totp' | ||
const { otp, secret, period, digits, algorithm, charSet } = generateTOTP({ | ||
charSet: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', // custom character set | ||
}) | ||
// Remember to save the charSet to your database as well. | ||
// To verify | ||
const isValid = verifyTOTP({ | ||
otp, | ||
secret, | ||
period, | ||
digits, | ||
algorithm, | ||
charSet, | ||
}) | ||
``` | ||
Just as an aside, you probably want to exclude the letter O and the number 0 to | ||
make it easier for users to enter the code. | ||
## API | ||
@@ -180,3 +233,4 @@ | ||
* secret: base32.encode(crypto.randomBytes(10)).toString(). | ||
* @returns {{otp: string, secret: string, period: number, digits: number, algorithm: string}} | ||
* @param {string} [options.charSet='0123456789'] - The character set to use, defaults to the numbers 0-9. | ||
* @returns {{otp: string, secret: string, period: number, digits: number, algorithm: string, charSet: string}} | ||
* The OTP, secret, and config options used to generate the OTP. | ||
@@ -200,2 +254,3 @@ */ | ||
* @param {string} [options.algorithm] The algorithm to use. | ||
* @param {string} [options.charSet] The character set to use, defaults to the numbers 0-9. | ||
* @param {number} [options.window] The number of OTPs to check before and after | ||
@@ -237,3 +292,3 @@ * the current OTP. Defaults to 1. | ||
[build]: https://github.com/epicweb-dev/totp/actions?query=workflow%3Arelease | ||
[license-badge]: https://img.shields.io/badge/license-GPL%203.0%20License-blue.svg?style=flat-square | ||
[license-badge]: https://img.shields.io/badge/license-MIT%20License-blue.svg?style=flat-square | ||
[license]: https://github.com/epicweb-dev/totp/blob/main/LICENSE | ||
@@ -240,0 +295,0 @@ [coc-badge]: https://img.shields.io/badge/code%20of-conduct-ff69b4.svg?style=flat-square |
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
24181
321
292