UNPKG

@sergiomorenoalbert/fastify-totp

Version:

A plugin to handle TOTP (e.g. for 2FA)

126 lines (123 loc) 4.22 kB
import fp from 'fastify-plugin'; import speakeasy from '@levminer/speakeasy'; import qrcode from 'qrcode'; function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } } function _async_to_generator(fn) { return function() { var self = this, args = arguments; return new Promise(function(resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; } function _extends() { _extends = Object.assign || function(target) { for(var i = 1; i < arguments.length; i++){ var source = arguments[i]; for(var key in source){ if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } const DEFAULT_TOTP_SECRET_LENGTH = 20; const DEFAULT_TOTP_LABEL = 'Fastify'; const DEFAULT_TOTP_WINDOW = 1; const DEFAULT_TOTP_ALG = 'sha512'; const DEFAULT_TOTP_STEP = 30; const plugin = function(fastify, opts, next) { const TOTP_SECRET_LENGTH = opts.secretLength || DEFAULT_TOTP_SECRET_LENGTH; const TOTP_LABEL = opts.totpLabel || DEFAULT_TOTP_LABEL; const TOTP_WINDOW = opts.totpWindow || DEFAULT_TOTP_WINDOW; const TOTP_ALG = opts.totpAlg || DEFAULT_TOTP_ALG; const TOTP_STEP = opts.totpStep || DEFAULT_TOTP_STEP; function generateTOTPSecret(length) { const secret = speakeasy.generateSecret({ length: length || TOTP_SECRET_LENGTH }); return secret; } function generateTOTPToken(options) { if (!options) return null; if (!options.secret) return null; const token = speakeasy.totp(_extends({ encoding: options.encoding || 'ascii', algorithm: options.algorithm || TOTP_ALG, step: options.step || TOTP_STEP }, options)); return token; } function generateAuthURLFromSecret(options) { if (!options) return null; if (!options.secret) return null; const url = speakeasy.otpauthURL(_extends({}, options, { algorithm: options.algorithm || TOTP_ALG, label: options.label || TOTP_LABEL })); return url; } function generateQRCodeFromSecret(options) { return _generateQRCodeFromSecret.apply(this, arguments); } function _generateQRCodeFromSecret() { _generateQRCodeFromSecret = _async_to_generator(function*(options) { const url = generateAuthURLFromSecret(options); if (!url) return null; return qrcode.toDataURL(url); }); return _generateQRCodeFromSecret.apply(this, arguments); } function verifyTOTP(options) { const result = speakeasy.totp.verifyDelta(_extends({ encoding: options.encoding || 'ascii', window: options.window || TOTP_WINDOW, step: options.step || TOTP_STEP }, options)); return !!result; } fastify.decorate('totp', { generateSecret: generateTOTPSecret, generateToken: generateTOTPToken, generateAuthURL: generateAuthURLFromSecret, generateQRCode: generateQRCodeFromSecret, verify: verifyTOTP, options: { secretLength: TOTP_SECRET_LENGTH, totpLabel: TOTP_LABEL, totpWindow: TOTP_WINDOW, totpAlg: TOTP_ALG, totpStep: TOTP_STEP } }); fastify.decorateRequest('totpVerify', verifyTOTP); next(); }; var index = fp(plugin, { fastify: '>=5.x', name: 'fastify-totp' }); export { index as default };