@otplib/core 13.1.1 → 13.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/utils.ts","../src/errors.ts"],"sourcesContent":["import {\n OTPError,\n SecretTooShortError,\n SecretTooLongError,\n CounterNegativeError,\n CounterOverflowError,\n TimeNegativeError,\n PeriodTooSmallError,\n PeriodTooLargeError,\n TokenLengthError,\n TokenFormatError,\n CounterToleranceTooLargeError,\n EpochToleranceNegativeError,\n EpochToleranceTooLargeError,\n CryptoPluginMissingError,\n Base32PluginMissingError,\n SecretMissingError,\n LabelMissingError,\n IssuerMissingError,\n SecretTypeError,\n} from \"./errors.js\";\n\nimport type {\n HashAlgorithm,\n SecretOptions,\n OTPResultOk,\n OTPResultError,\n OTPResult,\n} from \"./types.js\";\n\n/**\n * Singleton TextEncoder instance to avoid repeated allocations\n */\nconst textEncoder = new TextEncoder();\n\n/**\n * Minimum secret length in bytes (128 bits as per RFC 4226)\n */\nexport const MIN_SECRET_BYTES = 16;\n\n/**\n * Maximum secret length in bytes (512 bits)\n *\n * The 64-byte maximum is not part of the RFCs.\n * This is to prevent excessive memory usage in HMAC operations.\n */\nexport const MAX_SECRET_BYTES = 64;\n\n/**\n * Recommended secret length in bytes (160 bits as per RFC 4226)\n */\nexport const RECOMMENDED_SECRET_BYTES = 20;\n\n/**\n * Minimum period in seconds\n */\nexport const MIN_PERIOD = 1;\n\n/**\n * Maximum period in seconds (1 hour)\n */\nexport const MAX_PERIOD = 3600;\n\n/**\n * Default period in seconds (30 seconds as per RFC 6238)\n */\nexport const DEFAULT_PERIOD = 30;\n\n/**\n * Maximum safe integer for counter (2^53 - 1)\n */\nexport const MAX_COUNTER = Number.MAX_SAFE_INTEGER;\n\n/**\n * Maximum verification window size\n *\n * Limits the number of HMAC computations during verification to prevent DoS attacks.\n * A window of 100 means up to 201 HMAC computations ([-100, +100] range).\n *\n * For TOTP: window=1 is typically sufficient (allows +-30 seconds clock drift)\n * For HOTP: window=10-50 handles reasonable counter desynchronization\n */\nexport const MAX_WINDOW = 100;\n\n/**\n * Configurable guardrails for OTP validation\n *\n * Allows overriding default safety limits for non-standard production requirements.\n * Use with caution - custom guardrails can weaken security.\n */\nexport type OTPGuardrailsConfig = {\n MIN_SECRET_BYTES: number;\n MAX_SECRET_BYTES: number;\n MIN_PERIOD: number;\n MAX_PERIOD: number;\n MAX_COUNTER: number;\n MAX_WINDOW: number;\n};\n\n/**\n * Module-private symbol to track guardrail override status\n *\n * This symbol is used as a property key to store whether guardrails contain custom values.\n * Being module-private and a symbol ensures:\n * - Cannot be accessed outside this module (not exported)\n * - Cannot be recreated (each Symbol() call is unique)\n * - Hidden from normal enumeration (Object.keys, JSON.stringify, for-in)\n * - Minimal memory overhead (~1 byte per object)\n * - No garbage collection concerns\n *\n * @internal\n */\nconst OVERRIDE_SYMBOL = Symbol(\"otplib.guardrails.override\");\n\n/**\n * Complete guardrails configuration\n *\n * This represents the final, immutable configuration used by validation functions.\n * Internally tracks whether any values were overridden from RFC recommendations,\n * enabling security auditing and compliance monitoring without exposing implementation\n * details in the public API.\n *\n * The override status is stored using a module-private Symbol that cannot be accessed\n * or recreated outside this module, providing true encapsulation.\n *\n * @see {@link OTPGuardrailsConfig} for the base configuration structure\n * @see {@link createGuardrails} for creating guardrails instances\n * @see {@link hasGuardrailOverrides} to check if guardrails were customized\n */\nexport type OTPGuardrails = Readonly<OTPGuardrailsConfig> & {\n [OVERRIDE_SYMBOL]?: boolean;\n};\n\n/**\n * Default guardrails matching RFC recommendations\n *\n * Frozen to ensure immutability. Used as default parameter for validation functions.\n * For custom guardrails, use the createGuardrails() factory function.\n */\nconst DEFAULT_GUARDRAILS: OTPGuardrails = Object.freeze({\n MIN_SECRET_BYTES,\n MAX_SECRET_BYTES,\n MIN_PERIOD,\n MAX_PERIOD,\n MAX_COUNTER,\n MAX_WINDOW,\n [OVERRIDE_SYMBOL]: false,\n});\n\n/**\n * Create guardrails configuration object\n *\n * Factory function that merges custom guardrails with defaults and returns\n * an immutable (frozen) object. Validates custom guardrails to ensure they\n * maintain basic safety invariants.\n *\n * When called without arguments or with `undefined`, returns the default guardrails\n * singleton (optimized to avoid unnecessary allocations). When called with custom\n * values, creates a new frozen object and internally marks it as overridden.\n *\n * @param custom - Optional partial guardrails to override defaults\n * @returns Frozen guardrails object\n * @throws {Error} If custom guardrails violate safety invariants\n *\n * @example Basic usage\n * ```ts\n * import { createGuardrails, hasGuardrailOverrides } from '@otplib/core'\n *\n * // Returns default singleton (no overrides)\n * const defaults = createGuardrails();\n * hasGuardrailOverrides(defaults); // false\n *\n * // Creates new object with overrides\n * const custom = createGuardrails({\n * MIN_SECRET_BYTES: 8,\n * MAX_WINDOW: 200\n * });\n * hasGuardrailOverrides(custom); // true\n * ```\n *\n * @example Monitoring custom guardrails\n * ```ts\n * import { createGuardrails, hasGuardrailOverrides } from '@otplib/core';\n *\n * const guardrails = createGuardrails({ MAX_WINDOW: 20 });\n *\n * if (hasGuardrailOverrides(guardrails)) {\n * logger.warn('Non-default guardrails in use', { guardrails });\n * }\n * ```\n *\n * @see {@link hasGuardrailOverrides} to check if guardrails were customized\n */\nexport function createGuardrails(custom?: Partial<OTPGuardrailsConfig>): OTPGuardrails {\n if (!custom) {\n return DEFAULT_GUARDRAILS;\n }\n\n return Object.freeze({\n ...DEFAULT_GUARDRAILS,\n ...custom,\n [OVERRIDE_SYMBOL]: true,\n });\n}\n\n/**\n * Check if guardrails contain custom overrides\n *\n * Returns `true` if the guardrails object was created with custom values,\n * `false` if using RFC-recommended defaults. Useful for security auditing,\n * compliance monitoring, and development warnings.\n *\n * This function accesses a module-private Symbol property that cannot be\n * accessed or modified outside this module, ensuring reliable detection.\n *\n * @param guardrails - The guardrails object to check\n * @returns `true` if guardrails were customized, `false` if using defaults\n *\n * @example Security monitoring\n * ```ts\n * import { createGuardrails, hasGuardrailOverrides } from '@otplib/core';\n *\n * const guardrails = createGuardrails({ MAX_WINDOW: 20 });\n *\n * if (hasGuardrailOverrides(guardrails)) {\n * console.warn('Custom guardrails detected:', guardrails);\n * // Log to security audit system\n * }\n * ```\n *\n * @example Compliance check\n * ```ts\n * function validateGuardrails(guardrails: OTPGuardrails) {\n * if (hasGuardrailOverrides(guardrails)) {\n * throw new Error('Custom guardrails not allowed in production');\n * }\n * }\n * ```\n */\nexport function hasGuardrailOverrides(guardrails: OTPGuardrails): boolean {\n return guardrails[OVERRIDE_SYMBOL] ?? false;\n}\n\n/**\n * Validate secret key\n *\n * @param secret - The secret to validate\n * @param guardrails - Validation guardrails (defaults to RFC recommendations)\n * @throws {SecretTooShortError} If secret is too short\n * @throws {SecretTooLongError} If secret is too long\n */\nexport function validateSecret(\n secret: Uint8Array,\n guardrails: OTPGuardrails = DEFAULT_GUARDRAILS,\n): void {\n if (secret.length < guardrails.MIN_SECRET_BYTES) {\n throw new SecretTooShortError(guardrails.MIN_SECRET_BYTES, secret.length);\n }\n\n if (secret.length > guardrails.MAX_SECRET_BYTES) {\n throw new SecretTooLongError(guardrails.MAX_SECRET_BYTES, secret.length);\n }\n}\n\n/**\n * Validate counter value\n *\n * @param counter - The counter to validate\n * @param guardrails - Validation guardrails (defaults to RFC recommendations)\n * @throws {CounterNegativeError} If counter is negative\n * @throws {CounterOverflowError} If counter exceeds safe integer\n */\nexport function validateCounter(\n counter: number | bigint,\n guardrails: OTPGuardrails = DEFAULT_GUARDRAILS,\n): void {\n const value = typeof counter === \"bigint\" ? counter : BigInt(counter);\n\n if (value < 0n) {\n throw new CounterNegativeError();\n }\n\n if (value > BigInt(guardrails.MAX_COUNTER)) {\n throw new CounterOverflowError();\n }\n}\n\n/**\n * Validate time value\n *\n * @param time - The time in seconds to validate\n * @throws {TimeNegativeError} If time is negative\n */\nexport function validateTime(time: number): void {\n if (time < 0) {\n throw new TimeNegativeError();\n }\n}\n\n/**\n * Validate period value\n *\n * @param period - The period in seconds to validate\n * @param guardrails - Validation guardrails (defaults to RFC recommendations)\n * @throws {PeriodTooSmallError} If period is too small\n * @throws {PeriodTooLargeError} If period is too large\n */\nexport function validatePeriod(\n period: number,\n guardrails: OTPGuardrails = DEFAULT_GUARDRAILS,\n): void {\n if (!Number.isInteger(period) || period < guardrails.MIN_PERIOD) {\n throw new PeriodTooSmallError(guardrails.MIN_PERIOD);\n }\n\n if (period > guardrails.MAX_PERIOD) {\n throw new PeriodTooLargeError(guardrails.MAX_PERIOD);\n }\n}\n\n/**\n * Validate token\n *\n * @param token - The token string to validate\n * @param digits - Expected number of digits\n * @throws {TokenLengthError} If token has incorrect length\n * @throws {TokenFormatError} If token contains non-digit characters\n */\nexport function validateToken(token: string, digits: number): void {\n if (token.length !== digits) {\n throw new TokenLengthError(digits, token.length);\n }\n\n if (!/^\\d+$/.test(token)) {\n throw new TokenFormatError();\n }\n}\n\n/**\n * Validate counter tolerance for HOTP verification\n *\n * Prevents DoS attacks by limiting the number of counter values checked.\n *\n * @param counterTolerance - Counter tolerance specification (number or array of offsets)\n * @param guardrails - Validation guardrails (defaults to RFC recommendations)\n * @throws {CounterToleranceTooLargeError} If tolerance size exceeds MAX_WINDOW\n *\n * @example\n * ```ts\n * validateCounterTolerance(1); // OK: 3 offsets [-1, 0, 1]\n * validateCounterTolerance(100); // OK: 201 offsets [-100, ..., 100]\n * validateCounterTolerance(101); // Throws: exceeds MAX_WINDOW\n * validateCounterTolerance([0, 1]); // OK: 2 offsets\n * ```\n */\nexport function validateCounterTolerance(\n counterTolerance: number | number[],\n guardrails: OTPGuardrails = DEFAULT_GUARDRAILS,\n): void {\n const size = Array.isArray(counterTolerance) ? counterTolerance.length : counterTolerance * 2 + 1;\n\n if (size > guardrails.MAX_WINDOW * 2 + 1) {\n throw new CounterToleranceTooLargeError(\n guardrails.MAX_WINDOW,\n Array.isArray(counterTolerance) ? counterTolerance.length : counterTolerance,\n );\n }\n}\n\n/**\n * Validate epoch tolerance for TOTP verification\n *\n * Prevents DoS attacks by limiting the time range checked.\n * Also validates that tolerance values are non-negative.\n *\n * @param epochTolerance - Epoch tolerance specification (number or tuple [past, future])\n * @param period - The TOTP period in seconds (default: 30). Used to calculate max tolerance.\n * @param guardrails - Validation guardrails (defaults to RFC recommendations)\n * @throws {EpochToleranceNegativeError} If tolerance contains negative values\n * @throws {EpochToleranceTooLargeError} If tolerance exceeds MAX_WINDOW periods\n *\n * @example\n * ```ts\n * validateEpochTolerance(30); // OK: 30 seconds (default period 30s)\n * validateEpochTolerance([5, 0]); // OK: 5 seconds past only\n * validateEpochTolerance([-5, 0]); // Throws: negative values not allowed\n * validateEpochTolerance(3600); // Throws: exceeds MAX_WINDOW * period\n * validateEpochTolerance(6000, 60); // OK with 60s period (MAX_WINDOW * 60 = 6000)\n * ```\n */\nexport function validateEpochTolerance(\n epochTolerance: number | [number, number],\n period: number = DEFAULT_PERIOD,\n guardrails: OTPGuardrails = DEFAULT_GUARDRAILS,\n): void {\n const [pastTolerance, futureTolerance] = Array.isArray(epochTolerance)\n ? epochTolerance\n : [epochTolerance, epochTolerance];\n\n // Check for negative values\n if (pastTolerance < 0 || futureTolerance < 0) {\n throw new EpochToleranceNegativeError();\n }\n\n // Check total tolerance doesn't exceed reasonable limits\n // Convert to periods and check against MAX_WINDOW\n const maxToleranceSeconds = guardrails.MAX_WINDOW * period;\n const maxAllowed = Math.max(pastTolerance, futureTolerance);\n\n if (maxAllowed > maxToleranceSeconds) {\n throw new EpochToleranceTooLargeError(maxToleranceSeconds, maxAllowed);\n }\n}\n\n/**\n * Convert counter to 8-byte big-endian array\n *\n * Per RFC 4226 Section 5.1, the counter value is represented as an 8-byte\n * big-endian (network byte order) unsigned integer.\n *\n * @see {@link https://tools.ietf.org/html/rfc4226#section-5.1 | RFC 4226 Section 5.1 - Symbol Descriptions}\n *\n * @param value - The counter value to convert\n * @returns 8-byte big-endian array\n */\nexport function counterToBytes(value: number | bigint): Uint8Array {\n const bigintValue = typeof value === \"bigint\" ? value : BigInt(value);\n const buffer = new ArrayBuffer(8);\n const view = new DataView(buffer);\n\n view.setBigUint64(0, bigintValue, false);\n\n return new Uint8Array(buffer);\n}\n\n/**\n * Perform Dynamic Truncation as per RFC 4226 Section 5.3\n *\n * The algorithm:\n * 1. Take the low-order 4 bits of the last byte as offset\n * 2. Extract 4 bytes starting at offset\n * 3. Mask the most significant bit to get a 31-bit unsigned integer\n *\n * This ensures consistent extraction across different HMAC output sizes\n * while producing a value that fits in a signed 32-bit integer.\n *\n * @see {@link https://tools.ietf.org/html/rfc4226#section-5.3 | RFC 4226 Section 5.3 - Generating an HOTP Value}\n *\n * @param hmacResult - HMAC result (at least 20 bytes for SHA-1)\n * @returns Truncated 31-bit unsigned integer\n */\nexport function dynamicTruncate(hmacResult: Uint8Array): number {\n const offset = hmacResult[hmacResult.length - 1] & 0x0f;\n\n const binary =\n ((hmacResult[offset] & 0x7f) << 24) |\n (hmacResult[offset + 1] << 16) |\n (hmacResult[offset + 2] << 8) |\n hmacResult[offset + 3];\n\n return binary;\n}\n\n/**\n * Convert truncated integer to OTP string with specified digits\n *\n * Computes: Snum mod 10^Digit (RFC 4226 Section 5.3)\n *\n * The result is zero-padded to ensure consistent length,\n * as required for proper token comparison.\n *\n * @see {@link https://tools.ietf.org/html/rfc4226#section-5.3 | RFC 4226 Section 5.3 - Generating an HOTP Value}\n *\n * @param value - The truncated integer value (Snum)\n * @param digits - Number of digits for the OTP (Digit, typically 6-8)\n * @returns OTP string with leading zeros if necessary\n */\nexport function truncateDigits(value: number, digits: number): string {\n const maxOtp = 10 ** digits;\n const otp = value % maxOtp;\n return otp.toString().padStart(digits, \"0\");\n}\n\n/**\n * Validate that two byte arrays have equal length\n *\n * Useful as a preliminary check before performing byte-by-byte comparisons.\n *\n * @param a - First byte array\n * @param b - Second byte array\n * @returns true if arrays have equal length, false otherwise\n */\nexport function validateByteLengthEqual(a: Uint8Array, b: Uint8Array): boolean {\n return a.length === b.length;\n}\n\n/**\n * Constant-time comparison to prevent timing attacks\n *\n * This implements a timing-safe equality check as recommended in\n * RFC 4226 Section 7.2 for token validation to prevent\n * timing side-channel attacks.\n *\n * @see {@link https://tools.ietf.org/html/rfc4226#section-7.2 | RFC 4226 Section 7.2 - Validation and Verification}\n *\n * @param a - First value to compare\n * @param b - Second value to compare\n * @returns true if values are equal, false otherwise\n */\nexport function constantTimeEqual(a: string | Uint8Array, b: string | Uint8Array): boolean {\n const bufA = stringToBytes(a);\n const bufB = stringToBytes(b);\n\n if (!validateByteLengthEqual(bufA, bufB)) {\n return false;\n }\n\n let result = 0;\n for (let i = 0; i < bufA.length; i++) {\n result |= bufA[i] ^ bufB[i];\n }\n\n return result === 0;\n}\n\n/**\n * Get HMAC digest size in bytes for a given algorithm\n *\n * @param algorithm - The hash algorithm\n * @returns Digest size in bytes\n */\nexport function getDigestSize(algorithm: HashAlgorithm): number {\n switch (algorithm) {\n case \"sha1\":\n return 20;\n case \"sha256\":\n return 32;\n case \"sha512\":\n return 64;\n }\n}\n\n/**\n * Convert a string or Uint8Array to Uint8Array\n *\n * This utility function normalizes input to Uint8Array, converting strings\n * using UTF-8 encoding. Uint8Array inputs are returned as-is.\n *\n * @param value - The value to convert (string or Uint8Array)\n * @returns The value as a Uint8Array (UTF-8 encoded for strings)\n *\n * @example\n * ```ts\n * import { stringToBytes } from '@otplib/core'\n *\n * const bytes1 = stringToBytes('1234567890123456')\n * // Returns: Uint8Array([49, 50, 51, 52, 53, 54, 55, 56, 57, 48, 49, 50, 51, 52, 53, 54])\n *\n * const bytes2 = stringToBytes(new Uint8Array([1, 2, 3]))\n * // Returns: Uint8Array([1, 2, 3]) - returned as-is\n * ```\n */\nexport function stringToBytes(value: string | Uint8Array): Uint8Array {\n return typeof value === \"string\" ? textEncoder.encode(value) : value;\n}\n\n/**\n * Convert a hex string to a Uint8Array\n *\n * This is useful for working with RFC test vectors and debugging HMAC outputs,\n * which are commonly represented as hexadecimal strings.\n *\n * If your environment supports it, consider using `Uint8Array.fromHex()` instead.\n *\n * @param hex - The hex string to convert (lowercase or uppercase, no 0x prefix)\n * @returns The bytes as a Uint8Array\n *\n * @example\n * ```ts\n * import { hexToBytes } from '@otplib/core'\n *\n * // Convert RFC 4226 HMAC test vector\n * const hmac = hexToBytes('cc93cf18508d94934c64b65d8ba7667fb7cde4b0')\n * // Returns: Uint8Array([0xcc, 0x93, 0xcf, ...])\n * ```\n */\nexport function hexToBytes(hex: string): Uint8Array {\n const bytes = new Uint8Array(hex.length / 2);\n for (let i = 0; i < hex.length; i += 2) {\n bytes[i / 2] = parseInt(hex.substring(i, i + 2), 16);\n }\n return bytes;\n}\n\n/**\n * Normalize secret input to Uint8Array\n *\n * Accepts either a Base32-encoded string or Uint8Array and returns Uint8Array.\n * If a Base32Plugin is provided, string secrets will be automatically decoded.\n *\n * @param secret - The secret to normalize (string or Uint8Array)\n * @param base32 - Optional Base32Plugin to decode string secrets\n * @returns The secret as Uint8Array\n * @throws {Error} If secret is a string but no Base32Plugin is provided\n *\n * @example\n * ```ts\n * import { normalizeSecret } from '@otplib/core'\n * import { ScureBase32Plugin } from '@otplib/plugin-base32-scure'\n *\n * const base32 = new ScureBase32Plugin()\n *\n * // Uint8Array - returned as-is\n * const secret1 = normalizeSecret(new Uint8Array([1, 2, 3]))\n *\n * // Base32 string - automatically decoded\n * const secret2 = normalizeSecret('JBSWY3DPEHPK3PXP', base32)\n * ```\n */\nexport function normalizeSecret(\n secret: string | Uint8Array,\n base32?: { decode: (str: string) => Uint8Array },\n): Uint8Array {\n if (typeof secret === \"string\") {\n requireBase32Plugin(base32);\n return base32.decode(secret);\n }\n return secret;\n}\n\n/**\n * Generate a random Base32-encoded secret\n *\n * Creates a cryptographically secure random secret suitable for OTP generation.\n * The default length of 20 bytes (160 bits) matches RFC 4226 recommendations\n * and provides good security margin.\n *\n * @param options - Secret generation options\n * @returns Base32-encoded secret string (without padding for Google Authenticator compatibility)\n *\n * @example\n * ```ts\n * import { generateSecret } from '@otplib/core';\n * import { NodeCryptoPlugin } from '@otplib/plugin-crypto-node';\n * import { ScureBase32Plugin } from '@otplib/plugin-base32-scure';\n *\n * const secret = generateSecret({\n * crypto: new NodeCryptoPlugin(),\n * base32: new ScureBase32Plugin(),\n * });\n * // Returns: 'JBSWY3DPEHPK3PXP...' (32 characters)\n * ```\n *\n * @example Custom length\n * ```ts\n * const secret = generateSecret({\n * crypto: new NodeCryptoPlugin(),\n * base32: new ScureBase32Plugin(),\n * length: 32, // 256 bits for SHA-256\n * });\n * ```\n */\nexport function generateSecret(options: SecretOptions): string {\n const { crypto, base32, length = RECOMMENDED_SECRET_BYTES } = options;\n\n requireCryptoPlugin(crypto);\n requireBase32Plugin(base32);\n\n const randomBytes = crypto.randomBytes(length);\n return base32.encode(randomBytes, { padding: false });\n}\n\n/**\n * Normalize counter tolerance to an array of offsets\n *\n * Converts a number or array counter tolerance specification into an array of offsets\n * - Number: creates symmetric range [-tolerance, +tolerance]\n * - Array: uses the array as-is (already contains specific offsets)\n *\n * @param counterTolerance - Counter tolerance specification (number or array of offsets)\n * @returns Array of offsets to check\n *\n * @example\n * ```ts\n * normalizeCounterTolerance(0) // [0]\n * normalizeCounterTolerance(1) // [-1, 0, 1]\n * normalizeCounterTolerance(2) // [-2, -1, 0, 1, 2]\n * normalizeCounterTolerance([0, 1]) // [0, 1]\n * normalizeCounterTolerance([-1, 0, 1]) // [-1, 0, 1]\n * ```\n */\nexport function normalizeCounterTolerance(counterTolerance: number | number[] = 0): number[] {\n if (Array.isArray(counterTolerance)) {\n return counterTolerance;\n }\n\n const result: number[] = [];\n for (let i = -counterTolerance; i <= counterTolerance; i++) {\n // Bitwise OR with 0 converts -0 to 0 and preserves other integers\n result.push(i | 0);\n }\n return result;\n}\n\n/**\n * Normalize epoch tolerance to [past, future] tuple\n *\n * Converts a number or tuple epoch tolerance specification into a [past, future] tuple\n * - Number: creates symmetric tolerance [tolerance, tolerance]\n * - Tuple: uses the tuple as-is\n *\n * @param epochTolerance - Epoch tolerance specification (number or tuple [past, future])\n * @returns Tuple [pastTolerance, futureTolerance] in seconds\n *\n * @example\n * ```ts\n * normalizeEpochTolerance(0) // [0, 0]\n * normalizeEpochTolerance(30) // [30, 30]\n * normalizeEpochTolerance([5, 0]) // [5, 0]\n * normalizeEpochTolerance([10, 5]) // [10, 5]\n * ```\n */\nexport function normalizeEpochTolerance(\n epochTolerance: number | [number, number] = 0,\n): [number, number] {\n return Array.isArray(epochTolerance) ? epochTolerance : [epochTolerance, epochTolerance];\n}\n\n/**\n * Require crypto plugin to be configured\n *\n * @param crypto - The crypto plugin\n * @throws {CryptoPluginMissingError} If crypto plugin is not set\n */\nexport function requireCryptoPlugin<T>(crypto: T | undefined): asserts crypto is T {\n if (!crypto) {\n throw new CryptoPluginMissingError();\n }\n}\n\n/**\n * Require Base32 plugin to be configured\n *\n * @param base32 - The Base32 plugin\n * @throws {Base32PluginMissingError} If Base32 plugin is not set\n */\nexport function requireBase32Plugin<T>(base32: T | undefined): asserts base32 is T {\n if (!base32) {\n throw new Base32PluginMissingError();\n }\n}\n\n/**\n * Require secret to be configured\n *\n * @param secret - The secret value\n * @throws {SecretMissingError} If secret is not set\n */\nexport function requireSecret<T>(secret: T | undefined): asserts secret is T {\n if (!secret) {\n throw new SecretMissingError();\n }\n}\n\n/**\n * Require label to be configured (for URI generation)\n *\n * @param label - The label value\n * @throws {LabelMissingError} If label is not set\n */\nexport function requireLabel(label: string | undefined): asserts label is string {\n if (!label) {\n throw new LabelMissingError();\n }\n}\n\n/**\n * Require issuer to be configured (for URI generation)\n *\n * @param issuer - The issuer value\n * @throws {IssuerMissingError} If issuer is not set\n */\nexport function requireIssuer(issuer: string | undefined): asserts issuer is string {\n if (!issuer) {\n throw new IssuerMissingError();\n }\n}\n\n/**\n * Require secret to be a Base32 string (for URI generation)\n *\n * @param secret - The secret value\n * @throws {SecretTypeError} If secret is not a string\n */\nexport function requireBase32String(secret: string | Uint8Array): asserts secret is string {\n if (typeof secret !== \"string\") {\n throw new SecretTypeError();\n }\n}\n\n/**\n * Create a success result\n * @internal\n */\nfunction ok<T>(value: T): OTPResultOk<T> {\n return { ok: true, value };\n}\n\n/**\n * Create a failure result\n * @internal\n */\nfunction err<E>(error: E): OTPResultError<E> {\n return { ok: false, error };\n}\n\n/**\n * Wrap a synchronous function to return OTPResult instead of throwing\n *\n * Preserves the original OTPError subclass so users can access\n * specific error information via instanceof checks.\n *\n * @internal\n */\nexport function wrapResult<T, Args extends unknown[]>(\n fn: (...args: Args) => T,\n): (...args: Args) => OTPResult<T, OTPError> {\n return (...args: Args): OTPResult<T, OTPError> => {\n try {\n return ok(fn(...args));\n } catch (error) {\n return err(error as OTPError);\n }\n };\n}\n\n/**\n * Wrap an async function to return OTPResult instead of throwing\n *\n * Preserves the original OTPError subclass so users can access\n * specific error information via instanceof checks.\n *\n * @internal\n */\nexport function wrapResultAsync<T, Args extends unknown[]>(\n fn: (...args: Args) => Promise<T>,\n): (...args: Args) => Promise<OTPResult<T, OTPError>> {\n return async (...args: Args): Promise<OTPResult<T, OTPError>> => {\n try {\n return ok(await fn(...args));\n } catch (error) {\n return err(error as OTPError);\n }\n };\n}\n","/**\n * Options for OTPError construction\n */\nexport type OTPErrorOptions = {\n /**\n * The underlying error that caused this error.\n * Useful for error chaining and debugging.\n */\n cause?: unknown;\n};\n\n/**\n * Base error class for all otplib errors\n *\n * Supports ES2022 error chaining via the `cause` property.\n *\n * @example\n * ```typescript\n * try {\n * // ... operation that throws\n * } catch (error) {\n * throw new OTPError('Operation failed', { cause: error });\n * }\n * ```\n */\nexport class OTPError extends Error {\n constructor(message: string, options?: OTPErrorOptions) {\n super(message, options);\n this.name = \"OTPError\";\n }\n}\n\n/**\n * Error thrown when secret validation fails\n */\nexport class SecretError extends OTPError {\n constructor(message: string) {\n super(message);\n this.name = \"SecretError\";\n }\n}\n\n/**\n * Error thrown when secret is too short (< 128 bits)\n */\nexport class SecretTooShortError extends SecretError {\n constructor(minBytes: number, actualBytes: number) {\n super(\n `Secret must be at least ${minBytes} bytes (${minBytes * 8} bits), got ${actualBytes} bytes`,\n );\n this.name = \"SecretTooShortError\";\n }\n}\n\n/**\n * Error thrown when secret is unreasonably large (> 64 bytes)\n */\nexport class SecretTooLongError extends SecretError {\n constructor(maxBytes: number, actualBytes: number) {\n super(`Secret must not exceed ${maxBytes} bytes, got ${actualBytes} bytes`);\n this.name = \"SecretTooLongError\";\n }\n}\n\n/**\n * Error thrown when counter is invalid\n */\nexport class CounterError extends OTPError {\n constructor(message: string) {\n super(message);\n this.name = \"CounterError\";\n }\n}\n\n/**\n * Error thrown when counter is negative\n */\nexport class CounterNegativeError extends CounterError {\n constructor() {\n super(\"Counter must be non-negative\");\n this.name = \"CounterNegativeError\";\n }\n}\n\n/**\n * Error thrown when counter exceeds maximum value (2^53 - 1 for safe integer)\n */\nexport class CounterOverflowError extends CounterError {\n constructor() {\n super(\"Counter exceeds maximum safe integer value\");\n this.name = \"CounterOverflowError\";\n }\n}\n\n/**\n * Error thrown when time is invalid\n */\nexport class TimeError extends OTPError {\n constructor(message: string) {\n super(message);\n this.name = \"TimeError\";\n }\n}\n\n/**\n * Error thrown when time is negative\n */\nexport class TimeNegativeError extends TimeError {\n constructor() {\n super(\"Time must be non-negative\");\n this.name = \"TimeNegativeError\";\n }\n}\n\n/**\n * Error thrown when period is invalid\n */\nexport class PeriodError extends OTPError {\n constructor(message: string) {\n super(message);\n this.name = \"PeriodError\";\n }\n}\n\n/**\n * Error thrown when period is too small\n */\nexport class PeriodTooSmallError extends PeriodError {\n constructor(minPeriod: number) {\n super(`Period must be at least ${minPeriod} second(s)`);\n this.name = \"PeriodTooSmallError\";\n }\n}\n\n/**\n * Error thrown when period is too large\n */\nexport class PeriodTooLargeError extends PeriodError {\n constructor(maxPeriod: number) {\n super(`Period must not exceed ${maxPeriod} seconds`);\n this.name = \"PeriodTooLargeError\";\n }\n}\n\n/**\n * Error thrown when digits value is invalid\n */\nexport class DigitsError extends OTPError {\n constructor(message: string) {\n super(message);\n this.name = \"DigitsError\";\n }\n}\n\n/**\n * Error thrown when hash algorithm is invalid\n */\nexport class AlgorithmError extends OTPError {\n constructor(message: string) {\n super(message);\n this.name = \"AlgorithmError\";\n }\n}\n\n/**\n * Error thrown when token is invalid\n */\nexport class TokenError extends OTPError {\n constructor(message: string) {\n super(message);\n this.name = \"TokenError\";\n }\n}\n\n/**\n * Error thrown when token has incorrect length\n */\nexport class TokenLengthError extends TokenError {\n constructor(expected: number, actual: number) {\n super(`Token must be ${expected} digits, got ${actual}`);\n this.name = \"TokenLengthError\";\n }\n}\n\n/**\n * Error thrown when token contains non-digit characters\n */\nexport class TokenFormatError extends TokenError {\n constructor() {\n super(\"Token must contain only digits\");\n this.name = \"TokenFormatError\";\n }\n}\n\n/**\n * Error thrown when crypto operation fails\n */\nexport class CryptoError extends OTPError {\n constructor(message: string, options?: OTPErrorOptions) {\n super(message, options);\n this.name = \"CryptoError\";\n }\n}\n\n/**\n * Error thrown when HMAC computation fails\n *\n * The original error from the crypto plugin is available via `cause`.\n *\n * @example\n * ```typescript\n * try {\n * await cryptoContext.hmac('sha1', key, data);\n * } catch (error) {\n * if (error instanceof HMACError) {\n * console.log('HMAC failed:', error.message);\n * console.log('Original error:', error.cause);\n * }\n * }\n * ```\n */\nexport class HMACError extends CryptoError {\n constructor(message: string, options?: OTPErrorOptions) {\n super(`HMAC computation failed: ${message}`, options);\n this.name = \"HMACError\";\n }\n}\n\n/**\n * Error thrown when random byte generation fails\n *\n * The original error from the crypto plugin is available via `cause`.\n */\nexport class RandomBytesError extends CryptoError {\n constructor(message: string, options?: OTPErrorOptions) {\n super(`Random byte generation failed: ${message}`, options);\n this.name = \"RandomBytesError\";\n }\n}\n\n/**\n * Error thrown when Base32 operation fails\n */\nexport class Base32Error extends OTPError {\n constructor(message: string, options?: OTPErrorOptions) {\n super(message, options);\n this.name = \"Base32Error\";\n }\n}\n\n/**\n * Error thrown when Base32 encoding fails\n *\n * The original error from the Base32 plugin is available via `cause`.\n *\n * @example\n * ```typescript\n * try {\n * base32Context.encode(data);\n * } catch (error) {\n * if (error instanceof Base32EncodeError) {\n * console.log('Encoding failed:', error.message);\n * console.log('Original error:', error.cause);\n * }\n * }\n * ```\n */\nexport class Base32EncodeError extends Base32Error {\n constructor(message: string, options?: OTPErrorOptions) {\n super(`Base32 encoding failed: ${message}`, options);\n this.name = \"Base32EncodeError\";\n }\n}\n\n/**\n * Error thrown when Base32 decoding fails\n *\n * The original error from the Base32 plugin is available via `cause`.\n *\n * @example\n * ```typescript\n * try {\n * base32Context.decode(invalidString);\n * } catch (error) {\n * if (error instanceof Base32DecodeError) {\n * console.log('Decoding failed:', error.message);\n * console.log('Original error:', error.cause);\n * }\n * }\n * ```\n */\nexport class Base32DecodeError extends Base32Error {\n constructor(message: string, options?: OTPErrorOptions) {\n super(`Base32 decoding failed: ${message}`, options);\n this.name = \"Base32DecodeError\";\n }\n}\n\n/**\n * Error thrown when counter tolerance is invalid\n */\nexport class CounterToleranceError extends OTPError {\n constructor(message: string) {\n super(message);\n this.name = \"CounterToleranceError\";\n }\n}\n\n/**\n * Error thrown when counter tolerance is too large\n */\nexport class CounterToleranceTooLargeError extends CounterToleranceError {\n constructor(maxTolerance: number, actualSize: number) {\n super(\n `Counter tolerance size must not exceed ${maxTolerance}, got ${actualSize}. ` +\n `Large tolerances can cause performance issues.`,\n );\n this.name = \"CounterToleranceTooLargeError\";\n }\n}\n\n/**\n * Error thrown when epoch tolerance is invalid\n */\nexport class EpochToleranceError extends OTPError {\n constructor(message: string) {\n super(message);\n this.name = \"EpochToleranceError\";\n }\n}\n\n/**\n * Error thrown when epoch tolerance contains negative values\n */\nexport class EpochToleranceNegativeError extends EpochToleranceError {\n constructor() {\n super(\"Epoch tolerance cannot contain negative values\");\n this.name = \"EpochToleranceNegativeError\";\n }\n}\n\n/**\n * Error thrown when epoch tolerance is too large\n */\nexport class EpochToleranceTooLargeError extends EpochToleranceError {\n constructor(maxTolerance: number, actualValue: number) {\n super(\n `Epoch tolerance must not exceed ${maxTolerance} seconds, got ${actualValue}. ` +\n `Large tolerances can cause performance issues.`,\n );\n this.name = \"EpochToleranceTooLargeError\";\n }\n}\n\n/**\n * Error thrown when a required plugin is missing\n */\nexport class PluginError extends OTPError {\n constructor(message: string) {\n super(message);\n this.name = \"PluginError\";\n }\n}\n\n/**\n * Error thrown when crypto plugin is not configured\n */\nexport class CryptoPluginMissingError extends PluginError {\n constructor() {\n super(\"Crypto plugin is required.\");\n this.name = \"CryptoPluginMissingError\";\n }\n}\n\n/**\n * Error thrown when Base32 plugin is not configured\n */\nexport class Base32PluginMissingError extends PluginError {\n constructor() {\n super(\"Base32 plugin is required.\");\n this.name = \"Base32PluginMissingError\";\n }\n}\n\n/**\n * Error thrown when required configuration is missing\n */\nexport class ConfigurationError extends OTPError {\n constructor(message: string) {\n super(message);\n this.name = \"ConfigurationError\";\n }\n}\n\n/**\n * Error thrown when secret is not configured\n */\nexport class SecretMissingError extends ConfigurationError {\n constructor() {\n super(\n \"Secret is required. \" +\n \"Use generateSecret() to create one, or provide via { secret: 'YOUR_BASE32_SECRET' }\",\n );\n this.name = \"SecretMissingError\";\n }\n}\n\n/**\n * Error thrown when label is not configured (required for URI generation)\n */\nexport class LabelMissingError extends ConfigurationError {\n constructor() {\n super(\"Label is required for URI generation. Example: { label: 'user@example.com' }\");\n this.name = \"LabelMissingError\";\n }\n}\n\n/**\n * Error thrown when issuer is not configured (required for URI generation)\n */\nexport class IssuerMissingError extends ConfigurationError {\n constructor() {\n super(\"Issuer is required for URI generation. Example: { issuer: 'MyApp' }\");\n this.name = \"IssuerMissingError\";\n }\n}\n\n/**\n * Error thrown when secret must be a Base32 string but is provided as bytes\n */\nexport class SecretTypeError extends ConfigurationError {\n constructor() {\n super(\n \"Class API requires secret to be a Base32 string, not Uint8Array. \" +\n \"Use generateSecret() or provide a Base32-encoded string.\",\n );\n this.name = \"SecretTypeError\";\n }\n}\n"],"mappings":"+aAAA,IAAAA,GAAA,GAAAC,GAAAD,GAAA,oBAAAE,EAAA,gBAAAC,EAAA,eAAAC,EAAA,qBAAAC,EAAA,eAAAC,EAAA,eAAAC,EAAA,qBAAAC,EAAA,6BAAAC,EAAA,sBAAAC,GAAA,mBAAAC,GAAA,qBAAAC,GAAA,oBAAAC,GAAA,mBAAAC,GAAA,kBAAAC,GAAA,0BAAAC,GAAA,eAAAC,GAAA,8BAAAC,GAAA,4BAAAC,GAAA,oBAAAC,GAAA,wBAAAC,EAAA,wBAAAC,GAAA,wBAAAC,EAAA,kBAAAC,GAAA,iBAAAC,GAAA,kBAAAC,GAAA,kBAAAC,EAAA,mBAAAC,GAAA,4BAAAC,EAAA,oBAAAC,GAAA,6BAAAC,GAAA,2BAAAC,GAAA,mBAAAC,GAAA,mBAAAC,GAAA,iBAAAC,GAAA,kBAAAC,GAAA,eAAAC,GAAA,oBAAAC,KAAA,eAAAC,GAAAvC,ICyBO,IAAMwC,EAAN,cAAuB,KAAM,CAClC,YAAYC,EAAiBC,EAA2B,CACtD,MAAMD,EAASC,CAAO,EACtB,KAAK,KAAO,UACd,CACF,EAKaC,EAAN,cAA0BH,CAAS,CACxC,YAAYC,EAAiB,CAC3B,MAAMA,CAAO,EACb,KAAK,KAAO,aACd,CACF,EAKaG,EAAN,cAAkCD,CAAY,CACnD,YAAYE,EAAkBC,EAAqB,CACjD,MACE,2BAA2BD,CAAQ,WAAWA,EAAW,CAAC,eAAeC,CAAW,QACtF,EACA,KAAK,KAAO,qBACd,CACF,EAKaC,EAAN,cAAiCJ,CAAY,CAClD,YAAYK,EAAkBF,EAAqB,CACjD,MAAM,0BAA0BE,CAAQ,eAAeF,CAAW,QAAQ,EAC1E,KAAK,KAAO,oBACd,CACF,EAKaG,EAAN,cAA2BT,CAAS,CACzC,YAAYC,EAAiB,CAC3B,MAAMA,CAAO,EACb,KAAK,KAAO,cACd,CACF,EAKaS,EAAN,cAAmCD,CAAa,CACrD,aAAc,CACZ,MAAM,8BAA8B,EACpC,KAAK,KAAO,sBACd,CACF,EAKaE,EAAN,cAAmCF,CAAa,CACrD,aAAc,CACZ,MAAM,4CAA4C,EAClD,KAAK,KAAO,sBACd,CACF,EAKaG,EAAN,cAAwBZ,CAAS,CACtC,YAAYC,EAAiB,CAC3B,MAAMA,CAAO,EACb,KAAK,KAAO,WACd,CACF,EAKaY,EAAN,cAAgCD,CAAU,CAC/C,aAAc,CACZ,MAAM,2BAA2B,EACjC,KAAK,KAAO,mBACd,CACF,EAKaE,EAAN,cAA0Bd,CAAS,CACxC,YAAYC,EAAiB,CAC3B,MAAMA,CAAO,EACb,KAAK,KAAO,aACd,CACF,EAKac,EAAN,cAAkCD,CAAY,CACnD,YAAYE,EAAmB,CAC7B,MAAM,2BAA2BA,CAAS,YAAY,EACtD,KAAK,KAAO,qBACd,CACF,EAKaC,EAAN,cAAkCH,CAAY,CACnD,YAAYI,EAAmB,CAC7B,MAAM,0BAA0BA,CAAS,UAAU,EACnD,KAAK,KAAO,qBACd,CACF,EAyBO,IAAMC,EAAN,cAAyBC,CAAS,CACvC,YAAYC,EAAiB,CAC3B,MAAMA,CAAO,EACb,KAAK,KAAO,YACd,CACF,EAKaC,EAAN,cAA+BH,CAAW,CAC/C,YAAYI,EAAkBC,EAAgB,CAC5C,MAAM,iBAAiBD,CAAQ,gBAAgBC,CAAM,EAAE,EACvD,KAAK,KAAO,kBACd,CACF,EAKaC,EAAN,cAA+BN,CAAW,CAC/C,aAAc,CACZ,MAAM,gCAAgC,EACtC,KAAK,KAAO,kBACd,CACF,EA6GO,IAAMO,EAAN,cAAoCC,CAAS,CAClD,YAAYC,EAAiB,CAC3B,MAAMA,CAAO,EACb,KAAK,KAAO,uBACd,CACF,EAKaC,EAAN,cAA4CH,CAAsB,CACvE,YAAYI,EAAsBC,EAAoB,CACpD,MACE,0CAA0CD,CAAY,SAASC,CAAU,kDAE3E,EACA,KAAK,KAAO,+BACd,CACF,EAKaC,EAAN,cAAkCL,CAAS,CAChD,YAAYC,EAAiB,CAC3B,MAAMA,CAAO,EACb,KAAK,KAAO,qBACd,CACF,EAKaK,EAAN,cAA0CD,CAAoB,CACnE,aAAc,CACZ,MAAM,gDAAgD,EACtD,KAAK,KAAO,6BACd,CACF,EAKaE,EAAN,cAA0CF,CAAoB,CACnE,YAAYF,EAAsBK,EAAqB,CACrD,MACE,mCAAmCL,CAAY,iBAAiBK,CAAW,kDAE7E,EACA,KAAK,KAAO,6BACd,CACF,EAKaC,EAAN,cAA0BT,CAAS,CACxC,YAAYC,EAAiB,CAC3B,MAAMA,CAAO,EACb,KAAK,KAAO,aACd,CACF,EAKaS,EAAN,cAAuCD,CAAY,CACxD,aAAc,CACZ,MAAM,4BAA4B,EAClC,KAAK,KAAO,0BACd,CACF,EAKaE,EAAN,cAAuCF,CAAY,CACxD,aAAc,CACZ,MAAM,4BAA4B,EAClC,KAAK,KAAO,0BACd,CACF,EAKaG,EAAN,cAAiCZ,CAAS,CAC/C,YAAYC,EAAiB,CAC3B,MAAMA,CAAO,EACb,KAAK,KAAO,oBACd,CACF,EAKaY,EAAN,cAAiCD,CAAmB,CACzD,aAAc,CACZ,MACE,yGAEF,EACA,KAAK,KAAO,oBACd,CACF,EAKaE,EAAN,cAAgCF,CAAmB,CACxD,aAAc,CACZ,MAAM,8EAA8E,EACpF,KAAK,KAAO,mBACd,CACF,EAKaG,EAAN,cAAiCH,CAAmB,CACzD,aAAc,CACZ,MAAM,qEAAqE,EAC3E,KAAK,KAAO,oBACd,CACF,EAKaI,EAAN,cAA8BJ,CAAmB,CACtD,aAAc,CACZ,MACE,2HAEF,EACA,KAAK,KAAO,iBACd,CACF,EDrZA,IAAMK,GAAc,IAAI,YAKXC,EAAmB,GAQnBC,EAAmB,GAKnBC,EAA2B,GAK3BC,EAAa,EAKbC,EAAa,KAKbC,EAAiB,GAKjBC,EAAc,OAAO,iBAWrBC,EAAa,IA8BpBC,EAAkB,OAAO,4BAA4B,EA2BrDC,EAAoC,OAAO,OAAO,CACtD,iBAAAT,EACA,iBAAAC,EACA,WAAAE,EACA,WAAAC,EACA,YAAAE,EACA,WAAAC,EACA,CAACC,CAAe,EAAG,EACrB,CAAC,EA8CM,SAASE,GAAiBC,EAAsD,CACrF,OAAKA,EAIE,OAAO,OAAO,CACnB,GAAGF,EACH,GAAGE,EACH,CAACH,CAAe,EAAG,EACrB,CAAC,EAPQC,CAQX,CAoCO,SAASG,GAAsBC,EAAoC,CACxE,OAAOA,EAAWL,CAAe,GAAK,EACxC,CAUO,SAASM,GACdC,EACAF,EAA4BJ,EACtB,CACN,GAAIM,EAAO,OAASF,EAAW,iBAC7B,MAAM,IAAIG,EAAoBH,EAAW,iBAAkBE,EAAO,MAAM,EAG1E,GAAIA,EAAO,OAASF,EAAW,iBAC7B,MAAM,IAAII,EAAmBJ,EAAW,iBAAkBE,EAAO,MAAM,CAE3E,CAUO,SAASG,GACdC,EACAN,EAA4BJ,EACtB,CACN,IAAMW,EAAQ,OAAOD,GAAY,SAAWA,EAAU,OAAOA,CAAO,EAEpE,GAAIC,EAAQ,GACV,MAAM,IAAIC,EAGZ,GAAID,EAAQ,OAAOP,EAAW,WAAW,EACvC,MAAM,IAAIS,CAEd,CAQO,SAASC,GAAaC,EAAoB,CAC/C,GAAIA,EAAO,EACT,MAAM,IAAIC,CAEd,CAUO,SAASC,GACdC,EACAd,EAA4BJ,EACtB,CACN,GAAI,CAAC,OAAO,UAAUkB,CAAM,GAAKA,EAASd,EAAW,WACnD,MAAM,IAAIe,EAAoBf,EAAW,UAAU,EAGrD,GAAIc,EAASd,EAAW,WACtB,MAAM,IAAIgB,EAAoBhB,EAAW,UAAU,CAEvD,CAUO,SAASiB,GAAcC,EAAeC,EAAsB,CACjE,GAAID,EAAM,SAAWC,EACnB,MAAM,IAAIC,EAAiBD,EAAQD,EAAM,MAAM,EAGjD,GAAI,CAAC,QAAQ,KAAKA,CAAK,EACrB,MAAM,IAAIG,CAEd,CAmBO,SAASC,GACdC,EACAvB,EAA4BJ,EACtB,CAGN,IAFa,MAAM,QAAQ2B,CAAgB,EAAIA,EAAiB,OAASA,EAAmB,EAAI,GAErFvB,EAAW,WAAa,EAAI,EACrC,MAAM,IAAIwB,EACRxB,EAAW,WACX,MAAM,QAAQuB,CAAgB,EAAIA,EAAiB,OAASA,CAC9D,CAEJ,CAuBO,SAASE,GACdC,EACAZ,EAAiBtB,EACjBQ,EAA4BJ,EACtB,CACN,GAAM,CAAC+B,EAAeC,CAAe,EAAI,MAAM,QAAQF,CAAc,EACjEA,EACA,CAACA,EAAgBA,CAAc,EAGnC,GAAIC,EAAgB,GAAKC,EAAkB,EACzC,MAAM,IAAIC,EAKZ,IAAMC,EAAsB9B,EAAW,WAAac,EAC9CiB,EAAa,KAAK,IAAIJ,EAAeC,CAAe,EAE1D,GAAIG,EAAaD,EACf,MAAM,IAAIE,EAA4BF,EAAqBC,CAAU,CAEzE,CAaO,SAASE,GAAe1B,EAAoC,CACjE,IAAM2B,EAAc,OAAO3B,GAAU,SAAWA,EAAQ,OAAOA,CAAK,EAC9D4B,EAAS,IAAI,YAAY,CAAC,EAGhC,OAFa,IAAI,SAASA,CAAM,EAE3B,aAAa,EAAGD,EAAa,EAAK,EAEhC,IAAI,WAAWC,CAAM,CAC9B,CAkBO,SAASC,GAAgBC,EAAgC,CAC9D,IAAMC,EAASD,EAAWA,EAAW,OAAS,CAAC,EAAI,GAQnD,OALIA,EAAWC,CAAM,EAAI,MAAS,GAC/BD,EAAWC,EAAS,CAAC,GAAK,GAC1BD,EAAWC,EAAS,CAAC,GAAK,EAC3BD,EAAWC,EAAS,CAAC,CAGzB,CAgBO,SAASC,GAAehC,EAAeY,EAAwB,CACpE,IAAMqB,EAAS,IAAMrB,EAErB,OADYZ,EAAQiC,GACT,SAAS,EAAE,SAASrB,EAAQ,GAAG,CAC5C,CAWO,SAASsB,EAAwBC,EAAeC,EAAwB,CAC7E,OAAOD,EAAE,SAAWC,EAAE,MACxB,CAeO,SAASC,GAAkBF,EAAwBC,EAAiC,CACzF,IAAME,EAAOC,EAAcJ,CAAC,EACtBK,EAAOD,EAAcH,CAAC,EAE5B,GAAI,CAACF,EAAwBI,EAAME,CAAI,EACrC,MAAO,GAGT,IAAIC,EAAS,EACb,QAAS,EAAI,EAAG,EAAIH,EAAK,OAAQ,IAC/BG,GAAUH,EAAK,CAAC,EAAIE,EAAK,CAAC,EAG5B,OAAOC,IAAW,CACpB,CAQO,SAASC,GAAcC,EAAkC,CAC9D,OAAQA,EAAW,CACjB,IAAK,OACH,MAAO,IACT,IAAK,SACH,MAAO,IACT,IAAK,SACH,MAAO,GACX,CACF,CAsBO,SAASJ,EAAcvC,EAAwC,CACpE,OAAO,OAAOA,GAAU,SAAWrB,GAAY,OAAOqB,CAAK,EAAIA,CACjE,CAsBO,SAAS4C,GAAWC,EAAyB,CAClD,IAAMC,EAAQ,IAAI,WAAWD,EAAI,OAAS,CAAC,EAC3C,QAASE,EAAI,EAAGA,EAAIF,EAAI,OAAQE,GAAK,EACnCD,EAAMC,EAAI,CAAC,EAAI,SAASF,EAAI,UAAUE,EAAGA,EAAI,CAAC,EAAG,EAAE,EAErD,OAAOD,CACT,CA2BO,SAASE,GACdrD,EACAsD,EACY,CACZ,OAAI,OAAOtD,GAAW,UACpBuD,EAAoBD,CAAM,EACnBA,EAAO,OAAOtD,CAAM,GAEtBA,CACT,CAkCO,SAASwD,GAAeC,EAAgC,CAC7D,GAAM,CAAE,OAAAC,EAAQ,OAAAJ,EAAQ,OAAAK,EAASxE,CAAyB,EAAIsE,EAE9DG,EAAoBF,CAAM,EAC1BH,EAAoBD,CAAM,EAE1B,IAAMO,EAAcH,EAAO,YAAYC,CAAM,EAC7C,OAAOL,EAAO,OAAOO,EAAa,CAAE,QAAS,EAAM,CAAC,CACtD,CAqBO,SAASC,GAA0BzC,EAAsC,EAAa,CAC3F,GAAI,MAAM,QAAQA,CAAgB,EAChC,OAAOA,EAGT,IAAMyB,EAAmB,CAAC,EAC1B,QAASM,EAAI,CAAC/B,EAAkB+B,GAAK/B,EAAkB+B,IAErDN,EAAO,KAAKM,EAAI,CAAC,EAEnB,OAAON,CACT,CAoBO,SAASiB,GACdvC,EAA4C,EAC1B,CAClB,OAAO,MAAM,QAAQA,CAAc,EAAIA,EAAiB,CAACA,EAAgBA,CAAc,CACzF,CAQO,SAASoC,EAAuBF,EAA4C,CACjF,GAAI,CAACA,EACH,MAAM,IAAIM,CAEd,CAQO,SAAST,EAAuBD,EAA4C,CACjF,GAAI,CAACA,EACH,MAAM,IAAIW,CAEd,CAQO,SAASC,GAAiBlE,EAA4C,CAC3E,GAAI,CAACA,EACH,MAAM,IAAImE,CAEd,CAQO,SAASC,GAAaC,EAAoD,CAC/E,GAAI,CAACA,EACH,MAAM,IAAIC,CAEd,CAQO,SAASC,GAAcC,EAAsD,CAClF,GAAI,CAACA,EACH,MAAM,IAAIC,CAEd,CAQO,SAASC,GAAoB1E,EAAuD,CACzF,GAAI,OAAOA,GAAW,SACpB,MAAM,IAAI2E,CAEd,CAMA,SAASC,EAAMvE,EAA0B,CACvC,MAAO,CAAE,GAAI,GAAM,MAAAA,CAAM,CAC3B,CAMA,SAASwE,EAAOC,EAA6B,CAC3C,MAAO,CAAE,GAAI,GAAO,MAAAA,CAAM,CAC5B,CAUO,SAASC,GACdC,EAC2C,CAC3C,MAAO,IAAIC,IAAuC,CAChD,GAAI,CACF,OAAOL,EAAGI,EAAG,GAAGC,CAAI,CAAC,CACvB,OAASH,EAAO,CACd,OAAOD,EAAIC,CAAiB,CAC9B,CACF,CACF,CAUO,SAASI,GACdF,EACoD,CACpD,MAAO,UAAUC,IAAgD,CAC/D,GAAI,CACF,OAAOL,EAAG,MAAMI,EAAG,GAAGC,CAAI,CAAC,CAC7B,OAASH,EAAO,CACd,OAAOD,EAAIC,CAAiB,CAC9B,CACF,CACF","names":["utils_exports","__export","DEFAULT_PERIOD","MAX_COUNTER","MAX_PERIOD","MAX_SECRET_BYTES","MAX_WINDOW","MIN_PERIOD","MIN_SECRET_BYTES","RECOMMENDED_SECRET_BYTES","constantTimeEqual","counterToBytes","createGuardrails","dynamicTruncate","generateSecret","getDigestSize","hasGuardrailOverrides","hexToBytes","normalizeCounterTolerance","normalizeEpochTolerance","normalizeSecret","requireBase32Plugin","requireBase32String","requireCryptoPlugin","requireIssuer","requireLabel","requireSecret","stringToBytes","truncateDigits","validateByteLengthEqual","validateCounter","validateCounterTolerance","validateEpochTolerance","validatePeriod","validateSecret","validateTime","validateToken","wrapResult","wrapResultAsync","__toCommonJS","OTPError","message","options","SecretError","SecretTooShortError","minBytes","actualBytes","SecretTooLongError","maxBytes","CounterError","CounterNegativeError","CounterOverflowError","TimeError","TimeNegativeError","PeriodError","PeriodTooSmallError","minPeriod","PeriodTooLargeError","maxPeriod","TokenError","OTPError","message","TokenLengthError","expected","actual","TokenFormatError","CounterToleranceError","OTPError","message","CounterToleranceTooLargeError","maxTolerance","actualSize","EpochToleranceError","EpochToleranceNegativeError","EpochToleranceTooLargeError","actualValue","PluginError","CryptoPluginMissingError","Base32PluginMissingError","ConfigurationError","SecretMissingError","LabelMissingError","IssuerMissingError","SecretTypeError","textEncoder","MIN_SECRET_BYTES","MAX_SECRET_BYTES","RECOMMENDED_SECRET_BYTES","MIN_PERIOD","MAX_PERIOD","DEFAULT_PERIOD","MAX_COUNTER","MAX_WINDOW","OVERRIDE_SYMBOL","DEFAULT_GUARDRAILS","createGuardrails","custom","hasGuardrailOverrides","guardrails","validateSecret","secret","SecretTooShortError","SecretTooLongError","validateCounter","counter","value","CounterNegativeError","CounterOverflowError","validateTime","time","TimeNegativeError","validatePeriod","period","PeriodTooSmallError","PeriodTooLargeError","validateToken","token","digits","TokenLengthError","TokenFormatError","validateCounterTolerance","counterTolerance","CounterToleranceTooLargeError","validateEpochTolerance","epochTolerance","pastTolerance","futureTolerance","EpochToleranceNegativeError","maxToleranceSeconds","maxAllowed","EpochToleranceTooLargeError","counterToBytes","bigintValue","buffer","dynamicTruncate","hmacResult","offset","truncateDigits","maxOtp","validateByteLengthEqual","a","b","constantTimeEqual","bufA","stringToBytes","bufB","result","getDigestSize","algorithm","hexToBytes","hex","bytes","i","normalizeSecret","base32","requireBase32Plugin","generateSecret","options","crypto","length","requireCryptoPlugin","randomBytes","normalizeCounterTolerance","normalizeEpochTolerance","CryptoPluginMissingError","Base32PluginMissingError","requireSecret","SecretMissingError","requireLabel","label","LabelMissingError","requireIssuer","issuer","IssuerMissingError","requireBase32String","SecretTypeError","ok","err","error","wrapResult","fn","args","wrapResultAsync"]}
1
+ {"version":3,"sources":["../src/utils.ts","../src/errors.ts"],"sourcesContent":["import {\n OTPError,\n SecretTooShortError,\n SecretTooLongError,\n CounterNegativeError,\n CounterOverflowError,\n TimeNegativeError,\n PeriodTooSmallError,\n PeriodTooLargeError,\n TokenLengthError,\n TokenFormatError,\n CounterToleranceTooLargeError,\n CounterToleranceNegativeError,\n EpochToleranceNegativeError,\n EpochToleranceTooLargeError,\n CryptoPluginMissingError,\n Base32PluginMissingError,\n SecretMissingError,\n LabelMissingError,\n IssuerMissingError,\n SecretTypeError,\n} from \"./errors.js\";\n\nimport type {\n HashAlgorithm,\n SecretOptions,\n OTPResultOk,\n OTPResultError,\n OTPResult,\n} from \"./types.js\";\n\n/**\n * Singleton TextEncoder instance to avoid repeated allocations\n */\nconst textEncoder = new TextEncoder();\n\n/**\n * Singleton TextDecoder instance to avoid repeated allocations\n */\nconst textDecoder = new TextDecoder();\n\n/**\n * Minimum secret length in bytes (128 bits as per RFC 4226)\n */\nexport const MIN_SECRET_BYTES = 16;\n\n/**\n * Maximum secret length in bytes (512 bits)\n *\n * The 64-byte maximum is not part of the RFCs.\n * This is to prevent excessive memory usage in HMAC operations.\n */\nexport const MAX_SECRET_BYTES = 64;\n\n/**\n * Recommended secret length in bytes (160 bits as per RFC 4226)\n */\nexport const RECOMMENDED_SECRET_BYTES = 20;\n\n/**\n * Minimum period in seconds\n */\nexport const MIN_PERIOD = 1;\n\n/**\n * Maximum period in seconds (1 hour)\n */\nexport const MAX_PERIOD = 3600;\n\n/**\n * Default period in seconds (30 seconds as per RFC 6238)\n */\nexport const DEFAULT_PERIOD = 30;\n\n/**\n * Maximum safe integer for counter (2^53 - 1)\n */\nexport const MAX_COUNTER = Number.MAX_SAFE_INTEGER;\n\n/**\n * Maximum verification window size\n *\n * Limits the number of HMAC computations during verification to prevent DoS attacks.\n * A window of 99 means up to 99 HMAC computations (total checks including current counter).\n * Odd number to cater for equal distribution of time drift + current.\n *\n * For TOTP: window=1 is typically sufficient (allows +-30 seconds clock drift)\n * For HOTP: window=10-50 handles reasonable counter desynchronization\n */\nexport const MAX_WINDOW = 99;\n\n/**\n * Configurable guardrails for OTP validation\n *\n * Allows overriding default safety limits for non-standard production requirements.\n * Use with caution - custom guardrails can weaken security.\n */\nexport type OTPGuardrailsConfig = {\n MIN_SECRET_BYTES: number;\n MAX_SECRET_BYTES: number;\n MIN_PERIOD: number;\n MAX_PERIOD: number;\n MAX_COUNTER: number;\n MAX_WINDOW: number;\n};\n\n/**\n * Module-private symbol to track guardrail override status\n *\n * This symbol is used as a property key to store whether guardrails contain custom values.\n * Being module-private and a symbol ensures:\n * - Cannot be accessed outside this module (not exported)\n * - Cannot be recreated (each Symbol() call is unique)\n * - Hidden from normal enumeration (Object.keys, JSON.stringify, for-in)\n * - Minimal memory overhead (~1 byte per object)\n * - No garbage collection concerns\n *\n * @internal\n */\nconst OVERRIDE_SYMBOL = Symbol(\"otplib.guardrails.override\");\n\n/**\n * Complete guardrails configuration\n *\n * This represents the final, immutable configuration used by validation functions.\n * Internally tracks whether any values were overridden from RFC recommendations,\n * enabling security auditing and compliance monitoring without exposing implementation\n * details in the public API.\n *\n * The override status is stored using a module-private Symbol that cannot be accessed\n * or recreated outside this module, providing true encapsulation.\n *\n * @see {@link OTPGuardrailsConfig} for the base configuration structure\n * @see {@link createGuardrails} for creating guardrails instances\n * @see {@link hasGuardrailOverrides} to check if guardrails were customized\n */\nexport type OTPGuardrails = Readonly<OTPGuardrailsConfig> & {\n [OVERRIDE_SYMBOL]?: boolean;\n};\n\n/**\n * Default guardrails matching RFC recommendations\n *\n * Frozen to ensure immutability. Used as default parameter for validation functions.\n * For custom guardrails, use the createGuardrails() factory function.\n */\nconst DEFAULT_GUARDRAILS: OTPGuardrails = Object.freeze({\n MIN_SECRET_BYTES,\n MAX_SECRET_BYTES,\n MIN_PERIOD,\n MAX_PERIOD,\n MAX_COUNTER,\n MAX_WINDOW,\n [OVERRIDE_SYMBOL]: false,\n});\n\n/**\n * Create guardrails configuration object\n *\n * Factory function that merges custom guardrails with defaults and returns\n * an immutable (frozen) object. Validates custom guardrails to ensure they\n * maintain basic safety invariants.\n *\n * When called without arguments or with `undefined`, returns the default guardrails\n * singleton (optimized to avoid unnecessary allocations). When called with custom\n * values, creates a new frozen object and internally marks it as overridden.\n *\n * @param custom - Optional partial guardrails to override defaults\n * @returns Frozen guardrails object\n * @throws {Error} If custom guardrails violate safety invariants\n *\n * @example Basic usage\n * ```ts\n * import { createGuardrails, hasGuardrailOverrides } from '@otplib/core'\n *\n * // Returns default singleton (no overrides)\n * const defaults = createGuardrails();\n * hasGuardrailOverrides(defaults); // false\n *\n * // Creates new object with overrides\n * const custom = createGuardrails({\n * MIN_SECRET_BYTES: 8,\n * MAX_WINDOW: 200\n * });\n * hasGuardrailOverrides(custom); // true\n * ```\n *\n * @example Monitoring custom guardrails\n * ```ts\n * import { createGuardrails, hasGuardrailOverrides } from '@otplib/core';\n *\n * const guardrails = createGuardrails({ MAX_WINDOW: 20 });\n *\n * if (hasGuardrailOverrides(guardrails)) {\n * logger.warn('Non-default guardrails in use', { guardrails });\n * }\n * ```\n *\n * @see {@link hasGuardrailOverrides} to check if guardrails were customized\n */\nexport function createGuardrails(custom?: Partial<OTPGuardrailsConfig>): OTPGuardrails {\n if (!custom) {\n return DEFAULT_GUARDRAILS;\n }\n\n return Object.freeze({\n ...DEFAULT_GUARDRAILS,\n ...custom,\n [OVERRIDE_SYMBOL]: true,\n });\n}\n\n/**\n * Check if guardrails contain custom overrides\n *\n * Returns `true` if the guardrails object was created with custom values,\n * `false` if using RFC-recommended defaults. Useful for security auditing,\n * compliance monitoring, and development warnings.\n *\n * This function accesses a module-private Symbol property that cannot be\n * accessed or modified outside this module, ensuring reliable detection.\n *\n * @param guardrails - The guardrails object to check\n * @returns `true` if guardrails were customized, `false` if using defaults\n *\n * @example Security monitoring\n * ```ts\n * import { createGuardrails, hasGuardrailOverrides } from '@otplib/core';\n *\n * const guardrails = createGuardrails({ MAX_WINDOW: 20 });\n *\n * if (hasGuardrailOverrides(guardrails)) {\n * console.warn('Custom guardrails detected:', guardrails);\n * // Log to security audit system\n * }\n * ```\n *\n * @example Compliance check\n * ```ts\n * function validateGuardrails(guardrails: OTPGuardrails) {\n * if (hasGuardrailOverrides(guardrails)) {\n * throw new Error('Custom guardrails not allowed in production');\n * }\n * }\n * ```\n */\nexport function hasGuardrailOverrides(guardrails: OTPGuardrails): boolean {\n return guardrails[OVERRIDE_SYMBOL] ?? false;\n}\n\n/**\n * Validate secret key\n *\n * @param secret - The secret to validate\n * @param guardrails - Validation guardrails (defaults to RFC recommendations)\n * @throws {SecretTooShortError} If secret is too short\n * @throws {SecretTooLongError} If secret is too long\n */\nexport function validateSecret(\n secret: Uint8Array,\n guardrails: OTPGuardrails = DEFAULT_GUARDRAILS,\n): void {\n if (secret.length < guardrails.MIN_SECRET_BYTES) {\n throw new SecretTooShortError(guardrails.MIN_SECRET_BYTES, secret.length);\n }\n\n if (secret.length > guardrails.MAX_SECRET_BYTES) {\n throw new SecretTooLongError(guardrails.MAX_SECRET_BYTES, secret.length);\n }\n}\n\n/**\n * Validate counter value\n *\n * @param counter - The counter to validate\n * @param guardrails - Validation guardrails (defaults to RFC recommendations)\n * @throws {CounterNegativeError} If counter is negative\n * @throws {CounterOverflowError} If counter exceeds safe integer\n */\nexport function validateCounter(\n counter: number | bigint,\n guardrails: OTPGuardrails = DEFAULT_GUARDRAILS,\n): void {\n const value = typeof counter === \"bigint\" ? counter : BigInt(counter);\n\n if (value < 0n) {\n throw new CounterNegativeError();\n }\n\n if (value > BigInt(guardrails.MAX_COUNTER)) {\n throw new CounterOverflowError();\n }\n}\n\n/**\n * Validate time value\n *\n * @param time - The time in seconds to validate\n * @throws {TimeNegativeError} If time is negative\n */\nexport function validateTime(time: number): void {\n if (time < 0) {\n throw new TimeNegativeError();\n }\n}\n\n/**\n * Validate period value\n *\n * @param period - The period in seconds to validate\n * @param guardrails - Validation guardrails (defaults to RFC recommendations)\n * @throws {PeriodTooSmallError} If period is too small\n * @throws {PeriodTooLargeError} If period is too large\n */\nexport function validatePeriod(\n period: number,\n guardrails: OTPGuardrails = DEFAULT_GUARDRAILS,\n): void {\n if (!Number.isInteger(period) || period < guardrails.MIN_PERIOD) {\n throw new PeriodTooSmallError(guardrails.MIN_PERIOD);\n }\n\n if (period > guardrails.MAX_PERIOD) {\n throw new PeriodTooLargeError(guardrails.MAX_PERIOD);\n }\n}\n\n/**\n * Validate token\n *\n * @param token - The token string to validate\n * @param digits - Expected number of digits\n * @throws {TokenLengthError} If token has incorrect length\n * @throws {TokenFormatError} If token contains non-digit characters\n */\nexport function validateToken(token: string, digits: number): void {\n if (token.length !== digits) {\n throw new TokenLengthError(digits, token.length);\n }\n\n if (!/^\\d+$/.test(token)) {\n throw new TokenFormatError();\n }\n}\n\n/**\n * Validate counter tolerance for HOTP verification\n *\n * Prevents DoS attacks by limiting the number of counter values checked.\n *\n * @param counterTolerance - Counter tolerance specification (number or array of offsets)\n * @param guardrails - Validation guardrails (defaults to RFC recommendations)\n * @throws {CounterToleranceTooLargeError} If tolerance size exceeds MAX_WINDOW\n *\n * @example\n * ```ts\n * validateCounterTolerance(1); // OK: 3 offsets [-1, 0, 1]\n * validateCounterTolerance(100); // OK: 201 offsets [-100, ..., 100]\n * validateCounterTolerance(101); // Throws: exceeds MAX_WINDOW\n * validateCounterTolerance([0, 1]); // OK: 2 offsets\n * ```\n */\nexport function validateCounterTolerance(\n counterTolerance: number | [number, number],\n guardrails: OTPGuardrails = DEFAULT_GUARDRAILS,\n): void {\n const [past, future] = normalizeCounterTolerance(counterTolerance);\n\n if (past < 0 || future < 0) {\n throw new CounterToleranceNegativeError();\n }\n\n const totalChecks = past + future + 1;\n\n if (totalChecks > guardrails.MAX_WINDOW) {\n throw new CounterToleranceTooLargeError(guardrails.MAX_WINDOW, totalChecks);\n }\n}\n\n/**\n * Validate epoch tolerance for TOTP verification\n *\n * Prevents DoS attacks by limiting the time range checked.\n * Also validates that tolerance values are non-negative.\n *\n * @param epochTolerance - Epoch tolerance specification (number or tuple [past, future])\n * @param period - The TOTP period in seconds (default: 30). Used to calculate max tolerance.\n * @param guardrails - Validation guardrails (defaults to RFC recommendations)\n * @throws {EpochToleranceNegativeError} If tolerance contains negative values\n * @throws {EpochToleranceTooLargeError} If tolerance exceeds MAX_WINDOW periods\n *\n * @example\n * ```ts\n * validateEpochTolerance(30); // OK: 30 seconds (default period 30s)\n * validateEpochTolerance([5, 0]); // OK: 5 seconds past only\n * validateEpochTolerance([-5, 0]); // Throws: negative values not allowed\n * validateEpochTolerance(3600); // Throws: exceeds MAX_WINDOW * period\n * validateEpochTolerance(6000, 60); // OK with 60s period (MAX_WINDOW * 60 = 6000)\n * ```\n */\nexport function validateEpochTolerance(\n epochTolerance: number | [number, number],\n period: number = DEFAULT_PERIOD,\n guardrails: OTPGuardrails = DEFAULT_GUARDRAILS,\n): void {\n const [pastTolerance, futureTolerance] = Array.isArray(epochTolerance)\n ? epochTolerance\n : [epochTolerance, epochTolerance];\n\n // Check for negative values\n if (pastTolerance < 0 || futureTolerance < 0) {\n throw new EpochToleranceNegativeError();\n }\n\n // Check total tolerance doesn't exceed reasonable limits\n // Convert to periods and check against MAX_WINDOW\n const maxToleranceSeconds = guardrails.MAX_WINDOW * period;\n const maxAllowed = Math.max(pastTolerance, futureTolerance);\n\n if (maxAllowed > maxToleranceSeconds) {\n throw new EpochToleranceTooLargeError(maxToleranceSeconds, maxAllowed);\n }\n}\n\n/**\n * Convert counter to 8-byte big-endian array\n *\n * Per RFC 4226 Section 5.1, the counter value is represented as an 8-byte\n * big-endian (network byte order) unsigned integer.\n *\n * @see {@link https://tools.ietf.org/html/rfc4226#section-5.1 | RFC 4226 Section 5.1 - Symbol Descriptions}\n *\n * @param value - The counter value to convert\n * @returns 8-byte big-endian array\n */\nexport function counterToBytes(value: number | bigint): Uint8Array {\n const bigintValue = typeof value === \"bigint\" ? value : BigInt(value);\n const buffer = new ArrayBuffer(8);\n const view = new DataView(buffer);\n\n view.setBigUint64(0, bigintValue, false);\n\n return new Uint8Array(buffer);\n}\n\n/**\n * Perform Dynamic Truncation as per RFC 4226 Section 5.3\n *\n * The algorithm:\n * 1. Take the low-order 4 bits of the last byte as offset\n * 2. Extract 4 bytes starting at offset\n * 3. Mask the most significant bit to get a 31-bit unsigned integer\n *\n * This ensures consistent extraction across different HMAC output sizes\n * while producing a value that fits in a signed 32-bit integer.\n *\n * @see {@link https://tools.ietf.org/html/rfc4226#section-5.3 | RFC 4226 Section 5.3 - Generating an HOTP Value}\n *\n * @param hmacResult - HMAC result (at least 20 bytes for SHA-1)\n * @returns Truncated 31-bit unsigned integer\n */\nexport function dynamicTruncate(hmacResult: Uint8Array): number {\n const offset = hmacResult[hmacResult.length - 1] & 0x0f;\n\n const binary =\n ((hmacResult[offset] & 0x7f) << 24) |\n (hmacResult[offset + 1] << 16) |\n (hmacResult[offset + 2] << 8) |\n hmacResult[offset + 3];\n\n return binary;\n}\n\n/**\n * Convert truncated integer to OTP string with specified digits\n *\n * Computes: Snum mod 10^Digit (RFC 4226 Section 5.3)\n *\n * The result is zero-padded to ensure consistent length,\n * as required for proper token comparison.\n *\n * @see {@link https://tools.ietf.org/html/rfc4226#section-5.3 | RFC 4226 Section 5.3 - Generating an HOTP Value}\n *\n * @param value - The truncated integer value (Snum)\n * @param digits - Number of digits for the OTP (Digit, typically 6-8)\n * @returns OTP string with leading zeros if necessary\n */\nexport function truncateDigits(value: number, digits: number): string {\n const maxOtp = 10 ** digits;\n const otp = value % maxOtp;\n return otp.toString().padStart(digits, \"0\");\n}\n\n/**\n * Validate that two byte arrays have equal length\n *\n * Useful as a preliminary check before performing byte-by-byte comparisons.\n *\n * @param a - First byte array\n * @param b - Second byte array\n * @returns true if arrays have equal length, false otherwise\n */\nexport function validateByteLengthEqual(a: Uint8Array, b: Uint8Array): boolean {\n return a.length === b.length;\n}\n\n/**\n * Constant-time comparison to prevent timing attacks\n *\n * This implements a timing-safe equality check as recommended in\n * RFC 4226 Section 7.2 for token validation to prevent\n * timing side-channel attacks.\n *\n * @see {@link https://tools.ietf.org/html/rfc4226#section-7.2 | RFC 4226 Section 7.2 - Validation and Verification}\n *\n * @param a - First value to compare\n * @param b - Second value to compare\n * @returns true if values are equal, false otherwise\n */\nexport function constantTimeEqual(a: string | Uint8Array, b: string | Uint8Array): boolean {\n const bufA = stringToBytes(a);\n const bufB = stringToBytes(b);\n\n if (!validateByteLengthEqual(bufA, bufB)) {\n return false;\n }\n\n let result = 0;\n for (let i = 0; i < bufA.length; i++) {\n result |= bufA[i] ^ bufB[i];\n }\n\n return result === 0;\n}\n\n/**\n * Get HMAC digest size in bytes for a given algorithm\n *\n * @param algorithm - The hash algorithm\n * @returns Digest size in bytes\n */\nexport function getDigestSize(algorithm: HashAlgorithm): number {\n switch (algorithm) {\n case \"sha1\":\n return 20;\n case \"sha256\":\n return 32;\n case \"sha512\":\n return 64;\n }\n}\n\n/**\n * Convert a string or Uint8Array to Uint8Array\n *\n * This utility function normalizes input to Uint8Array, converting strings\n * using UTF-8 encoding. Uint8Array inputs are returned as-is.\n *\n * Use this to convert raw secret strings (passphrases) to Uint8Array\n * before passing them to generation or verification functions.\n *\n * @param value - The value to convert (string or Uint8Array)\n * @returns The value as a Uint8Array (UTF-8 encoded for strings)\n *\n * @example\n * ```ts\n * import { stringToBytes } from '@otplib/core'\n *\n * const bytes1 = stringToBytes('1234567890123456')\n * // Returns: Uint8Array([49, 50, 51, 52, 53, 54, 55, 56, 57, 48, 49, 50, 51, 52, 53, 54])\n *\n * const bytes2 = stringToBytes(new Uint8Array([1, 2, 3]))\n * // Returns: Uint8Array([1, 2, 3]) - returned as-is\n * ```\n */\nexport function stringToBytes(value: string | Uint8Array): Uint8Array {\n return typeof value === \"string\" ? textEncoder.encode(value) : value;\n}\n\n/**\n * Convert bytes to UTF-8 string\n *\n * Uses TextDecoder for proper UTF-8 handling.\n *\n * @param bytes - Uint8Array to convert\n * @returns UTF-8 string\n *\n * @example\n * ```ts\n * const str = bytesToString(new Uint8Array([104, 101, 108, 108, 111]));\n * // str === \"hello\"\n * ```\n */\nexport function bytesToString(bytes: Uint8Array): string {\n return textDecoder.decode(bytes);\n}\n\n/**\n * Normalize secret input to Uint8Array\n *\n * Accepts either a Base32-encoded string or Uint8Array and returns Uint8Array.\n * If a Base32Plugin is provided, string secrets will be automatically decoded.\n *\n * **Note**: By default, strings are assumed to be Base32 encoded.\n * If you have a raw string secret (e.g. a passphrase), you must convert it\n * to a Uint8Array using {@link stringToBytes} before calling this function.\n *\n * @param secret - The secret to normalize (Base32 string or Uint8Array)\n * @param base32 - Optional Base32Plugin to decode string secrets\n * @returns The secret as Uint8Array\n * @throws {Error} If secret is a string but no Base32Plugin is provided\n *\n * @example\n * ```ts\n * import { normalizeSecret } from '@otplib/core'\n * import { ScureBase32Plugin } from '@otplib/plugin-base32-scure'\n *\n * const base32 = new ScureBase32Plugin()\n *\n * // Uint8Array - returned as-is\n * const secret1 = normalizeSecret(new Uint8Array([1, 2, 3]))\n *\n * // Base32 string - automatically decoded\n * const secret2 = normalizeSecret('JBSWY3DPEHPK3PXP', base32)\n * ```\n */\nexport function normalizeSecret(\n secret: string | Uint8Array,\n base32?: { decode: (str: string) => Uint8Array },\n): Uint8Array {\n if (typeof secret === \"string\") {\n requireBase32Plugin(base32);\n return base32.decode(secret);\n }\n return secret;\n}\n\n/**\n * Generate a random Base32-encoded secret\n *\n * Creates a cryptographically secure random secret suitable for OTP generation.\n * The default length of 20 bytes (160 bits) matches RFC 4226 recommendations\n * and provides good security margin.\n *\n * @param options - Secret generation options\n * @returns Base32-encoded secret string (without padding for Google Authenticator compatibility)\n *\n * @example\n * ```ts\n * import { generateSecret } from '@otplib/core';\n * import { NodeCryptoPlugin } from '@otplib/plugin-crypto-node';\n * import { ScureBase32Plugin } from '@otplib/plugin-base32-scure';\n *\n * const secret = generateSecret({\n * crypto: new NodeCryptoPlugin(),\n * base32: new ScureBase32Plugin(),\n * });\n * // Returns: 'JBSWY3DPEHPK3PXP...' (32 characters)\n * ```\n *\n * @example Custom length\n * ```ts\n * const secret = generateSecret({\n * crypto: new NodeCryptoPlugin(),\n * base32: new ScureBase32Plugin(),\n * length: 32, // 256 bits for SHA-256\n * });\n * ```\n */\nexport function generateSecret(options: SecretOptions): string {\n const { crypto, base32, length = RECOMMENDED_SECRET_BYTES } = options;\n\n requireCryptoPlugin(crypto);\n requireBase32Plugin(base32);\n\n const randomBytes = crypto.randomBytes(length);\n return base32.encode(randomBytes, { padding: false });\n}\n\n/**\n * Normalize counter tolerance to [past, future] tuple\n *\n * Converts a number or tuple counter tolerance specification into a [past, future] tuple\n * - Number: creates look-ahead only tolerance [0, tolerance] (default for security)\n * - Tuple: uses the tuple as-is (past, future)\n *\n * The default behavior (number → look-ahead only) improves security by preventing\n * replay attacks. HOTP counters should only move forward in normal operation.\n *\n * @param counterTolerance - Counter tolerance specification (number or tuple [past, future])\n * @returns Tuple [past, future] representing counters to check\n *\n * @example\n * ```ts\n * normalizeCounterTolerance(0) // [0, 0]\n * normalizeCounterTolerance(5) // [0, 5] - look-ahead only (secure default)\n * normalizeCounterTolerance([10, 5]) // [10, 5] - explicit past/future\n * normalizeCounterTolerance([5, 5]) // [5, 5] - explicit symmetric (use with caution)\n * ```\n */\nexport function normalizeCounterTolerance(\n counterTolerance: number | [number, number] = 0,\n): [number, number] {\n return Array.isArray(counterTolerance) ? counterTolerance : [0, counterTolerance];\n}\n\n/**\n * Normalize epoch tolerance to [past, future] tuple\n *\n * Converts a number or tuple epoch tolerance specification into a [past, future] tuple\n * - Number: creates symmetric tolerance [tolerance, tolerance]\n * - Tuple: uses the tuple as-is\n *\n * @param epochTolerance - Epoch tolerance specification (number or tuple [past, future])\n * @returns Tuple [pastTolerance, futureTolerance] in seconds\n *\n * @example\n * ```ts\n * normalizeEpochTolerance(0) // [0, 0]\n * normalizeEpochTolerance(30) // [30, 30]\n * normalizeEpochTolerance([5, 0]) // [5, 0]\n * normalizeEpochTolerance([10, 5]) // [10, 5]\n * ```\n */\nexport function normalizeEpochTolerance(\n epochTolerance: number | [number, number] = 0,\n): [number, number] {\n return Array.isArray(epochTolerance) ? epochTolerance : [epochTolerance, epochTolerance];\n}\n\n/**\n * Require crypto plugin to be configured\n *\n * @param crypto - The crypto plugin\n * @throws {CryptoPluginMissingError} If crypto plugin is not set\n */\nexport function requireCryptoPlugin<T>(crypto: T | undefined): asserts crypto is T {\n if (!crypto) {\n throw new CryptoPluginMissingError();\n }\n}\n\n/**\n * Require Base32 plugin to be configured\n *\n * @param base32 - The Base32 plugin\n * @throws {Base32PluginMissingError} If Base32 plugin is not set\n */\nexport function requireBase32Plugin<T>(base32: T | undefined): asserts base32 is T {\n if (!base32) {\n throw new Base32PluginMissingError();\n }\n}\n\n/**\n * Require secret to be configured\n *\n * @param secret - The secret value\n * @throws {SecretMissingError} If secret is not set\n */\nexport function requireSecret<T>(secret: T | undefined): asserts secret is T {\n if (!secret) {\n throw new SecretMissingError();\n }\n}\n\n/**\n * Require label to be configured (for URI generation)\n *\n * @param label - The label value\n * @throws {LabelMissingError} If label is not set\n */\nexport function requireLabel(label: string | undefined): asserts label is string {\n if (!label) {\n throw new LabelMissingError();\n }\n}\n\n/**\n * Require issuer to be configured (for URI generation)\n *\n * @param issuer - The issuer value\n * @throws {IssuerMissingError} If issuer is not set\n */\nexport function requireIssuer(issuer: string | undefined): asserts issuer is string {\n if (!issuer) {\n throw new IssuerMissingError();\n }\n}\n\n/**\n * Require secret to be a Base32 string (for URI generation)\n *\n * @param secret - The secret value\n * @throws {SecretTypeError} If secret is not a string\n */\nexport function requireBase32String(secret: string | Uint8Array): asserts secret is string {\n if (typeof secret !== \"string\") {\n throw new SecretTypeError();\n }\n}\n\n/**\n * Create a success result\n * @internal\n */\nfunction ok<T>(value: T): OTPResultOk<T> {\n return { ok: true, value };\n}\n\n/**\n * Create a failure result\n * @internal\n */\nfunction err<E>(error: E): OTPResultError<E> {\n return { ok: false, error };\n}\n\n/**\n * Wrap a synchronous function to return OTPResult instead of throwing\n *\n * Preserves the original OTPError subclass so users can access\n * specific error information via instanceof checks.\n *\n * @internal\n */\nexport function wrapResult<T, Args extends unknown[]>(\n fn: (...args: Args) => T,\n): (...args: Args) => OTPResult<T, OTPError> {\n return (...args: Args): OTPResult<T, OTPError> => {\n try {\n return ok(fn(...args));\n } catch (error) {\n return err(error as OTPError);\n }\n };\n}\n\n/**\n * Wrap an async function to return OTPResult instead of throwing\n *\n * Preserves the original OTPError subclass so users can access\n * specific error information via instanceof checks.\n *\n * @internal\n */\nexport function wrapResultAsync<T, Args extends unknown[]>(\n fn: (...args: Args) => Promise<T>,\n): (...args: Args) => Promise<OTPResult<T, OTPError>> {\n return async (...args: Args): Promise<OTPResult<T, OTPError>> => {\n try {\n return ok(await fn(...args));\n } catch (error) {\n return err(error as OTPError);\n }\n };\n}\n","/**\n * Options for OTPError construction\n */\nexport type OTPErrorOptions = {\n /**\n * The underlying error that caused this error.\n * Useful for error chaining and debugging.\n */\n cause?: unknown;\n};\n\n/**\n * Base error class for all otplib errors\n *\n * Supports ES2022 error chaining via the `cause` property.\n *\n * @example\n * ```typescript\n * try {\n * // ... operation that throws\n * } catch (error) {\n * throw new OTPError('Operation failed', { cause: error });\n * }\n * ```\n */\nexport class OTPError extends Error {\n constructor(message: string, options?: OTPErrorOptions) {\n super(message, options);\n this.name = \"OTPError\";\n }\n}\n\n/**\n * Error thrown when secret validation fails\n */\nexport class SecretError extends OTPError {\n constructor(message: string) {\n super(message);\n this.name = \"SecretError\";\n }\n}\n\n/**\n * Error thrown when secret is too short (< 128 bits)\n */\nexport class SecretTooShortError extends SecretError {\n constructor(minBytes: number, actualBytes: number) {\n super(\n `Secret must be at least ${minBytes} bytes (${minBytes * 8} bits), got ${actualBytes} bytes`,\n );\n this.name = \"SecretTooShortError\";\n }\n}\n\n/**\n * Error thrown when secret is unreasonably large (> 64 bytes)\n */\nexport class SecretTooLongError extends SecretError {\n constructor(maxBytes: number, actualBytes: number) {\n super(`Secret must not exceed ${maxBytes} bytes, got ${actualBytes} bytes`);\n this.name = \"SecretTooLongError\";\n }\n}\n\n/**\n * Error thrown when counter is invalid\n */\nexport class CounterError extends OTPError {\n constructor(message: string) {\n super(message);\n this.name = \"CounterError\";\n }\n}\n\n/**\n * Error thrown when counter is negative\n */\nexport class CounterNegativeError extends CounterError {\n constructor() {\n super(\"Counter must be non-negative\");\n this.name = \"CounterNegativeError\";\n }\n}\n\n/**\n * Error thrown when counter exceeds maximum value (2^53 - 1 for safe integer)\n */\nexport class CounterOverflowError extends CounterError {\n constructor() {\n super(\"Counter exceeds maximum safe integer value\");\n this.name = \"CounterOverflowError\";\n }\n}\n\n/**\n * Error thrown when time is invalid\n */\nexport class TimeError extends OTPError {\n constructor(message: string) {\n super(message);\n this.name = \"TimeError\";\n }\n}\n\n/**\n * Error thrown when time is negative\n */\nexport class TimeNegativeError extends TimeError {\n constructor() {\n super(\"Time must be non-negative\");\n this.name = \"TimeNegativeError\";\n }\n}\n\n/**\n * Error thrown when period is invalid\n */\nexport class PeriodError extends OTPError {\n constructor(message: string) {\n super(message);\n this.name = \"PeriodError\";\n }\n}\n\n/**\n * Error thrown when period is too small\n */\nexport class PeriodTooSmallError extends PeriodError {\n constructor(minPeriod: number) {\n super(`Period must be at least ${minPeriod} second(s)`);\n this.name = \"PeriodTooSmallError\";\n }\n}\n\n/**\n * Error thrown when period is too large\n */\nexport class PeriodTooLargeError extends PeriodError {\n constructor(maxPeriod: number) {\n super(`Period must not exceed ${maxPeriod} seconds`);\n this.name = \"PeriodTooLargeError\";\n }\n}\n\n/**\n * Error thrown when digits value is invalid\n */\nexport class DigitsError extends OTPError {\n constructor(message: string) {\n super(message);\n this.name = \"DigitsError\";\n }\n}\n\n/**\n * Error thrown when hash algorithm is invalid\n */\nexport class AlgorithmError extends OTPError {\n constructor(message: string) {\n super(message);\n this.name = \"AlgorithmError\";\n }\n}\n\n/**\n * Error thrown when token is invalid\n */\nexport class TokenError extends OTPError {\n constructor(message: string) {\n super(message);\n this.name = \"TokenError\";\n }\n}\n\n/**\n * Error thrown when token has incorrect length\n */\nexport class TokenLengthError extends TokenError {\n constructor(expected: number, actual: number) {\n super(`Token must be ${expected} digits, got ${actual}`);\n this.name = \"TokenLengthError\";\n }\n}\n\n/**\n * Error thrown when token contains non-digit characters\n */\nexport class TokenFormatError extends TokenError {\n constructor() {\n super(\"Token must contain only digits\");\n this.name = \"TokenFormatError\";\n }\n}\n\n/**\n * Error thrown when crypto operation fails\n */\nexport class CryptoError extends OTPError {\n constructor(message: string, options?: OTPErrorOptions) {\n super(message, options);\n this.name = \"CryptoError\";\n }\n}\n\n/**\n * Error thrown when HMAC computation fails\n *\n * The original error from the crypto plugin is available via `cause`.\n *\n * @example\n * ```typescript\n * try {\n * await cryptoContext.hmac('sha1', key, data);\n * } catch (error) {\n * if (error instanceof HMACError) {\n * console.log('HMAC failed:', error.message);\n * console.log('Original error:', error.cause);\n * }\n * }\n * ```\n */\nexport class HMACError extends CryptoError {\n constructor(message: string, options?: OTPErrorOptions) {\n super(`HMAC computation failed: ${message}`, options);\n this.name = \"HMACError\";\n }\n}\n\n/**\n * Error thrown when random byte generation fails\n *\n * The original error from the crypto plugin is available via `cause`.\n */\nexport class RandomBytesError extends CryptoError {\n constructor(message: string, options?: OTPErrorOptions) {\n super(`Random byte generation failed: ${message}`, options);\n this.name = \"RandomBytesError\";\n }\n}\n\n/**\n * Error thrown when Base32 operation fails\n */\nexport class Base32Error extends OTPError {\n constructor(message: string, options?: OTPErrorOptions) {\n super(message, options);\n this.name = \"Base32Error\";\n }\n}\n\n/**\n * Error thrown when Base32 encoding fails\n *\n * The original error from the Base32 plugin is available via `cause`.\n *\n * @example\n * ```typescript\n * try {\n * base32Context.encode(data);\n * } catch (error) {\n * if (error instanceof Base32EncodeError) {\n * console.log('Encoding failed:', error.message);\n * console.log('Original error:', error.cause);\n * }\n * }\n * ```\n */\nexport class Base32EncodeError extends Base32Error {\n constructor(message: string, options?: OTPErrorOptions) {\n super(`Base32 encoding failed: ${message}`, options);\n this.name = \"Base32EncodeError\";\n }\n}\n\n/**\n * Error thrown when Base32 decoding fails\n *\n * The original error from the Base32 plugin is available via `cause`.\n *\n * @example\n * ```typescript\n * try {\n * base32Context.decode(invalidString);\n * } catch (error) {\n * if (error instanceof Base32DecodeError) {\n * console.log('Decoding failed:', error.message);\n * console.log('Original error:', error.cause);\n * }\n * }\n * ```\n */\nexport class Base32DecodeError extends Base32Error {\n constructor(message: string, options?: OTPErrorOptions) {\n super(`Base32 decoding failed: ${message}`, options);\n this.name = \"Base32DecodeError\";\n }\n}\n\n/**\n * Error thrown when counter tolerance is invalid\n */\nexport class CounterToleranceError extends OTPError {\n constructor(message: string) {\n super(message);\n this.name = \"CounterToleranceError\";\n }\n}\n\n/**\n * Error thrown when counter tolerance is too large\n */\nexport class CounterToleranceTooLargeError extends CounterToleranceError {\n constructor(maxWindow: number, totalChecks: number) {\n super(\n `Counter tolerance validation failed: total checks (${totalChecks}) exceeds MAX_WINDOW (${maxWindow})`,\n );\n this.name = \"CounterToleranceTooLargeError\";\n }\n}\n\n/**\n * Error thrown when counter tolerance contains negative values\n */\nexport class CounterToleranceNegativeError extends CounterToleranceError {\n constructor() {\n super(\"Counter tolerance cannot contain negative values\");\n this.name = \"CounterToleranceNegativeError\";\n }\n}\n\n/**\n * Error thrown when epoch tolerance is invalid\n */\nexport class EpochToleranceError extends OTPError {\n constructor(message: string) {\n super(message);\n this.name = \"EpochToleranceError\";\n }\n}\n\n/**\n * Error thrown when epoch tolerance contains negative values\n */\nexport class EpochToleranceNegativeError extends EpochToleranceError {\n constructor() {\n super(\"Epoch tolerance cannot contain negative values\");\n this.name = \"EpochToleranceNegativeError\";\n }\n}\n\n/**\n * Error thrown when epoch tolerance is too large\n */\nexport class EpochToleranceTooLargeError extends EpochToleranceError {\n constructor(maxTolerance: number, actualValue: number) {\n super(\n `Epoch tolerance must not exceed ${maxTolerance} seconds, got ${actualValue}. ` +\n `Large tolerances can cause performance issues.`,\n );\n this.name = \"EpochToleranceTooLargeError\";\n }\n}\n\n/**\n * Error thrown when a required plugin is missing\n */\nexport class PluginError extends OTPError {\n constructor(message: string) {\n super(message);\n this.name = \"PluginError\";\n }\n}\n\n/**\n * Error thrown when crypto plugin is not configured\n */\nexport class CryptoPluginMissingError extends PluginError {\n constructor() {\n super(\"Crypto plugin is required.\");\n this.name = \"CryptoPluginMissingError\";\n }\n}\n\n/**\n * Error thrown when Base32 plugin is not configured\n */\nexport class Base32PluginMissingError extends PluginError {\n constructor() {\n super(\"Base32 plugin is required.\");\n this.name = \"Base32PluginMissingError\";\n }\n}\n\n/**\n * Error thrown when required configuration is missing\n */\nexport class ConfigurationError extends OTPError {\n constructor(message: string) {\n super(message);\n this.name = \"ConfigurationError\";\n }\n}\n\n/**\n * Error thrown when secret is not configured\n */\nexport class SecretMissingError extends ConfigurationError {\n constructor() {\n super(\n \"Secret is required. \" +\n \"Use generateSecret() to create one, or provide via { secret: 'YOUR_BASE32_SECRET' }\",\n );\n this.name = \"SecretMissingError\";\n }\n}\n\n/**\n * Error thrown when label is not configured (required for URI generation)\n */\nexport class LabelMissingError extends ConfigurationError {\n constructor() {\n super(\"Label is required for URI generation. Example: { label: 'user@example.com' }\");\n this.name = \"LabelMissingError\";\n }\n}\n\n/**\n * Error thrown when issuer is not configured (required for URI generation)\n */\nexport class IssuerMissingError extends ConfigurationError {\n constructor() {\n super(\"Issuer is required for URI generation. Example: { issuer: 'MyApp' }\");\n this.name = \"IssuerMissingError\";\n }\n}\n\n/**\n * Error thrown when secret must be a Base32 string but is provided as bytes\n */\nexport class SecretTypeError extends ConfigurationError {\n constructor() {\n super(\n \"Class API requires secret to be a Base32 string, not Uint8Array. \" +\n \"Use generateSecret() or provide a Base32-encoded string.\",\n );\n this.name = \"SecretTypeError\";\n }\n}\n\n/**\n * Error thrown when afterTimeStep parameter is invalid\n */\nexport class AfterTimeStepError extends OTPError {\n constructor(message: string) {\n super(message);\n this.name = \"AfterTimeStepError\";\n }\n}\n\n/**\n * Error thrown when afterTimeStep is negative\n */\nexport class AfterTimeStepNegativeError extends AfterTimeStepError {\n constructor() {\n super(\"afterTimeStep must be >= 0\");\n this.name = \"AfterTimeStepNegativeError\";\n }\n}\n\n/**\n * Error thrown when afterTimeStep is not an integer\n */\nexport class AfterTimeStepNotIntegerError extends AfterTimeStepError {\n constructor() {\n super(\"Invalid afterTimeStep: non-integer value\");\n this.name = \"AfterTimeStepNotIntegerError\";\n }\n}\n\n/**\n * Error thrown when afterTimeStep exceeds the verification range\n */\nexport class AfterTimeStepRangeExceededError extends AfterTimeStepError {\n constructor() {\n super(\"Invalid afterTimeStep: cannot be greater than current time step plus window\");\n this.name = \"AfterTimeStepRangeExceededError\";\n }\n}\n"],"mappings":"mbAAA,IAAAA,GAAA,GAAAC,GAAAD,GAAA,oBAAAE,EAAA,gBAAAC,EAAA,eAAAC,EAAA,qBAAAC,EAAA,eAAAC,EAAA,eAAAC,EAAA,qBAAAC,EAAA,6BAAAC,EAAA,kBAAAC,GAAA,sBAAAC,GAAA,mBAAAC,GAAA,qBAAAC,GAAA,oBAAAC,GAAA,mBAAAC,GAAA,kBAAAC,GAAA,0BAAAC,GAAA,8BAAAC,EAAA,4BAAAC,GAAA,oBAAAC,GAAA,wBAAAC,EAAA,wBAAAC,GAAA,wBAAAC,EAAA,kBAAAC,GAAA,iBAAAC,GAAA,kBAAAC,GAAA,kBAAAC,EAAA,mBAAAC,GAAA,4BAAAC,EAAA,oBAAAC,GAAA,6BAAAC,GAAA,2BAAAC,GAAA,mBAAAC,GAAA,mBAAAC,GAAA,iBAAAC,GAAA,kBAAAC,GAAA,eAAAC,GAAA,oBAAAC,KAAA,eAAAC,GAAAvC,ICyBO,IAAMwC,EAAN,cAAuB,KAAM,CAClC,YAAYC,EAAiBC,EAA2B,CACtD,MAAMD,EAASC,CAAO,EACtB,KAAK,KAAO,UACd,CACF,EAKaC,EAAN,cAA0BH,CAAS,CACxC,YAAYC,EAAiB,CAC3B,MAAMA,CAAO,EACb,KAAK,KAAO,aACd,CACF,EAKaG,EAAN,cAAkCD,CAAY,CACnD,YAAYE,EAAkBC,EAAqB,CACjD,MACE,2BAA2BD,CAAQ,WAAWA,EAAW,CAAC,eAAeC,CAAW,QACtF,EACA,KAAK,KAAO,qBACd,CACF,EAKaC,EAAN,cAAiCJ,CAAY,CAClD,YAAYK,EAAkBF,EAAqB,CACjD,MAAM,0BAA0BE,CAAQ,eAAeF,CAAW,QAAQ,EAC1E,KAAK,KAAO,oBACd,CACF,EAKaG,EAAN,cAA2BT,CAAS,CACzC,YAAYC,EAAiB,CAC3B,MAAMA,CAAO,EACb,KAAK,KAAO,cACd,CACF,EAKaS,EAAN,cAAmCD,CAAa,CACrD,aAAc,CACZ,MAAM,8BAA8B,EACpC,KAAK,KAAO,sBACd,CACF,EAKaE,EAAN,cAAmCF,CAAa,CACrD,aAAc,CACZ,MAAM,4CAA4C,EAClD,KAAK,KAAO,sBACd,CACF,EAKaG,EAAN,cAAwBZ,CAAS,CACtC,YAAYC,EAAiB,CAC3B,MAAMA,CAAO,EACb,KAAK,KAAO,WACd,CACF,EAKaY,EAAN,cAAgCD,CAAU,CAC/C,aAAc,CACZ,MAAM,2BAA2B,EACjC,KAAK,KAAO,mBACd,CACF,EAKaE,EAAN,cAA0Bd,CAAS,CACxC,YAAYC,EAAiB,CAC3B,MAAMA,CAAO,EACb,KAAK,KAAO,aACd,CACF,EAKac,EAAN,cAAkCD,CAAY,CACnD,YAAYE,EAAmB,CAC7B,MAAM,2BAA2BA,CAAS,YAAY,EACtD,KAAK,KAAO,qBACd,CACF,EAKaC,EAAN,cAAkCH,CAAY,CACnD,YAAYI,EAAmB,CAC7B,MAAM,0BAA0BA,CAAS,UAAU,EACnD,KAAK,KAAO,qBACd,CACF,EAyBO,IAAMC,EAAN,cAAyBC,CAAS,CACvC,YAAYC,EAAiB,CAC3B,MAAMA,CAAO,EACb,KAAK,KAAO,YACd,CACF,EAKaC,EAAN,cAA+BH,CAAW,CAC/C,YAAYI,EAAkBC,EAAgB,CAC5C,MAAM,iBAAiBD,CAAQ,gBAAgBC,CAAM,EAAE,EACvD,KAAK,KAAO,kBACd,CACF,EAKaC,EAAN,cAA+BN,CAAW,CAC/C,aAAc,CACZ,MAAM,gCAAgC,EACtC,KAAK,KAAO,kBACd,CACF,EA6GO,IAAMO,EAAN,cAAoCC,CAAS,CAClD,YAAYC,EAAiB,CAC3B,MAAMA,CAAO,EACb,KAAK,KAAO,uBACd,CACF,EAKaC,EAAN,cAA4CH,CAAsB,CACvE,YAAYI,EAAmBC,EAAqB,CAClD,MACE,sDAAsDA,CAAW,yBAAyBD,CAAS,GACrG,EACA,KAAK,KAAO,+BACd,CACF,EAKaE,EAAN,cAA4CN,CAAsB,CACvE,aAAc,CACZ,MAAM,kDAAkD,EACxD,KAAK,KAAO,+BACd,CACF,EAKaO,EAAN,cAAkCN,CAAS,CAChD,YAAYC,EAAiB,CAC3B,MAAMA,CAAO,EACb,KAAK,KAAO,qBACd,CACF,EAKaM,EAAN,cAA0CD,CAAoB,CACnE,aAAc,CACZ,MAAM,gDAAgD,EACtD,KAAK,KAAO,6BACd,CACF,EAKaE,EAAN,cAA0CF,CAAoB,CACnE,YAAYG,EAAsBC,EAAqB,CACrD,MACE,mCAAmCD,CAAY,iBAAiBC,CAAW,kDAE7E,EACA,KAAK,KAAO,6BACd,CACF,EAKaC,EAAN,cAA0BX,CAAS,CACxC,YAAYC,EAAiB,CAC3B,MAAMA,CAAO,EACb,KAAK,KAAO,aACd,CACF,EAKaW,EAAN,cAAuCD,CAAY,CACxD,aAAc,CACZ,MAAM,4BAA4B,EAClC,KAAK,KAAO,0BACd,CACF,EAKaE,EAAN,cAAuCF,CAAY,CACxD,aAAc,CACZ,MAAM,4BAA4B,EAClC,KAAK,KAAO,0BACd,CACF,EAKaG,EAAN,cAAiCd,CAAS,CAC/C,YAAYC,EAAiB,CAC3B,MAAMA,CAAO,EACb,KAAK,KAAO,oBACd,CACF,EAKac,EAAN,cAAiCD,CAAmB,CACzD,aAAc,CACZ,MACE,yGAEF,EACA,KAAK,KAAO,oBACd,CACF,EAKaE,EAAN,cAAgCF,CAAmB,CACxD,aAAc,CACZ,MAAM,8EAA8E,EACpF,KAAK,KAAO,mBACd,CACF,EAKaG,EAAN,cAAiCH,CAAmB,CACzD,aAAc,CACZ,MAAM,qEAAqE,EAC3E,KAAK,KAAO,oBACd,CACF,EAKaI,EAAN,cAA8BJ,CAAmB,CACtD,aAAc,CACZ,MACE,2HAEF,EACA,KAAK,KAAO,iBACd,CACF,ED7ZA,IAAMK,GAAc,IAAI,YAKlBC,GAAc,IAAI,YAKXC,EAAmB,GAQnBC,EAAmB,GAKnBC,EAA2B,GAK3BC,EAAa,EAKbC,EAAa,KAKbC,EAAiB,GAKjBC,EAAc,OAAO,iBAYrBC,EAAa,GA8BpBC,EAAkB,OAAO,4BAA4B,EA2BrDC,EAAoC,OAAO,OAAO,CACtD,iBAAAT,EACA,iBAAAC,EACA,WAAAE,EACA,WAAAC,EACA,YAAAE,EACA,WAAAC,EACA,CAACC,CAAe,EAAG,EACrB,CAAC,EA8CM,SAASE,GAAiBC,EAAsD,CACrF,OAAKA,EAIE,OAAO,OAAO,CACnB,GAAGF,EACH,GAAGE,EACH,CAACH,CAAe,EAAG,EACrB,CAAC,EAPQC,CAQX,CAoCO,SAASG,GAAsBC,EAAoC,CACxE,OAAOA,EAAWL,CAAe,GAAK,EACxC,CAUO,SAASM,GACdC,EACAF,EAA4BJ,EACtB,CACN,GAAIM,EAAO,OAASF,EAAW,iBAC7B,MAAM,IAAIG,EAAoBH,EAAW,iBAAkBE,EAAO,MAAM,EAG1E,GAAIA,EAAO,OAASF,EAAW,iBAC7B,MAAM,IAAII,EAAmBJ,EAAW,iBAAkBE,EAAO,MAAM,CAE3E,CAUO,SAASG,GACdC,EACAN,EAA4BJ,EACtB,CACN,IAAMW,EAAQ,OAAOD,GAAY,SAAWA,EAAU,OAAOA,CAAO,EAEpE,GAAIC,EAAQ,GACV,MAAM,IAAIC,EAGZ,GAAID,EAAQ,OAAOP,EAAW,WAAW,EACvC,MAAM,IAAIS,CAEd,CAQO,SAASC,GAAaC,EAAoB,CAC/C,GAAIA,EAAO,EACT,MAAM,IAAIC,CAEd,CAUO,SAASC,GACdC,EACAd,EAA4BJ,EACtB,CACN,GAAI,CAAC,OAAO,UAAUkB,CAAM,GAAKA,EAASd,EAAW,WACnD,MAAM,IAAIe,EAAoBf,EAAW,UAAU,EAGrD,GAAIc,EAASd,EAAW,WACtB,MAAM,IAAIgB,EAAoBhB,EAAW,UAAU,CAEvD,CAUO,SAASiB,GAAcC,EAAeC,EAAsB,CACjE,GAAID,EAAM,SAAWC,EACnB,MAAM,IAAIC,EAAiBD,EAAQD,EAAM,MAAM,EAGjD,GAAI,CAAC,QAAQ,KAAKA,CAAK,EACrB,MAAM,IAAIG,CAEd,CAmBO,SAASC,GACdC,EACAvB,EAA4BJ,EACtB,CACN,GAAM,CAAC4B,EAAMC,CAAM,EAAIC,EAA0BH,CAAgB,EAEjE,GAAIC,EAAO,GAAKC,EAAS,EACvB,MAAM,IAAIE,EAGZ,IAAMC,EAAcJ,EAAOC,EAAS,EAEpC,GAAIG,EAAc5B,EAAW,WAC3B,MAAM,IAAI6B,EAA8B7B,EAAW,WAAY4B,CAAW,CAE9E,CAuBO,SAASE,GACdC,EACAjB,EAAiBtB,EACjBQ,EAA4BJ,EACtB,CACN,GAAM,CAACoC,EAAeC,CAAe,EAAI,MAAM,QAAQF,CAAc,EACjEA,EACA,CAACA,EAAgBA,CAAc,EAGnC,GAAIC,EAAgB,GAAKC,EAAkB,EACzC,MAAM,IAAIC,EAKZ,IAAMC,EAAsBnC,EAAW,WAAac,EAC9CsB,EAAa,KAAK,IAAIJ,EAAeC,CAAe,EAE1D,GAAIG,EAAaD,EACf,MAAM,IAAIE,EAA4BF,EAAqBC,CAAU,CAEzE,CAaO,SAASE,GAAe/B,EAAoC,CACjE,IAAMgC,EAAc,OAAOhC,GAAU,SAAWA,EAAQ,OAAOA,CAAK,EAC9DiC,EAAS,IAAI,YAAY,CAAC,EAGhC,OAFa,IAAI,SAASA,CAAM,EAE3B,aAAa,EAAGD,EAAa,EAAK,EAEhC,IAAI,WAAWC,CAAM,CAC9B,CAkBO,SAASC,GAAgBC,EAAgC,CAC9D,IAAMC,EAASD,EAAWA,EAAW,OAAS,CAAC,EAAI,GAQnD,OALIA,EAAWC,CAAM,EAAI,MAAS,GAC/BD,EAAWC,EAAS,CAAC,GAAK,GAC1BD,EAAWC,EAAS,CAAC,GAAK,EAC3BD,EAAWC,EAAS,CAAC,CAGzB,CAgBO,SAASC,GAAerC,EAAeY,EAAwB,CACpE,IAAM0B,EAAS,IAAM1B,EAErB,OADYZ,EAAQsC,GACT,SAAS,EAAE,SAAS1B,EAAQ,GAAG,CAC5C,CAWO,SAAS2B,EAAwBC,EAAeC,EAAwB,CAC7E,OAAOD,EAAE,SAAWC,EAAE,MACxB,CAeO,SAASC,GAAkBF,EAAwBC,EAAiC,CACzF,IAAME,EAAOC,EAAcJ,CAAC,EACtBK,EAAOD,EAAcH,CAAC,EAE5B,GAAI,CAACF,EAAwBI,EAAME,CAAI,EACrC,MAAO,GAGT,IAAIC,EAAS,EACb,QAAS,EAAI,EAAG,EAAIH,EAAK,OAAQ,IAC/BG,GAAUH,EAAK,CAAC,EAAIE,EAAK,CAAC,EAG5B,OAAOC,IAAW,CACpB,CAQO,SAASC,GAAcC,EAAkC,CAC9D,OAAQA,EAAW,CACjB,IAAK,OACH,MAAO,IACT,IAAK,SACH,MAAO,IACT,IAAK,SACH,MAAO,GACX,CACF,CAyBO,SAASJ,EAAc5C,EAAwC,CACpE,OAAO,OAAOA,GAAU,SAAWtB,GAAY,OAAOsB,CAAK,EAAIA,CACjE,CAgBO,SAASiD,GAAcC,EAA2B,CACvD,OAAOvE,GAAY,OAAOuE,CAAK,CACjC,CA+BO,SAASC,GACdxD,EACAyD,EACY,CACZ,OAAI,OAAOzD,GAAW,UACpB0D,EAAoBD,CAAM,EACnBA,EAAO,OAAOzD,CAAM,GAEtBA,CACT,CAkCO,SAAS2D,GAAeC,EAAgC,CAC7D,GAAM,CAAE,OAAAC,EAAQ,OAAAJ,EAAQ,OAAAK,EAAS3E,CAAyB,EAAIyE,EAE9DG,EAAoBF,CAAM,EAC1BH,EAAoBD,CAAM,EAE1B,IAAMO,EAAcH,EAAO,YAAYC,CAAM,EAC7C,OAAOL,EAAO,OAAOO,EAAa,CAAE,QAAS,EAAM,CAAC,CACtD,CAuBO,SAASxC,EACdH,EAA8C,EAC5B,CAClB,OAAO,MAAM,QAAQA,CAAgB,EAAIA,EAAmB,CAAC,EAAGA,CAAgB,CAClF,CAoBO,SAAS4C,GACdpC,EAA4C,EAC1B,CAClB,OAAO,MAAM,QAAQA,CAAc,EAAIA,EAAiB,CAACA,EAAgBA,CAAc,CACzF,CAQO,SAASkC,EAAuBF,EAA4C,CACjF,GAAI,CAACA,EACH,MAAM,IAAIK,CAEd,CAQO,SAASR,EAAuBD,EAA4C,CACjF,GAAI,CAACA,EACH,MAAM,IAAIU,CAEd,CAQO,SAASC,GAAiBpE,EAA4C,CAC3E,GAAI,CAACA,EACH,MAAM,IAAIqE,CAEd,CAQO,SAASC,GAAaC,EAAoD,CAC/E,GAAI,CAACA,EACH,MAAM,IAAIC,CAEd,CAQO,SAASC,GAAcC,EAAsD,CAClF,GAAI,CAACA,EACH,MAAM,IAAIC,CAEd,CAQO,SAASC,GAAoB5E,EAAuD,CACzF,GAAI,OAAOA,GAAW,SACpB,MAAM,IAAI6E,CAEd,CAMA,SAASC,EAAMzE,EAA0B,CACvC,MAAO,CAAE,GAAI,GAAM,MAAAA,CAAM,CAC3B,CAMA,SAAS0E,EAAOC,EAA6B,CAC3C,MAAO,CAAE,GAAI,GAAO,MAAAA,CAAM,CAC5B,CAUO,SAASC,GACdC,EAC2C,CAC3C,MAAO,IAAIC,IAAuC,CAChD,GAAI,CACF,OAAOL,EAAGI,EAAG,GAAGC,CAAI,CAAC,CACvB,OAASH,EAAO,CACd,OAAOD,EAAIC,CAAiB,CAC9B,CACF,CACF,CAUO,SAASI,GACdF,EACoD,CACpD,MAAO,UAAUC,IAAgD,CAC/D,GAAI,CACF,OAAOL,EAAG,MAAMI,EAAG,GAAGC,CAAI,CAAC,CAC7B,OAASH,EAAO,CACd,OAAOD,EAAIC,CAAiB,CAC9B,CACF,CACF","names":["utils_exports","__export","DEFAULT_PERIOD","MAX_COUNTER","MAX_PERIOD","MAX_SECRET_BYTES","MAX_WINDOW","MIN_PERIOD","MIN_SECRET_BYTES","RECOMMENDED_SECRET_BYTES","bytesToString","constantTimeEqual","counterToBytes","createGuardrails","dynamicTruncate","generateSecret","getDigestSize","hasGuardrailOverrides","normalizeCounterTolerance","normalizeEpochTolerance","normalizeSecret","requireBase32Plugin","requireBase32String","requireCryptoPlugin","requireIssuer","requireLabel","requireSecret","stringToBytes","truncateDigits","validateByteLengthEqual","validateCounter","validateCounterTolerance","validateEpochTolerance","validatePeriod","validateSecret","validateTime","validateToken","wrapResult","wrapResultAsync","__toCommonJS","OTPError","message","options","SecretError","SecretTooShortError","minBytes","actualBytes","SecretTooLongError","maxBytes","CounterError","CounterNegativeError","CounterOverflowError","TimeError","TimeNegativeError","PeriodError","PeriodTooSmallError","minPeriod","PeriodTooLargeError","maxPeriod","TokenError","OTPError","message","TokenLengthError","expected","actual","TokenFormatError","CounterToleranceError","OTPError","message","CounterToleranceTooLargeError","maxWindow","totalChecks","CounterToleranceNegativeError","EpochToleranceError","EpochToleranceNegativeError","EpochToleranceTooLargeError","maxTolerance","actualValue","PluginError","CryptoPluginMissingError","Base32PluginMissingError","ConfigurationError","SecretMissingError","LabelMissingError","IssuerMissingError","SecretTypeError","textEncoder","textDecoder","MIN_SECRET_BYTES","MAX_SECRET_BYTES","RECOMMENDED_SECRET_BYTES","MIN_PERIOD","MAX_PERIOD","DEFAULT_PERIOD","MAX_COUNTER","MAX_WINDOW","OVERRIDE_SYMBOL","DEFAULT_GUARDRAILS","createGuardrails","custom","hasGuardrailOverrides","guardrails","validateSecret","secret","SecretTooShortError","SecretTooLongError","validateCounter","counter","value","CounterNegativeError","CounterOverflowError","validateTime","time","TimeNegativeError","validatePeriod","period","PeriodTooSmallError","PeriodTooLargeError","validateToken","token","digits","TokenLengthError","TokenFormatError","validateCounterTolerance","counterTolerance","past","future","normalizeCounterTolerance","CounterToleranceNegativeError","totalChecks","CounterToleranceTooLargeError","validateEpochTolerance","epochTolerance","pastTolerance","futureTolerance","EpochToleranceNegativeError","maxToleranceSeconds","maxAllowed","EpochToleranceTooLargeError","counterToBytes","bigintValue","buffer","dynamicTruncate","hmacResult","offset","truncateDigits","maxOtp","validateByteLengthEqual","a","b","constantTimeEqual","bufA","stringToBytes","bufB","result","getDigestSize","algorithm","bytesToString","bytes","normalizeSecret","base32","requireBase32Plugin","generateSecret","options","crypto","length","requireCryptoPlugin","randomBytes","normalizeEpochTolerance","CryptoPluginMissingError","Base32PluginMissingError","requireSecret","SecretMissingError","requireLabel","label","LabelMissingError","requireIssuer","issuer","IssuerMissingError","requireBase32String","SecretTypeError","ok","err","error","wrapResult","fn","args","wrapResultAsync"]}
package/dist/utils.d.cts CHANGED
@@ -36,12 +36,13 @@ declare const MAX_COUNTER: number;
36
36
  * Maximum verification window size
37
37
  *
38
38
  * Limits the number of HMAC computations during verification to prevent DoS attacks.
39
- * A window of 100 means up to 201 HMAC computations ([-100, +100] range).
39
+ * A window of 99 means up to 99 HMAC computations (total checks including current counter).
40
+ * Odd number to cater for equal distribution of time drift + current.
40
41
  *
41
42
  * For TOTP: window=1 is typically sufficient (allows +-30 seconds clock drift)
42
43
  * For HOTP: window=10-50 handles reasonable counter desynchronization
43
44
  */
44
- declare const MAX_WINDOW = 100;
45
+ declare const MAX_WINDOW = 99;
45
46
  /**
46
47
  * Configurable guardrails for OTP validation
47
48
  *
@@ -228,7 +229,7 @@ declare function validateToken(token: string, digits: number): void;
228
229
  * validateCounterTolerance([0, 1]); // OK: 2 offsets
229
230
  * ```
230
231
  */
231
- declare function validateCounterTolerance(counterTolerance: number | number[], guardrails?: OTPGuardrails): void;
232
+ declare function validateCounterTolerance(counterTolerance: number | [number, number], guardrails?: OTPGuardrails): void;
232
233
  /**
233
234
  * Validate epoch tolerance for TOTP verification
234
235
  *
@@ -332,6 +333,9 @@ declare function getDigestSize(algorithm: HashAlgorithm): number;
332
333
  * This utility function normalizes input to Uint8Array, converting strings
333
334
  * using UTF-8 encoding. Uint8Array inputs are returned as-is.
334
335
  *
336
+ * Use this to convert raw secret strings (passphrases) to Uint8Array
337
+ * before passing them to generation or verification functions.
338
+ *
335
339
  * @param value - The value to convert (string or Uint8Array)
336
340
  * @returns The value as a Uint8Array (UTF-8 encoded for strings)
337
341
  *
@@ -348,33 +352,31 @@ declare function getDigestSize(algorithm: HashAlgorithm): number;
348
352
  */
349
353
  declare function stringToBytes(value: string | Uint8Array): Uint8Array;
350
354
  /**
351
- * Convert a hex string to a Uint8Array
352
- *
353
- * This is useful for working with RFC test vectors and debugging HMAC outputs,
354
- * which are commonly represented as hexadecimal strings.
355
+ * Convert bytes to UTF-8 string
355
356
  *
356
- * If your environment supports it, consider using `Uint8Array.fromHex()` instead.
357
+ * Uses TextDecoder for proper UTF-8 handling.
357
358
  *
358
- * @param hex - The hex string to convert (lowercase or uppercase, no 0x prefix)
359
- * @returns The bytes as a Uint8Array
359
+ * @param bytes - Uint8Array to convert
360
+ * @returns UTF-8 string
360
361
  *
361
362
  * @example
362
363
  * ```ts
363
- * import { hexToBytes } from '@otplib/core'
364
- *
365
- * // Convert RFC 4226 HMAC test vector
366
- * const hmac = hexToBytes('cc93cf18508d94934c64b65d8ba7667fb7cde4b0')
367
- * // Returns: Uint8Array([0xcc, 0x93, 0xcf, ...])
364
+ * const str = bytesToString(new Uint8Array([104, 101, 108, 108, 111]));
365
+ * // str === "hello"
368
366
  * ```
369
367
  */
370
- declare function hexToBytes(hex: string): Uint8Array;
368
+ declare function bytesToString(bytes: Uint8Array): string;
371
369
  /**
372
370
  * Normalize secret input to Uint8Array
373
371
  *
374
372
  * Accepts either a Base32-encoded string or Uint8Array and returns Uint8Array.
375
373
  * If a Base32Plugin is provided, string secrets will be automatically decoded.
376
374
  *
377
- * @param secret - The secret to normalize (string or Uint8Array)
375
+ * **Note**: By default, strings are assumed to be Base32 encoded.
376
+ * If you have a raw string secret (e.g. a passphrase), you must convert it
377
+ * to a Uint8Array using {@link stringToBytes} before calling this function.
378
+ *
379
+ * @param secret - The secret to normalize (Base32 string or Uint8Array)
378
380
  * @param base32 - Optional Base32Plugin to decode string secrets
379
381
  * @returns The secret as Uint8Array
380
382
  * @throws {Error} If secret is a string but no Base32Plugin is provided
@@ -430,25 +432,27 @@ declare function normalizeSecret(secret: string | Uint8Array, base32?: {
430
432
  */
431
433
  declare function generateSecret(options: SecretOptions): string;
432
434
  /**
433
- * Normalize counter tolerance to an array of offsets
435
+ * Normalize counter tolerance to [past, future] tuple
434
436
  *
435
- * Converts a number or array counter tolerance specification into an array of offsets
436
- * - Number: creates symmetric range [-tolerance, +tolerance]
437
- * - Array: uses the array as-is (already contains specific offsets)
437
+ * Converts a number or tuple counter tolerance specification into a [past, future] tuple
438
+ * - Number: creates look-ahead only tolerance [0, tolerance] (default for security)
439
+ * - Tuple: uses the tuple as-is (past, future)
438
440
  *
439
- * @param counterTolerance - Counter tolerance specification (number or array of offsets)
440
- * @returns Array of offsets to check
441
+ * The default behavior (number look-ahead only) improves security by preventing
442
+ * replay attacks. HOTP counters should only move forward in normal operation.
443
+ *
444
+ * @param counterTolerance - Counter tolerance specification (number or tuple [past, future])
445
+ * @returns Tuple [past, future] representing counters to check
441
446
  *
442
447
  * @example
443
448
  * ```ts
444
- * normalizeCounterTolerance(0) // [0]
445
- * normalizeCounterTolerance(1) // [-1, 0, 1]
446
- * normalizeCounterTolerance(2) // [-2, -1, 0, 1, 2]
447
- * normalizeCounterTolerance([0, 1]) // [0, 1]
448
- * normalizeCounterTolerance([-1, 0, 1]) // [-1, 0, 1]
449
+ * normalizeCounterTolerance(0) // [0, 0]
450
+ * normalizeCounterTolerance(5) // [0, 5] - look-ahead only (secure default)
451
+ * normalizeCounterTolerance([10, 5]) // [10, 5] - explicit past/future
452
+ * normalizeCounterTolerance([5, 5]) // [5, 5] - explicit symmetric (use with caution)
449
453
  * ```
450
454
  */
451
- declare function normalizeCounterTolerance(counterTolerance?: number | number[]): number[];
455
+ declare function normalizeCounterTolerance(counterTolerance?: number | [number, number]): [number, number];
452
456
  /**
453
457
  * Normalize epoch tolerance to [past, future] tuple
454
458
  *
@@ -529,4 +533,4 @@ declare function wrapResult<T, Args extends unknown[]>(fn: (...args: Args) => T)
529
533
  */
530
534
  declare function wrapResultAsync<T, Args extends unknown[]>(fn: (...args: Args) => Promise<T>): (...args: Args) => Promise<OTPResult<T, OTPError>>;
531
535
 
532
- export { DEFAULT_PERIOD, MAX_COUNTER, MAX_PERIOD, MAX_SECRET_BYTES, MAX_WINDOW, MIN_PERIOD, MIN_SECRET_BYTES, type OTPGuardrails, type OTPGuardrailsConfig, RECOMMENDED_SECRET_BYTES, constantTimeEqual, counterToBytes, createGuardrails, dynamicTruncate, generateSecret, getDigestSize, hasGuardrailOverrides, hexToBytes, normalizeCounterTolerance, normalizeEpochTolerance, normalizeSecret, requireBase32Plugin, requireBase32String, requireCryptoPlugin, requireIssuer, requireLabel, requireSecret, stringToBytes, truncateDigits, validateByteLengthEqual, validateCounter, validateCounterTolerance, validateEpochTolerance, validatePeriod, validateSecret, validateTime, validateToken, wrapResult, wrapResultAsync };
536
+ export { DEFAULT_PERIOD, MAX_COUNTER, MAX_PERIOD, MAX_SECRET_BYTES, MAX_WINDOW, MIN_PERIOD, MIN_SECRET_BYTES, type OTPGuardrails, type OTPGuardrailsConfig, RECOMMENDED_SECRET_BYTES, bytesToString, constantTimeEqual, counterToBytes, createGuardrails, dynamicTruncate, generateSecret, getDigestSize, hasGuardrailOverrides, normalizeCounterTolerance, normalizeEpochTolerance, normalizeSecret, requireBase32Plugin, requireBase32String, requireCryptoPlugin, requireIssuer, requireLabel, requireSecret, stringToBytes, truncateDigits, validateByteLengthEqual, validateCounter, validateCounterTolerance, validateEpochTolerance, validatePeriod, validateSecret, validateTime, validateToken, wrapResult, wrapResultAsync };
package/dist/utils.d.ts CHANGED
@@ -36,12 +36,13 @@ declare const MAX_COUNTER: number;
36
36
  * Maximum verification window size
37
37
  *
38
38
  * Limits the number of HMAC computations during verification to prevent DoS attacks.
39
- * A window of 100 means up to 201 HMAC computations ([-100, +100] range).
39
+ * A window of 99 means up to 99 HMAC computations (total checks including current counter).
40
+ * Odd number to cater for equal distribution of time drift + current.
40
41
  *
41
42
  * For TOTP: window=1 is typically sufficient (allows +-30 seconds clock drift)
42
43
  * For HOTP: window=10-50 handles reasonable counter desynchronization
43
44
  */
44
- declare const MAX_WINDOW = 100;
45
+ declare const MAX_WINDOW = 99;
45
46
  /**
46
47
  * Configurable guardrails for OTP validation
47
48
  *
@@ -228,7 +229,7 @@ declare function validateToken(token: string, digits: number): void;
228
229
  * validateCounterTolerance([0, 1]); // OK: 2 offsets
229
230
  * ```
230
231
  */
231
- declare function validateCounterTolerance(counterTolerance: number | number[], guardrails?: OTPGuardrails): void;
232
+ declare function validateCounterTolerance(counterTolerance: number | [number, number], guardrails?: OTPGuardrails): void;
232
233
  /**
233
234
  * Validate epoch tolerance for TOTP verification
234
235
  *
@@ -332,6 +333,9 @@ declare function getDigestSize(algorithm: HashAlgorithm): number;
332
333
  * This utility function normalizes input to Uint8Array, converting strings
333
334
  * using UTF-8 encoding. Uint8Array inputs are returned as-is.
334
335
  *
336
+ * Use this to convert raw secret strings (passphrases) to Uint8Array
337
+ * before passing them to generation or verification functions.
338
+ *
335
339
  * @param value - The value to convert (string or Uint8Array)
336
340
  * @returns The value as a Uint8Array (UTF-8 encoded for strings)
337
341
  *
@@ -348,33 +352,31 @@ declare function getDigestSize(algorithm: HashAlgorithm): number;
348
352
  */
349
353
  declare function stringToBytes(value: string | Uint8Array): Uint8Array;
350
354
  /**
351
- * Convert a hex string to a Uint8Array
352
- *
353
- * This is useful for working with RFC test vectors and debugging HMAC outputs,
354
- * which are commonly represented as hexadecimal strings.
355
+ * Convert bytes to UTF-8 string
355
356
  *
356
- * If your environment supports it, consider using `Uint8Array.fromHex()` instead.
357
+ * Uses TextDecoder for proper UTF-8 handling.
357
358
  *
358
- * @param hex - The hex string to convert (lowercase or uppercase, no 0x prefix)
359
- * @returns The bytes as a Uint8Array
359
+ * @param bytes - Uint8Array to convert
360
+ * @returns UTF-8 string
360
361
  *
361
362
  * @example
362
363
  * ```ts
363
- * import { hexToBytes } from '@otplib/core'
364
- *
365
- * // Convert RFC 4226 HMAC test vector
366
- * const hmac = hexToBytes('cc93cf18508d94934c64b65d8ba7667fb7cde4b0')
367
- * // Returns: Uint8Array([0xcc, 0x93, 0xcf, ...])
364
+ * const str = bytesToString(new Uint8Array([104, 101, 108, 108, 111]));
365
+ * // str === "hello"
368
366
  * ```
369
367
  */
370
- declare function hexToBytes(hex: string): Uint8Array;
368
+ declare function bytesToString(bytes: Uint8Array): string;
371
369
  /**
372
370
  * Normalize secret input to Uint8Array
373
371
  *
374
372
  * Accepts either a Base32-encoded string or Uint8Array and returns Uint8Array.
375
373
  * If a Base32Plugin is provided, string secrets will be automatically decoded.
376
374
  *
377
- * @param secret - The secret to normalize (string or Uint8Array)
375
+ * **Note**: By default, strings are assumed to be Base32 encoded.
376
+ * If you have a raw string secret (e.g. a passphrase), you must convert it
377
+ * to a Uint8Array using {@link stringToBytes} before calling this function.
378
+ *
379
+ * @param secret - The secret to normalize (Base32 string or Uint8Array)
378
380
  * @param base32 - Optional Base32Plugin to decode string secrets
379
381
  * @returns The secret as Uint8Array
380
382
  * @throws {Error} If secret is a string but no Base32Plugin is provided
@@ -430,25 +432,27 @@ declare function normalizeSecret(secret: string | Uint8Array, base32?: {
430
432
  */
431
433
  declare function generateSecret(options: SecretOptions): string;
432
434
  /**
433
- * Normalize counter tolerance to an array of offsets
435
+ * Normalize counter tolerance to [past, future] tuple
434
436
  *
435
- * Converts a number or array counter tolerance specification into an array of offsets
436
- * - Number: creates symmetric range [-tolerance, +tolerance]
437
- * - Array: uses the array as-is (already contains specific offsets)
437
+ * Converts a number or tuple counter tolerance specification into a [past, future] tuple
438
+ * - Number: creates look-ahead only tolerance [0, tolerance] (default for security)
439
+ * - Tuple: uses the tuple as-is (past, future)
438
440
  *
439
- * @param counterTolerance - Counter tolerance specification (number or array of offsets)
440
- * @returns Array of offsets to check
441
+ * The default behavior (number look-ahead only) improves security by preventing
442
+ * replay attacks. HOTP counters should only move forward in normal operation.
443
+ *
444
+ * @param counterTolerance - Counter tolerance specification (number or tuple [past, future])
445
+ * @returns Tuple [past, future] representing counters to check
441
446
  *
442
447
  * @example
443
448
  * ```ts
444
- * normalizeCounterTolerance(0) // [0]
445
- * normalizeCounterTolerance(1) // [-1, 0, 1]
446
- * normalizeCounterTolerance(2) // [-2, -1, 0, 1, 2]
447
- * normalizeCounterTolerance([0, 1]) // [0, 1]
448
- * normalizeCounterTolerance([-1, 0, 1]) // [-1, 0, 1]
449
+ * normalizeCounterTolerance(0) // [0, 0]
450
+ * normalizeCounterTolerance(5) // [0, 5] - look-ahead only (secure default)
451
+ * normalizeCounterTolerance([10, 5]) // [10, 5] - explicit past/future
452
+ * normalizeCounterTolerance([5, 5]) // [5, 5] - explicit symmetric (use with caution)
449
453
  * ```
450
454
  */
451
- declare function normalizeCounterTolerance(counterTolerance?: number | number[]): number[];
455
+ declare function normalizeCounterTolerance(counterTolerance?: number | [number, number]): [number, number];
452
456
  /**
453
457
  * Normalize epoch tolerance to [past, future] tuple
454
458
  *
@@ -529,4 +533,4 @@ declare function wrapResult<T, Args extends unknown[]>(fn: (...args: Args) => T)
529
533
  */
530
534
  declare function wrapResultAsync<T, Args extends unknown[]>(fn: (...args: Args) => Promise<T>): (...args: Args) => Promise<OTPResult<T, OTPError>>;
531
535
 
532
- export { DEFAULT_PERIOD, MAX_COUNTER, MAX_PERIOD, MAX_SECRET_BYTES, MAX_WINDOW, MIN_PERIOD, MIN_SECRET_BYTES, type OTPGuardrails, type OTPGuardrailsConfig, RECOMMENDED_SECRET_BYTES, constantTimeEqual, counterToBytes, createGuardrails, dynamicTruncate, generateSecret, getDigestSize, hasGuardrailOverrides, hexToBytes, normalizeCounterTolerance, normalizeEpochTolerance, normalizeSecret, requireBase32Plugin, requireBase32String, requireCryptoPlugin, requireIssuer, requireLabel, requireSecret, stringToBytes, truncateDigits, validateByteLengthEqual, validateCounter, validateCounterTolerance, validateEpochTolerance, validatePeriod, validateSecret, validateTime, validateToken, wrapResult, wrapResultAsync };
536
+ export { DEFAULT_PERIOD, MAX_COUNTER, MAX_PERIOD, MAX_SECRET_BYTES, MAX_WINDOW, MIN_PERIOD, MIN_SECRET_BYTES, type OTPGuardrails, type OTPGuardrailsConfig, RECOMMENDED_SECRET_BYTES, bytesToString, constantTimeEqual, counterToBytes, createGuardrails, dynamicTruncate, generateSecret, getDigestSize, hasGuardrailOverrides, normalizeCounterTolerance, normalizeEpochTolerance, normalizeSecret, requireBase32Plugin, requireBase32String, requireCryptoPlugin, requireIssuer, requireLabel, requireSecret, stringToBytes, truncateDigits, validateByteLengthEqual, validateCounter, validateCounterTolerance, validateEpochTolerance, validatePeriod, validateSecret, validateTime, validateToken, wrapResult, wrapResultAsync };
package/dist/utils.js CHANGED
@@ -1,2 +1,2 @@
1
- var n=class extends Error{constructor(e,t){super(e,t),this.name="OTPError"}},c=class extends n{constructor(e){super(e),this.name="SecretError"}},p=class extends c{constructor(e,t){super(`Secret must be at least ${e} bytes (${e*8} bits), got ${t} bytes`),this.name="SecretTooShortError"}},l=class extends c{constructor(e,t){super(`Secret must not exceed ${e} bytes, got ${t} bytes`),this.name="SecretTooLongError"}},x=class extends n{constructor(e){super(e),this.name="CounterError"}},g=class extends x{constructor(){super("Counter must be non-negative"),this.name="CounterNegativeError"}},m=class extends x{constructor(){super("Counter exceeds maximum safe integer value"),this.name="CounterOverflowError"}},U=class extends n{constructor(e){super(e),this.name="TimeError"}},d=class extends U{constructor(){super("Time must be non-negative"),this.name="TimeNegativeError"}},E=class extends n{constructor(e){super(e),this.name="PeriodError"}},f=class extends E{constructor(e){super(`Period must be at least ${e} second(s)`),this.name="PeriodTooSmallError"}},T=class extends E{constructor(e){super(`Period must not exceed ${e} seconds`),this.name="PeriodTooLargeError"}};var b=class extends n{constructor(e){super(e),this.name="TokenError"}},h=class extends b{constructor(e,t){super(`Token must be ${e} digits, got ${t}`),this.name="TokenLengthError"}},O=class extends b{constructor(){super("Token must contain only digits"),this.name="TokenFormatError"}};var v=class extends n{constructor(e){super(e),this.name="CounterToleranceError"}},A=class extends v{constructor(e,t){super(`Counter tolerance size must not exceed ${e}, got ${t}. Large tolerances can cause performance issues.`),this.name="CounterToleranceTooLargeError"}},y=class extends n{constructor(e){super(e),this.name="EpochToleranceError"}},P=class extends y{constructor(){super("Epoch tolerance cannot contain negative values"),this.name="EpochToleranceNegativeError"}},w=class extends y{constructor(e,t){super(`Epoch tolerance must not exceed ${e} seconds, got ${t}. Large tolerances can cause performance issues.`),this.name="EpochToleranceTooLargeError"}},S=class extends n{constructor(e){super(e),this.name="PluginError"}},_=class extends S{constructor(){super("Crypto plugin is required."),this.name="CryptoPluginMissingError"}},R=class extends S{constructor(){super("Base32 plugin is required."),this.name="Base32PluginMissingError"}},u=class extends n{constructor(e){super(e),this.name="ConfigurationError"}},I=class extends u{constructor(){super("Secret is required. Use generateSecret() to create one, or provide via { secret: 'YOUR_BASE32_SECRET' }"),this.name="SecretMissingError"}},M=class extends u{constructor(){super("Label is required for URI generation. Example: { label: 'user@example.com' }"),this.name="LabelMissingError"}},B=class extends u{constructor(){super("Issuer is required for URI generation. Example: { issuer: 'MyApp' }"),this.name="IssuerMissingError"}},C=class extends u{constructor(){super("Class API requires secret to be a Base32 string, not Uint8Array. Use generateSecret() or provide a Base32-encoded string."),this.name="SecretTypeError"}};var q=new TextEncoder,Y=16,k=64,W=20,z=1,F=3600,V=30,j=Number.MAX_SAFE_INTEGER,H=100,D=Symbol("otplib.guardrails.override"),a=Object.freeze({MIN_SECRET_BYTES:Y,MAX_SECRET_BYTES:k,MIN_PERIOD:z,MAX_PERIOD:F,MAX_COUNTER:j,MAX_WINDOW:H,[D]:!1});function er(r){return r?Object.freeze({...a,...r,[D]:!0}):a}function tr(r){return r[D]??!1}function nr(r,e=a){if(r.length<e.MIN_SECRET_BYTES)throw new p(e.MIN_SECRET_BYTES,r.length);if(r.length>e.MAX_SECRET_BYTES)throw new l(e.MAX_SECRET_BYTES,r.length)}function sr(r,e=a){let t=typeof r=="bigint"?r:BigInt(r);if(t<0n)throw new g;if(t>BigInt(e.MAX_COUNTER))throw new m}function or(r){if(r<0)throw new d}function ir(r,e=a){if(!Number.isInteger(r)||r<e.MIN_PERIOD)throw new f(e.MIN_PERIOD);if(r>e.MAX_PERIOD)throw new T(e.MAX_PERIOD)}function ar(r,e){if(r.length!==e)throw new h(e,r.length);if(!/^\d+$/.test(r))throw new O}function ur(r,e=a){if((Array.isArray(r)?r.length:r*2+1)>e.MAX_WINDOW*2+1)throw new A(e.MAX_WINDOW,Array.isArray(r)?r.length:r)}function cr(r,e=V,t=a){let[s,o]=Array.isArray(r)?r:[r,r];if(s<0||o<0)throw new P;let i=t.MAX_WINDOW*e,N=Math.max(s,o);if(N>i)throw new w(i,N)}function pr(r){let e=typeof r=="bigint"?r:BigInt(r),t=new ArrayBuffer(8);return new DataView(t).setBigUint64(0,e,!1),new Uint8Array(t)}function lr(r){let e=r[r.length-1]&15;return(r[e]&127)<<24|r[e+1]<<16|r[e+2]<<8|r[e+3]}function xr(r,e){let t=10**e;return(r%t).toString().padStart(e,"0")}function J(r,e){return r.length===e.length}function gr(r,e){let t=$(r),s=$(e);if(!J(t,s))return!1;let o=0;for(let i=0;i<t.length;i++)o|=t[i]^s[i];return o===0}function mr(r){switch(r){case"sha1":return 20;case"sha256":return 32;case"sha512":return 64}}function $(r){return typeof r=="string"?q.encode(r):r}function dr(r){let e=new Uint8Array(r.length/2);for(let t=0;t<r.length;t+=2)e[t/2]=parseInt(r.substring(t,t+2),16);return e}function Er(r,e){return typeof r=="string"?(X(e),e.decode(r)):r}function fr(r){let{crypto:e,base32:t,length:s=W}=r;K(e),X(t);let o=e.randomBytes(s);return t.encode(o,{padding:!1})}function Tr(r=0){if(Array.isArray(r))return r;let e=[];for(let t=-r;t<=r;t++)e.push(t|0);return e}function br(r=0){return Array.isArray(r)?r:[r,r]}function K(r){if(!r)throw new _}function X(r){if(!r)throw new R}function hr(r){if(!r)throw new I}function Or(r){if(!r)throw new M}function Ar(r){if(!r)throw new B}function yr(r){if(typeof r!="string")throw new C}function G(r){return{ok:!0,value:r}}function L(r){return{ok:!1,error:r}}function Pr(r){return(...e)=>{try{return G(r(...e))}catch(t){return L(t)}}}function wr(r){return async(...e)=>{try{return G(await r(...e))}catch(t){return L(t)}}}export{V as DEFAULT_PERIOD,j as MAX_COUNTER,F as MAX_PERIOD,k as MAX_SECRET_BYTES,H as MAX_WINDOW,z as MIN_PERIOD,Y as MIN_SECRET_BYTES,W as RECOMMENDED_SECRET_BYTES,gr as constantTimeEqual,pr as counterToBytes,er as createGuardrails,lr as dynamicTruncate,fr as generateSecret,mr as getDigestSize,tr as hasGuardrailOverrides,dr as hexToBytes,Tr as normalizeCounterTolerance,br as normalizeEpochTolerance,Er as normalizeSecret,X as requireBase32Plugin,yr as requireBase32String,K as requireCryptoPlugin,Ar as requireIssuer,Or as requireLabel,hr as requireSecret,$ as stringToBytes,xr as truncateDigits,J as validateByteLengthEqual,sr as validateCounter,ur as validateCounterTolerance,cr as validateEpochTolerance,ir as validatePeriod,nr as validateSecret,or as validateTime,ar as validateToken,Pr as wrapResult,wr as wrapResultAsync};
1
+ var s=class extends Error{constructor(e,t){super(e,t),this.name="OTPError"}},c=class extends s{constructor(e){super(e),this.name="SecretError"}},p=class extends c{constructor(e,t){super(`Secret must be at least ${e} bytes (${e*8} bits), got ${t} bytes`),this.name="SecretTooShortError"}},l=class extends c{constructor(e,t){super(`Secret must not exceed ${e} bytes, got ${t} bytes`),this.name="SecretTooLongError"}},x=class extends s{constructor(e){super(e),this.name="CounterError"}},d=class extends x{constructor(){super("Counter must be non-negative"),this.name="CounterNegativeError"}},m=class extends x{constructor(){super("Counter exceeds maximum safe integer value"),this.name="CounterOverflowError"}},D=class extends s{constructor(e){super(e),this.name="TimeError"}},g=class extends D{constructor(){super("Time must be non-negative"),this.name="TimeNegativeError"}},T=class extends s{constructor(e){super(e),this.name="PeriodError"}},E=class extends T{constructor(e){super(`Period must be at least ${e} second(s)`),this.name="PeriodTooSmallError"}},f=class extends T{constructor(e){super(`Period must not exceed ${e} seconds`),this.name="PeriodTooLargeError"}};var b=class extends s{constructor(e){super(e),this.name="TokenError"}},h=class extends b{constructor(e,t){super(`Token must be ${e} digits, got ${t}`),this.name="TokenLengthError"}},O=class extends b{constructor(){super("Token must contain only digits"),this.name="TokenFormatError"}};var A=class extends s{constructor(e){super(e),this.name="CounterToleranceError"}},y=class extends A{constructor(e,t){super(`Counter tolerance validation failed: total checks (${t}) exceeds MAX_WINDOW (${e})`),this.name="CounterToleranceTooLargeError"}},P=class extends A{constructor(){super("Counter tolerance cannot contain negative values"),this.name="CounterToleranceNegativeError"}},w=class extends s{constructor(e){super(e),this.name="EpochToleranceError"}},S=class extends w{constructor(){super("Epoch tolerance cannot contain negative values"),this.name="EpochToleranceNegativeError"}},_=class extends w{constructor(e,t){super(`Epoch tolerance must not exceed ${e} seconds, got ${t}. Large tolerances can cause performance issues.`),this.name="EpochToleranceTooLargeError"}},R=class extends s{constructor(e){super(e),this.name="PluginError"}},I=class extends R{constructor(){super("Crypto plugin is required."),this.name="CryptoPluginMissingError"}},M=class extends R{constructor(){super("Base32 plugin is required."),this.name="Base32PluginMissingError"}},u=class extends s{constructor(e){super(e),this.name="ConfigurationError"}},v=class extends u{constructor(){super("Secret is required. Use generateSecret() to create one, or provide via { secret: 'YOUR_BASE32_SECRET' }"),this.name="SecretMissingError"}},C=class extends u{constructor(){super("Label is required for URI generation. Example: { label: 'user@example.com' }"),this.name="LabelMissingError"}},B=class extends u{constructor(){super("Issuer is required for URI generation. Example: { issuer: 'MyApp' }"),this.name="IssuerMissingError"}},U=class extends u{constructor(){super("Class API requires secret to be a Base32 string, not Uint8Array. Use generateSecret() or provide a Base32-encoded string."),this.name="SecretTypeError"}};var k=new TextEncoder,W=new TextDecoder,Y=16,z=64,F=20,V=1,j=3600,H=30,J=Number.MAX_SAFE_INTEGER,K=99,N=Symbol("otplib.guardrails.override"),a=Object.freeze({MIN_SECRET_BYTES:Y,MAX_SECRET_BYTES:z,MIN_PERIOD:V,MAX_PERIOD:j,MAX_COUNTER:J,MAX_WINDOW:K,[N]:!1});function sr(r){return r?Object.freeze({...a,...r,[N]:!0}):a}function or(r){return r[N]??!1}function ir(r,e=a){if(r.length<e.MIN_SECRET_BYTES)throw new p(e.MIN_SECRET_BYTES,r.length);if(r.length>e.MAX_SECRET_BYTES)throw new l(e.MAX_SECRET_BYTES,r.length)}function ar(r,e=a){let t=typeof r=="bigint"?r:BigInt(r);if(t<0n)throw new d;if(t>BigInt(e.MAX_COUNTER))throw new m}function ur(r){if(r<0)throw new g}function cr(r,e=a){if(!Number.isInteger(r)||r<e.MIN_PERIOD)throw new E(e.MIN_PERIOD);if(r>e.MAX_PERIOD)throw new f(e.MAX_PERIOD)}function pr(r,e){if(r.length!==e)throw new h(e,r.length);if(!/^\d+$/.test(r))throw new O}function lr(r,e=a){let[t,n]=Z(r);if(t<0||n<0)throw new P;let o=t+n+1;if(o>e.MAX_WINDOW)throw new y(e.MAX_WINDOW,o)}function xr(r,e=H,t=a){let[n,o]=Array.isArray(r)?r:[r,r];if(n<0||o<0)throw new S;let i=t.MAX_WINDOW*e,X=Math.max(n,o);if(X>i)throw new _(i,X)}function dr(r){let e=typeof r=="bigint"?r:BigInt(r),t=new ArrayBuffer(8);return new DataView(t).setBigUint64(0,e,!1),new Uint8Array(t)}function mr(r){let e=r[r.length-1]&15;return(r[e]&127)<<24|r[e+1]<<16|r[e+2]<<8|r[e+3]}function gr(r,e){let t=10**e;return(r%t).toString().padStart(e,"0")}function Q(r,e){return r.length===e.length}function Tr(r,e){let t=$(r),n=$(e);if(!Q(t,n))return!1;let o=0;for(let i=0;i<t.length;i++)o|=t[i]^n[i];return o===0}function Er(r){switch(r){case"sha1":return 20;case"sha256":return 32;case"sha512":return 64}}function $(r){return typeof r=="string"?k.encode(r):r}function fr(r){return W.decode(r)}function br(r,e){return typeof r=="string"?(G(e),e.decode(r)):r}function hr(r){let{crypto:e,base32:t,length:n=F}=r;rr(e),G(t);let o=e.randomBytes(n);return t.encode(o,{padding:!1})}function Z(r=0){return Array.isArray(r)?r:[0,r]}function Or(r=0){return Array.isArray(r)?r:[r,r]}function rr(r){if(!r)throw new I}function G(r){if(!r)throw new M}function Ar(r){if(!r)throw new v}function yr(r){if(!r)throw new C}function Pr(r){if(!r)throw new B}function wr(r){if(typeof r!="string")throw new U}function L(r){return{ok:!0,value:r}}function q(r){return{ok:!1,error:r}}function Sr(r){return(...e)=>{try{return L(r(...e))}catch(t){return q(t)}}}function _r(r){return async(...e)=>{try{return L(await r(...e))}catch(t){return q(t)}}}export{H as DEFAULT_PERIOD,J as MAX_COUNTER,j as MAX_PERIOD,z as MAX_SECRET_BYTES,K as MAX_WINDOW,V as MIN_PERIOD,Y as MIN_SECRET_BYTES,F as RECOMMENDED_SECRET_BYTES,fr as bytesToString,Tr as constantTimeEqual,dr as counterToBytes,sr as createGuardrails,mr as dynamicTruncate,hr as generateSecret,Er as getDigestSize,or as hasGuardrailOverrides,Z as normalizeCounterTolerance,Or as normalizeEpochTolerance,br as normalizeSecret,G as requireBase32Plugin,wr as requireBase32String,rr as requireCryptoPlugin,Pr as requireIssuer,yr as requireLabel,Ar as requireSecret,$ as stringToBytes,gr as truncateDigits,Q as validateByteLengthEqual,ar as validateCounter,lr as validateCounterTolerance,xr as validateEpochTolerance,cr as validatePeriod,ir as validateSecret,ur as validateTime,pr as validateToken,Sr as wrapResult,_r as wrapResultAsync};
2
2
  //# sourceMappingURL=utils.js.map