UNPKG

@daitanjs/security

Version:

A lightweight library for token generation and verification.

179 lines (178 loc) 5.46 kB
// src/security/src/index.js import crypto from "crypto"; import jwt from "jsonwebtoken"; import { v4 as uuidv4 } from "uuid"; import { getLogger } from "@daitanjs/development"; import { DaitanInvalidInputError, DaitanOperationError } from "@daitanjs/error"; var logger = getLogger("daitan-security"); var DEFAULT_OTP_LENGTH = 6; var DEFAULT_OTP_VALIDITY_MS = 5 * 60 * 1e3; var generateNumericOTP = ({ length = DEFAULT_OTP_LENGTH, validityMs = DEFAULT_OTP_VALIDITY_MS } = {}) => { if (!Number.isInteger(length) || length <= 0 || length > 10) { throw new DaitanInvalidInputError( "OTP length must be a positive integer, typically between 4 and 10." ); } if (typeof validityMs !== "number" || validityMs <= 0) { throw new DaitanInvalidInputError( "OTP validityMs must be a positive number." ); } let otp = ""; for (let i = 0; i < length; i++) { otp += crypto.randomInt(0, 10).toString(); } const expiresAt = Date.now() + validityMs; logger.debug( `Generated numeric OTP (length ${length}), expires at ${new Date( expiresAt ).toISOString()}` ); return { otp, expiresAt }; }; var generateAlphanumericOTP = ({ length = 8, validityMs = DEFAULT_OTP_VALIDITY_MS } = {}) => { if (!Number.isInteger(length) || length <= 0 || length > 32) { throw new DaitanInvalidInputError( "Alphanumeric OTP length must be a positive integer, typically between 6 and 32." ); } if (typeof validityMs !== "number" || validityMs <= 0) { throw new DaitanInvalidInputError( "OTP validityMs must be a positive number." ); } const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; let otp = ""; const randomBytes = crypto.randomBytes(length); for (let i = 0; i < length; i++) { otp += characters[randomBytes[i] % characters.length]; } const expiresAt = Date.now() + validityMs; logger.debug( `Generated alphanumeric OTP (length ${length}), expires at ${new Date( expiresAt ).toISOString()}` ); return { otp, expiresAt }; }; var verifyOTP = ({ submittedOTP, storedOTP, storedOTPExpiresAt }) => { if (typeof submittedOTP !== "string" || !submittedOTP || typeof storedOTP !== "string" || !storedOTP || typeof storedOTPExpiresAt !== "number") { logger.warn("verifyOTP: Invalid parameters received for OTP verification."); return false; } if (Date.now() > storedOTPExpiresAt) { logger.debug("OTP verification failed: OTP has expired."); return false; } const isValid = crypto.timingSafeEqual( Buffer.from(submittedOTP), Buffer.from(storedOTP) ); if (!isValid) { logger.debug( "OTP verification failed: Submitted OTP does not match stored OTP." ); } else { logger.debug("OTP verification successful."); } return isValid; }; var DEFAULT_JWT_EXPIRY = "1h"; var generateJWT = ({ payload, secretOrPrivateKey, options = {} }) => { if (!payload || typeof payload !== "object" || Array.isArray(payload)) { throw new DaitanInvalidInputError( "JWT payload must be a non-null plain object." ); } if (!secretOrPrivateKey) { throw new DaitanInvalidInputError( "JWT secret or private key is required for signing." ); } const jwtOptions = { expiresIn: DEFAULT_JWT_EXPIRY, jwtid: uuidv4(), // JWT ID (jti) claim, useful for revocation or tracking ...options // User-provided options override defaults }; try { const token = jwt.sign(payload, secretOrPrivateKey, jwtOptions); logger.debug( `Generated JWT with JTI: ${jwtOptions.jwtid}, expires: ${jwtOptions.expiresIn}.` ); return token; } catch (error) { logger.error(`Error generating JWT: ${error.message}`, { errorName: error.name }); throw new DaitanOperationError( `JWT signing failed: ${error.message}`, {}, error ); } }; var verifyJWT = ({ token, secretOrPublicKey, options = {} }) => { if (!token || typeof token !== "string") { throw new DaitanInvalidInputError( "JWT token string is required for verification." ); } if (!secretOrPublicKey) { throw new DaitanInvalidInputError( "JWT secret or public key is required for verification." ); } try { const decoded = jwt.verify(token, secretOrPublicKey, options); logger.debug("JWT verified successfully.", { jti: decoded.jti, sub: decoded.sub }); return decoded; } catch (error) { logger.warn(`JWT verification failed: ${error.message}`, { errorName: error.name, tokenPreview: token.substring(0, 20) + "..." }); throw error; } }; var decodeJWT = ({ token, options = {} }) => { if (!token || typeof token !== "string") { logger.warn("decodeJWT: Invalid or missing token string provided."); return null; } try { const decoded = jwt.decode(token, options); if (decoded === null) { logger.warn("decodeJWT: Token was malformed and could not be decoded.", { tokenPreview: token.substring(0, 20) + "..." }); } else { logger.debug("JWT decoded (signature NOT VERIFIED)."); } return decoded; } catch (error) { logger.error(`Error during JWT decode (unexpected): ${error.message}`, { errorName: error.name }); return null; } }; export { decodeJWT, generateAlphanumericOTP, generateJWT, generateNumericOTP, verifyJWT, verifyOTP }; //# sourceMappingURL=index.js.map