UNPKG

2fa-utils

Version:
131 lines 5.34 kB
"use strict"; /* * [2FA-utils]{@link https://github.com/boranseckin/2fa} * @author Boran Seckin <boran@boranseckin.com> * @license MIT */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const crypto_1 = __importDefault(require("crypto")); const hi_base32_1 = __importDefault(require("hi-base32")); class twoFA { /** * Resources: * - https://hackernoon.com/how-to-implement-google-authenticator-two-factor-auth-in-javascript-091wy3vh3 * - https://tools.ietf.org/rfc/rfc4226 * - https://tools.ietf.org/rfc/rfc6238 */ /** * Generate a base32 encoded secret at given length (default is 16 characters). * * @param length Length of the secret * * @returns String of random secret */ static generateSecret(length = 16) { // Create a buffer of random bytes. const randomBuffer = crypto_1.default.randomBytes(length); // Create the secret as base32 of random buffer. // Remove excess '=' at the end of base32. const secret = hi_base32_1.default.encode(randomBuffer).replace(/=/g, ''); return secret; } /** * Generate a HMAC-based one-time password using a secret and a counter at a specific length. * * @param secret Base32 encoded secret * @param counter Current Unix time value with a time-step * @param otpLength Length of the one-time password * * @returns One time password */ static generateHOTP(secret, counter, otpLength = 6) { // Decode base32 encoded secret into array of numbers. const decodedSecret = hi_base32_1.default.decode.asBytes(secret); // Allocate an empty buffer of 8 bytes. const bufferLength = 8; const buffer = Buffer.alloc(bufferLength); // Create a local counter using the parameter. let localCounter = counter; // Write the counter into the buffer. for (let i = 0; i < bufferLength; i += 1) { // Starting from low-order, for each byte, write 8 bits of the counter. buffer[(bufferLength - 1) - i] = localCounter & 0xff; // Shift counter 8 bits to the right. localCounter >>= 8; } // Step 1: Generate an HMAC-SHA-1 value // Create HMAC using sha1 algorithm and the buffer of the decoded secret. const hmac = crypto_1.default.createHmac('sha1', Buffer.from(decodedSecret)); // Update HMAC using the buffer of the counter. hmac.update(buffer); // Calculate the digest of all the data passed into HMAC. const hmacResult = hmac.digest(); // Step 2: Generate a 4-byte string (Dynamic Truncation) const code = this.dynamicTruncation(hmacResult); // Step 3: Compute an HTOP value // Get the last digits of the code according to otpLength. let token = (code % (Math.pow(10, otpLength))).toString(); // Make sure the code matches the optLength // Add zeros to the beginning if int parsing ommited any zeros token = token.padStart(otpLength, '0'); return token; } /** * Extract 4-byte dynamic binary code from a 20-byte HMAC-SHA-1 result. * Copied from [RFC 4226](https://tools.ietf.org/html/rfc4226). * * @param hmacValue HMAC-SHA-1 result * * @returns 4-byte binary code */ static dynamicTruncation(hmacValue) { // Offset is the low-order 4 bits of the supplied string. // 0 <= Offset <= 15 const offset = hmacValue[hmacValue.length - 1] & 0xf; return (((hmacValue[offset] & 0x7f) << 24) | ((hmacValue[offset + 1] & 0xff) << 16) | ((hmacValue[offset + 2] & 0xff) << 8) | (hmacValue[offset + 3] & 0xff)); } /** * Generate a time-based HMAC-based one-time password using a secret and interval of time. * * @param secret Base32 encoded secret * @param window Time offset (n = n(30) seconds to the future / -n = n(30) second to the past) * * @returns Time-based one time password */ static generateTOTP(secret, window = 0) { // Counter will increment every 30 seconds. const counter = Math.floor(Date.now() / 30000); // Calculate the one-time password using the counter and the window. const hotp = this.generateHOTP(secret, counter + window); return hotp; } /** * Checks the validity of a token using a secret. * * @param token Token from the user * @param secret Base32 encoded secret * @param window Accepted time offset (both past and future) [0, 10] * * @returns Whether the token is correct or not */ static verifyTOTP(token, secret, window = 1) { if (Math.abs(+window) > 10) { return false; } for (let errorWindow = -window; errorWindow <= window; errorWindow += 1) { // For each window check the validity of the token. const totp = this.generateTOTP(secret, errorWindow); if (token === totp) return true; } return false; } } exports.default = twoFA; //# sourceMappingURL=2FA.js.map