@otplib/hotp 13.1.0 → 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.
package/README.md CHANGED
@@ -22,7 +22,7 @@ const base32 = new ScureBase32Plugin();
22
22
 
23
23
  // Generate an HOTP token for counter 0
24
24
  const token = await generate({
25
- secret: "JBSWY3DPEHPK3PXP",
25
+ secret: "GEZDGNBVGY3TQOJQGEZDGNBVGY",
26
26
  counter: 0,
27
27
  crypto,
28
28
  base32,
@@ -30,7 +30,7 @@ const token = await generate({
30
30
 
31
31
  // Verify an HOTP token
32
32
  const result = await verify({
33
- secret: "JBSWY3DPEHPK3PXP",
33
+ secret: "GEZDGNBVGY3TQOJQGEZDGNBVGY",
34
34
  token: "123456",
35
35
  counter: 0,
36
36
  crypto,
package/dist/index.cjs CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";var d=Object.defineProperty;var G=Object.getOwnPropertyDescriptor;var V=Object.getOwnPropertyNames;var v=Object.prototype.hasOwnProperty;var R=(c,e)=>{for(var r in e)d(c,r,{get:e[r],enumerable:!0})},B=(c,e,r,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of V(e))!v.call(c,n)&&n!==r&&d(c,n,{get:()=>e[n],enumerable:!(s=G(e,n))||s.enumerable});return c};var S=c=>B(d({},"__esModule",{value:!0}),c);var k={};R(k,{HOTP:()=>g,generate:()=>f,generateSync:()=>b,verify:()=>P,verifySync:()=>q,wrapResult:()=>m.wrapResult,wrapResultAsync:()=>m.wrapResultAsync});module.exports=S(k);var t=require("@otplib/core");var i=require("@otplib/core"),h=require("@otplib/uri");var g=class{options;guardrails;constructor(e={}){this.options=e,this.guardrails=(0,i.createGuardrails)(e.guardrails)}generateSecret(){let{crypto:e,base32:r}=this.options;return(0,i.requireCryptoPlugin)(e),(0,i.requireBase32Plugin)(r),(0,i.generateSecret)({crypto:e,base32:r})}async generate(e,r){let s={...this.options,...r},{secret:n,crypto:a,base32:o,algorithm:u="sha1",digits:p=6}=s;(0,i.requireSecret)(n),(0,i.requireCryptoPlugin)(a),(0,i.requireBase32Plugin)(o);let l=r?.guardrails??this.guardrails;return f({secret:n,counter:e,algorithm:u,digits:p,crypto:a,base32:o,guardrails:l})}async verify(e,r){let s={...this.options,...r},{secret:n,crypto:a,base32:o,algorithm:u="sha1",digits:p=6,counterTolerance:l=0}=s;(0,i.requireSecret)(n),(0,i.requireCryptoPlugin)(a),(0,i.requireBase32Plugin)(o);let y=r?.guardrails??this.guardrails;return P({secret:n,token:e.token,counter:e.counter,algorithm:u,digits:p,counterTolerance:l,crypto:a,base32:o,guardrails:y})}toURI(e=0){let{issuer:r,label:s,secret:n,algorithm:a="sha1",digits:o=6}=this.options;return(0,i.requireSecret)(n),(0,i.requireLabel)(s),(0,i.requireIssuer)(r),(0,i.requireBase32String)(n),(0,h.generateHOTP)({issuer:r,label:s,secret:n,algorithm:a,digits:o,counter:e})}};var m=require("@otplib/core");function H(c){let{secret:e,counter:r,algorithm:s="sha1",digits:n=6,crypto:a,base32:o,guardrails:u}=c;(0,t.requireSecret)(e),(0,t.requireCryptoPlugin)(a);let p=(0,t.normalizeSecret)(e,o);(0,t.validateSecret)(p,u),(0,t.validateCounter)(r,u);let l=(0,t.createCryptoContext)(a),y=(0,t.counterToBytes)(r);return{ctx:l,algorithm:s,digits:n,secretBytes:p,counterBytes:y}}async function f(c){let{ctx:e,algorithm:r,digits:s,secretBytes:n,counterBytes:a}=H(c),o=await e.hmac(r,n,a),u=(0,t.dynamicTruncate)(o);return(0,t.truncateDigits)(u,s)}function b(c){let{ctx:e,algorithm:r,digits:s,secretBytes:n,counterBytes:a}=H(c),o=e.hmacSync(r,n,a),u=(0,t.dynamicTruncate)(o);return(0,t.truncateDigits)(u,s)}function C(c){let{secret:e,counter:r,token:s,algorithm:n="sha1",digits:a=6,crypto:o,base32:u,counterTolerance:p=0,guardrails:l=(0,t.createGuardrails)()}=c;(0,t.requireSecret)(e),(0,t.requireCryptoPlugin)(o);let y=(0,t.normalizeSecret)(e,u);(0,t.validateSecret)(y,l),(0,t.validateCounter)(r,l),(0,t.validateToken)(s,a),(0,t.validateCounterTolerance)(p,l);let T=typeof r=="bigint"?Number(r):r,x=(0,t.normalizeCounterTolerance)(p).filter(O=>T+O>=0);return{token:s,counterNum:T,offsets:x,crypto:o,getGenerateOptions:O=>({secret:y,counter:O,algorithm:n,digits:a,crypto:o})}}async function P(c){let{token:e,counterNum:r,offsets:s,crypto:n,getGenerateOptions:a}=C(c);for(let o of s){let u=r+o,p=await f(a(u));if(n.constantTimeEqual(p,e))return{valid:!0,delta:o}}return{valid:!1}}function q(c){let{token:e,counterNum:r,offsets:s,crypto:n,getGenerateOptions:a}=C(c);for(let o of s){let u=r+o,p=b(a(u));if(n.constantTimeEqual(p,e))return{valid:!0,delta:o}}return{valid:!1}}0&&(module.exports={HOTP,generate,generateSync,verify,verifySync,wrapResult,wrapResultAsync});
1
+ "use strict";var d=Object.defineProperty;var v=Object.getOwnPropertyDescriptor;var R=Object.getOwnPropertyNames;var k=Object.prototype.hasOwnProperty;var B=(c,t)=>{for(var e in t)d(c,e,{get:t[e],enumerable:!0})},S=(c,t,e,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of R(t))!k.call(c,n)&&n!==e&&d(c,n,{get:()=>t[n],enumerable:!(o=v(t,n))||o.enumerable});return c};var q=c=>S(d({},"__esModule",{value:!0}),c);var A={};B(A,{HOTP:()=>m,generate:()=>O,generateSync:()=>C,verify:()=>P,verifySync:()=>I,wrapResult:()=>f.wrapResult,wrapResultAsync:()=>f.wrapResultAsync});module.exports=q(A);var r=require("@otplib/core");var s=require("@otplib/core"),H=require("@otplib/uri");var m=class{options;guardrails;constructor(t={}){this.options=t,this.guardrails=(0,s.createGuardrails)(t.guardrails)}generateSecret(){let{crypto:t,base32:e}=this.options;return(0,s.requireCryptoPlugin)(t),(0,s.requireBase32Plugin)(e),(0,s.generateSecret)({crypto:t,base32:e})}async generate(t,e){let o={...this.options,...e},{secret:n,crypto:i,base32:a,algorithm:p="sha1",digits:u=6}=o;(0,s.requireSecret)(n),(0,s.requireCryptoPlugin)(i),(0,s.requireBase32Plugin)(a);let l=e?.guardrails??this.guardrails;return O({secret:n,counter:t,algorithm:p,digits:u,crypto:i,base32:a,guardrails:l})}async verify(t,e){let o={...this.options,...e},{secret:n,crypto:i,base32:a,algorithm:p="sha1",digits:u=6,counterTolerance:l=0}=o;(0,s.requireSecret)(n),(0,s.requireCryptoPlugin)(i),(0,s.requireBase32Plugin)(a);let y=e?.guardrails??this.guardrails;return P({secret:n,token:t.token,counter:t.counter,algorithm:p,digits:u,counterTolerance:l,crypto:i,base32:a,guardrails:y})}toURI(t=0){let{issuer:e,label:o,secret:n,algorithm:i="sha1",digits:a=6}=this.options;return(0,s.requireSecret)(n),(0,s.requireLabel)(o),(0,s.requireIssuer)(e),(0,s.requireBase32String)(n),(0,H.generateHOTP)({issuer:e,label:o,secret:n,algorithm:i,digits:a,counter:t})}};var f=require("@otplib/core");function b(c){let{secret:t,counter:e,algorithm:o="sha1",digits:n=6,crypto:i,base32:a,guardrails:p}=c;(0,r.requireSecret)(t),(0,r.requireCryptoPlugin)(i);let u=(0,r.normalizeSecret)(t,a);(0,r.validateSecret)(u,p),(0,r.validateCounter)(e,p);let l=(0,r.createCryptoContext)(i),y=(0,r.counterToBytes)(e);return{ctx:l,algorithm:o,digits:n,secretBytes:u,counterBytes:y}}async function O(c){let{ctx:t,algorithm:e,digits:o,secretBytes:n,counterBytes:i}=b(c),a=await t.hmac(e,n,i),p=(0,r.dynamicTruncate)(a);return(0,r.truncateDigits)(p,o)}function C(c){let{ctx:t,algorithm:e,digits:o,secretBytes:n,counterBytes:i}=b(c),a=t.hmacSync(e,n,i),p=(0,r.dynamicTruncate)(a);return(0,r.truncateDigits)(p,o)}function x(c){let{secret:t,counter:e,token:o,algorithm:n="sha1",digits:i=6,crypto:a,base32:p,counterTolerance:u=0,guardrails:l=(0,r.createGuardrails)()}=c;(0,r.requireSecret)(t),(0,r.requireCryptoPlugin)(a);let y=(0,r.normalizeSecret)(t,p);(0,r.validateSecret)(y,l),(0,r.validateCounter)(e,l),(0,r.validateToken)(o,i),(0,r.validateCounterTolerance)(u,l);let g=typeof e=="bigint"?Number(e):e,[T,h]=(0,r.normalizeCounterTolerance)(u),G=T+h+1;return{token:o,counterNum:g,past:T,future:h,totalChecks:G,crypto:a,getGenerateOptions:V=>({secret:y,counter:V,algorithm:n,digits:i,crypto:a,guardrails:l})}}async function P(c){let{token:t,counterNum:e,past:o,totalChecks:n,crypto:i,getGenerateOptions:a}=x(c),p=Math.max(0,o-e);for(let u=p;u<n;u++){let l=u-o,y=e+l,g=await O(a(y));if(i.constantTimeEqual(g,t))return{valid:!0,delta:l|0}}return{valid:!1}}function I(c){let{token:t,counterNum:e,past:o,totalChecks:n,crypto:i,getGenerateOptions:a}=x(c),p=Math.max(0,o-e);for(let u=p;u<n;u++){let l=u-o,y=e+l,g=C(a(y));if(i.constantTimeEqual(g,t))return{valid:!0,delta:l|0}}return{valid:!1}}0&&(module.exports={HOTP,generate,generateSync,verify,verifySync,wrapResult,wrapResultAsync});
2
2
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/class.ts"],"sourcesContent":["/**\n * @otplib/hotp\n *\n * RFC 4226 HOTP (HMAC-Based One-Time Password) implementation.\n *\n * @see {@link https://tools.ietf.org/html/rfc4226 | RFC 4226}\n */\n\nimport {\n counterToBytes,\n createCryptoContext,\n createGuardrails,\n dynamicTruncate,\n truncateDigits,\n validateCounter,\n validateSecret,\n validateToken,\n validateCounterTolerance,\n normalizeSecret,\n normalizeCounterTolerance,\n requireSecret,\n requireCryptoPlugin,\n} from \"@otplib/core\";\n\nimport type { HOTPGenerateOptions, HOTPVerifyOptions, VerifyResult } from \"./types\";\nimport type { CryptoContext } from \"@otplib/core\";\nimport type { Digits, HashAlgorithm, CryptoPlugin } from \"@otplib/core\";\n\n/**\n * Normalized options for HOTP generation\n * @internal\n */\ntype HOTPGenerateOptionsInternal = {\n ctx: CryptoContext;\n algorithm: HashAlgorithm;\n digits: Digits;\n secretBytes: Uint8Array;\n counterBytes: Uint8Array;\n};\n\n/**\n * Prepare and validate HOTP generation options\n *\n * Extracts defaults, normalizes the secret, validates parameters,\n * and creates the crypto context.\n *\n * @param options - HOTP generation options\n * @returns Normalized options with crypto context and counter bytes\n * @internal\n */\nfunction getHOTPGenerateOptions(options: HOTPGenerateOptions): HOTPGenerateOptionsInternal {\n const { secret, counter, algorithm = \"sha1\", digits = 6, crypto, base32, guardrails } = options;\n\n requireSecret(secret);\n requireCryptoPlugin(crypto);\n\n const secretBytes = normalizeSecret(secret, base32);\n validateSecret(secretBytes, guardrails);\n validateCounter(counter, guardrails);\n\n const ctx = createCryptoContext(crypto);\n const counterBytes = counterToBytes(counter);\n\n return { ctx, algorithm, digits, secretBytes, counterBytes };\n}\n\n/**\n * Generate an HMAC-based One-Time Password (HOTP)\n *\n * Implements the HOTP algorithm as specified in RFC 4226 Section 5.3:\n *\n * 1. Convert counter to 8-byte big-endian array (RFC 4226 Section 5.1)\n * 2. Compute HMAC-SHA-1 using the secret key and counter (RFC 4226 Section 5.2)\n * 3. Apply dynamic truncation to extract 4-byte code (RFC 4226 Section 5.3)\n * 4. Reduce modulo 10^digits to get final OTP (RFC 4226 Section 5.3)\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 options - HOTP generation options\n * @returns The HOTP code as a string\n *\n * @example\n * ```ts\n * import { generate } from '@otplib/hotp';\n * import { NodeCryptoPlugin } from '@otplib/plugin-crypto-node';\n *\n * const hotp = generate({\n * secret: new Uint8Array([1, 2, 3, 4, 5]),\n * counter: 0,\n * digits: 6,\n * crypto: new NodeCryptoPlugin(),\n * });\n * // Returns: '123456'\n * ```\n */\nexport async function generate(options: HOTPGenerateOptions): Promise<string> {\n const { ctx, algorithm, digits, secretBytes, counterBytes } = getHOTPGenerateOptions(options);\n const hmac = await ctx.hmac(algorithm, secretBytes, counterBytes);\n const dt = dynamicTruncate(hmac);\n\n return truncateDigits(dt, digits);\n}\n\n/**\n * Generate an HMAC-based One-Time Password (HOTP) synchronously\n *\n * This is the synchronous version of {@link generate}. It requires a crypto\n * plugin that supports synchronous HMAC operations (e.g., NodeCryptoPlugin\n * or NobleCryptoPlugin). Using this with WebCryptoPlugin will throw an error.\n *\n * @see {@link generate} for the async version\n * @see {@link https://tools.ietf.org/html/rfc4226#section-5.3 | RFC 4226 Section 5.3}\n *\n * @param options - HOTP generation options\n * @returns The HOTP code as a string\n * @throws {HMACError} If the crypto plugin doesn't support sync operations\n *\n * @example\n * ```ts\n * import { generateSync } from '@otplib/hotp';\n * import { NodeCryptoPlugin } from '@otplib/plugin-crypto-node';\n *\n * const hotp = generateSync({\n * secret: new Uint8Array([1, 2, 3, 4, 5]),\n * counter: 0,\n * digits: 6,\n * crypto: new NodeCryptoPlugin(),\n * });\n * // Returns: '123456'\n * ```\n */\nexport function generateSync(options: HOTPGenerateOptions): string {\n const { ctx, algorithm, digits, secretBytes, counterBytes } = getHOTPGenerateOptions(options);\n const hmac = ctx.hmacSync(algorithm, secretBytes, counterBytes);\n const dt = dynamicTruncate(hmac);\n\n return truncateDigits(dt, digits);\n}\n\n/**\n * Normalized options for HOTP verification\n * @internal\n */\ntype HOTPVerifyOptionsInternal = {\n token: string;\n counterNum: number;\n offsets: number[];\n crypto: CryptoPlugin;\n\n getGenerateOptions: (counter: number) => HOTPGenerateOptions;\n};\n\n/**\n * Prepare and validate HOTP verification options\n *\n * Extracts defaults, normalizes the secret, validates parameters,\n * and calculates the counter offsets based on tolerance.\n *\n * @param options - HOTP verification options\n * @returns Normalized options with calculated counter offsets\n * @internal\n */\nfunction getHOTPVerifyOptions(options: HOTPVerifyOptions): HOTPVerifyOptionsInternal {\n const {\n secret,\n counter,\n token,\n algorithm = \"sha1\",\n digits = 6,\n crypto,\n base32,\n counterTolerance = 0,\n guardrails = createGuardrails(),\n } = options;\n\n requireSecret(secret);\n requireCryptoPlugin(crypto);\n\n const secretBytes = normalizeSecret(secret, base32);\n validateSecret(secretBytes, guardrails);\n validateCounter(counter, guardrails);\n validateToken(token, digits);\n validateCounterTolerance(counterTolerance, guardrails);\n\n const counterNum = typeof counter === \"bigint\" ? Number(counter) : counter;\n // Pre-filter offsets that would result in invalid counters (e.g., negative values)\n const offsets = normalizeCounterTolerance(counterTolerance).filter(\n (offset) => counterNum + offset >= 0,\n );\n\n return {\n token,\n counterNum,\n offsets,\n crypto,\n getGenerateOptions: (cnt: number) => ({\n secret: secretBytes,\n counter: cnt,\n algorithm,\n digits,\n crypto,\n }),\n };\n}\n\n/**\n * Verify an HOTP code\n *\n * Compares the provided token against the expected HOTP value\n * using constant-time comparison to prevent timing attacks.\n *\n * @see {@link https://tools.ietf.org/html/rfc4226#section-7.2 | RFC 4226 Section 7.2 - Validation and Verification}\n * @see {@link https://tools.ietf.org/html/rfc4226#section-7.4 | RFC 4226 Section 7.4 - Resynchronization}\n *\n * ## Counter Resynchronization (RFC 4226 Section 7.4)\n *\n * When using a verification window, the `delta` value in the result indicates\n * how many counter steps ahead the token was found. After successful verification,\n * you should update the stored counter to prevent replay attacks:\n *\n * ```ts\n * const nextCounter = counter + result.delta + 1;\n * ```\n *\n * This ensures that the same token cannot be reused.\n *\n * @param options - HOTP verification options\n * @returns Verification result with validity and optional delta\n *\n * @example Basic verification\n * ```ts\n * import { verify } from '@otplib/hotp';\n * import { NodeCryptoPlugin } from '@otplib/plugin-crypto-node';\n *\n * const result = await verify({\n * secret: new Uint8Array([1, 2, 3, 4, 5]),\n * counter: 0,\n * token: '123456',\n * crypto: new NodeCryptoPlugin(),\n * });\n * // Returns: { valid: true, delta: 0 }\n * ```\n *\n * @example Counter resynchronization with counterTolerance\n * ```ts\n * // User's token was generated at counter 5, but server expects counter 3\n * const result = await verify({\n * secret,\n * counter: 3, // Server's stored counter\n * token: userToken,\n * counterTolerance: 5, // Allow up to 5 counters ahead\n * crypto: new NodeCryptoPlugin(),\n * });\n *\n * if (result.valid) {\n * // Token matched at counter 3 + delta\n * // Update stored counter to prevent replay attacks\n * const nextCounter = 3 + result.delta + 1; // = 6\n * await saveCounter(userId, nextCounter);\n * }\n * ```\n */\nexport async function verify(options: HOTPVerifyOptions): Promise<VerifyResult> {\n const { token, counterNum, offsets, crypto, getGenerateOptions } = getHOTPVerifyOptions(options);\n\n for (const offset of offsets) {\n const currentCounter = counterNum + offset;\n const expected = await generate(getGenerateOptions(currentCounter));\n if (crypto.constantTimeEqual(expected, token)) {\n return { valid: true, delta: offset };\n }\n }\n\n return { valid: false };\n}\n\n/**\n * Verify an HOTP code synchronously\n *\n * This is the synchronous version of {@link verify}. It requires a crypto\n * plugin that supports synchronous HMAC operations (e.g., NodeCryptoPlugin\n * or NobleCryptoPlugin). Using this with WebCryptoPlugin will throw an error.\n *\n * @see {@link verify} for the async version\n * @see {@link https://tools.ietf.org/html/rfc4226#section-7.2 | RFC 4226 Section 7.2}\n *\n * @param options - HOTP verification options\n * @returns Verification result with validity and optional delta\n * @throws {HMACError} If the crypto plugin doesn't support sync operations\n *\n * @example\n * ```ts\n * import { verifySync } from '@otplib/hotp';\n * import { NodeCryptoPlugin } from '@otplib/plugin-crypto-node';\n *\n * const result = verifySync({\n * secret: new Uint8Array([1, 2, 3, 4, 5]),\n * counter: 0,\n * token: '123456',\n * crypto: new NodeCryptoPlugin(),\n * });\n * // Returns: { valid: true, delta: 0 }\n * ```\n */\nexport function verifySync(options: HOTPVerifyOptions): VerifyResult {\n const { token, counterNum, offsets, crypto, getGenerateOptions } = getHOTPVerifyOptions(options);\n\n for (const offset of offsets) {\n const currentCounter = counterNum + offset;\n const expected = generateSync(getGenerateOptions(currentCounter));\n if (crypto.constantTimeEqual(expected, token)) {\n return { valid: true, delta: offset };\n }\n }\n\n return { valid: false };\n}\n\nexport type { CryptoPlugin, Digits, HashAlgorithm, OTPResult } from \"@otplib/core\";\nexport type {\n HOTPOptions,\n HOTPGenerateOptions,\n HOTPVerifyOptions,\n VerifyResult,\n VerifyResultValid,\n VerifyResultInvalid,\n} from \"./types\";\n\nexport { HOTP } from \"./class\";\n\n// Result wrapping utilities for users who want safe variants\nexport { wrapResult, wrapResultAsync } from \"@otplib/core\";\n","/**\n * @otplib/hotp\n *\n * HOTP class wrapper for convenient API\n */\n\nimport {\n generateSecret as generateSecretCore,\n requireCryptoPlugin,\n requireBase32Plugin,\n requireSecret,\n requireLabel,\n requireIssuer,\n requireBase32String,\n createGuardrails,\n} from \"@otplib/core\";\nimport { generateHOTP as generateHOTPURI } from \"@otplib/uri\";\n\nimport { generate as generateCode, verify as verifyCode } from \"./index\";\n\nimport type { VerifyResult, HOTPOptions } from \"./types\";\nimport type { OTPGuardrails } from \"@otplib/core\";\n\n/**\n * HOTP class for HMAC-based one-time password generation\n *\n * @example\n * ```typescript\n * import { HOTP } from '@otplib/hotp';\n * import { NodeCryptoPlugin } from '@otplib/plugin-crypto-node';\n * import { ScureBase32Plugin } from '@otplib/plugin-base32-scure';\n *\n * const hotp = new HOTP({\n * issuer: 'MyApp',\n * label: 'user@example.com',\n * counter: 0,\n * crypto: new NodeCryptoPlugin(),\n * base32: new ScureBase32Plugin(),\n * });\n *\n * const secret = hotp.generateSecret();\n * const token = await hotp.generate(0);\n * const isValid = await hotp.verify({ token, counter: 0 });\n * ```\n */\nexport class HOTP {\n private readonly options: HOTPOptions;\n private readonly guardrails: OTPGuardrails;\n\n constructor(options: HOTPOptions = {}) {\n this.options = options;\n this.guardrails = createGuardrails(options.guardrails);\n }\n\n /**\n * Generate a random Base32-encoded secret\n *\n * @returns Base32-encoded secret\n */\n generateSecret(): string {\n const { crypto, base32 } = this.options;\n\n requireCryptoPlugin(crypto);\n requireBase32Plugin(base32);\n\n return generateSecretCore({ crypto, base32 });\n }\n\n /**\n * Generate an HOTP code for a specific counter\n *\n * @param counter - The counter value\n * @param options - Optional overrides\n * @returns The HOTP code\n */\n async generate(counter: number, options?: Partial<HOTPOptions>): Promise<string> {\n const mergedOptions = { ...this.options, ...options };\n const { secret, crypto, base32, algorithm = \"sha1\", digits = 6 } = mergedOptions;\n\n requireSecret(secret);\n requireCryptoPlugin(crypto);\n requireBase32Plugin(base32);\n\n // Use class guardrails, or override if provided in options\n const guardrails = options?.guardrails ?? this.guardrails;\n\n return generateCode({\n secret,\n counter,\n algorithm,\n digits,\n crypto,\n base32,\n guardrails,\n });\n }\n\n /**\n * Verify an HOTP code\n *\n * @param params - Verification parameters\n * @param options - Optional verification options\n * @returns Verification result with validity and optional delta\n */\n async verify(\n params: { token: string; counter: number },\n options?: Partial<HOTPOptions & { counterTolerance?: number | number[] }>,\n ): Promise<VerifyResult> {\n const mergedOptions = { ...this.options, ...options };\n const {\n secret,\n crypto,\n base32,\n algorithm = \"sha1\",\n digits = 6,\n counterTolerance = 0,\n } = mergedOptions;\n\n requireSecret(secret);\n requireCryptoPlugin(crypto);\n requireBase32Plugin(base32);\n\n // Use class guardrails, or override if provided in options\n const guardrails = options?.guardrails ?? this.guardrails;\n\n return verifyCode({\n secret,\n token: params.token,\n counter: params.counter,\n algorithm,\n digits,\n counterTolerance,\n crypto,\n base32,\n guardrails,\n });\n }\n\n /**\n * Generate an otpauth:// URI for QR codes\n *\n * @param counter - The counter value\n * @returns The otpauth:// URI\n */\n toURI(counter: number = 0): string {\n const { issuer, label, secret, algorithm = \"sha1\", digits = 6 } = this.options;\n\n requireSecret(secret);\n requireLabel(label);\n requireIssuer(issuer);\n requireBase32String(secret);\n\n return generateHOTPURI({\n issuer,\n label,\n secret,\n algorithm,\n digits,\n counter,\n });\n }\n}\n"],"mappings":"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","names":["index_exports","__export","HOTP","generate","generateSync","verify","verifySync","__toCommonJS","import_core","import_core","import_uri","HOTP","options","crypto","base32","generateSecretCore","counter","mergedOptions","secret","algorithm","digits","guardrails","generate","params","counterTolerance","verify","issuer","label","generateHOTPURI","import_core","getHOTPGenerateOptions","options","secret","counter","algorithm","digits","crypto","base32","guardrails","secretBytes","ctx","counterBytes","generate","hmac","dt","generateSync","getHOTPVerifyOptions","token","counterTolerance","counterNum","offsets","offset","cnt","verify","getGenerateOptions","currentCounter","expected","verifySync"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/class.ts"],"sourcesContent":["/**\n * @otplib/hotp\n *\n * RFC 4226 HOTP (HMAC-Based One-Time Password) implementation.\n *\n * @see {@link https://tools.ietf.org/html/rfc4226 | RFC 4226}\n */\n\nimport {\n counterToBytes,\n createCryptoContext,\n createGuardrails,\n dynamicTruncate,\n truncateDigits,\n validateCounter,\n validateSecret,\n validateToken,\n validateCounterTolerance,\n normalizeSecret,\n normalizeCounterTolerance,\n requireSecret,\n requireCryptoPlugin,\n} from \"@otplib/core\";\n\nimport type { HOTPGenerateOptions, HOTPVerifyOptions, VerifyResult } from \"./types.js\";\nimport type { CryptoContext } from \"@otplib/core\";\nimport type { Digits, HashAlgorithm, CryptoPlugin } from \"@otplib/core\";\n\n/**\n * Normalized options for HOTP generation\n * @internal\n */\ntype HOTPGenerateOptionsInternal = {\n ctx: CryptoContext;\n algorithm: HashAlgorithm;\n digits: Digits;\n secretBytes: Uint8Array;\n counterBytes: Uint8Array;\n};\n\n/**\n * Prepare and validate HOTP generation options\n *\n * Extracts defaults, normalizes the secret, validates parameters,\n * and creates the crypto context.\n *\n * @param options - HOTP generation options\n * @returns Normalized options with crypto context and counter bytes\n * @internal\n */\nfunction getHOTPGenerateOptions(options: HOTPGenerateOptions): HOTPGenerateOptionsInternal {\n const { secret, counter, algorithm = \"sha1\", digits = 6, crypto, base32, guardrails } = options;\n\n requireSecret(secret);\n requireCryptoPlugin(crypto);\n\n const secretBytes = normalizeSecret(secret, base32);\n validateSecret(secretBytes, guardrails);\n validateCounter(counter, guardrails);\n\n const ctx = createCryptoContext(crypto);\n const counterBytes = counterToBytes(counter);\n\n return { ctx, algorithm, digits, secretBytes, counterBytes };\n}\n\n/**\n * Generate an HMAC-based One-Time Password (HOTP)\n *\n * Implements the HOTP algorithm as specified in RFC 4226 Section 5.3:\n *\n * 1. Convert counter to 8-byte big-endian array (RFC 4226 Section 5.1)\n * 2. Compute HMAC-SHA-1 using the secret key and counter (RFC 4226 Section 5.2)\n * 3. Apply dynamic truncation to extract 4-byte code (RFC 4226 Section 5.3)\n * 4. Reduce modulo 10^digits to get final OTP (RFC 4226 Section 5.3)\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 options - HOTP generation options\n * @returns The HOTP code as a string\n *\n * @example\n * ```ts\n * import { generate } from '@otplib/hotp';\n * import { NodeCryptoPlugin } from '@otplib/plugin-crypto-node';\n *\n * const hotp = generate({\n * secret: new Uint8Array([1, 2, 3, 4, 5]),\n * counter: 0,\n * digits: 6,\n * crypto: new NodeCryptoPlugin(),\n * });\n * // Returns: '123456'\n * ```\n */\nexport async function generate(options: HOTPGenerateOptions): Promise<string> {\n const { ctx, algorithm, digits, secretBytes, counterBytes } = getHOTPGenerateOptions(options);\n const hmac = await ctx.hmac(algorithm, secretBytes, counterBytes);\n const dt = dynamicTruncate(hmac);\n\n return truncateDigits(dt, digits);\n}\n\n/**\n * Generate an HMAC-based One-Time Password (HOTP) synchronously\n *\n * This is the synchronous version of {@link generate}. It requires a crypto\n * plugin that supports synchronous HMAC operations (e.g., NodeCryptoPlugin\n * or NobleCryptoPlugin). Using this with WebCryptoPlugin will throw an error.\n *\n * @see {@link generate} for the async version\n * @see {@link https://tools.ietf.org/html/rfc4226#section-5.3 | RFC 4226 Section 5.3}\n *\n * @param options - HOTP generation options\n * @returns The HOTP code as a string\n * @throws {HMACError} If the crypto plugin doesn't support sync operations\n *\n * @example\n * ```ts\n * import { generateSync } from '@otplib/hotp';\n * import { NodeCryptoPlugin } from '@otplib/plugin-crypto-node';\n *\n * const hotp = generateSync({\n * secret: new Uint8Array([1, 2, 3, 4, 5]),\n * counter: 0,\n * digits: 6,\n * crypto: new NodeCryptoPlugin(),\n * });\n * // Returns: '123456'\n * ```\n */\nexport function generateSync(options: HOTPGenerateOptions): string {\n const { ctx, algorithm, digits, secretBytes, counterBytes } = getHOTPGenerateOptions(options);\n const hmac = ctx.hmacSync(algorithm, secretBytes, counterBytes);\n const dt = dynamicTruncate(hmac);\n\n return truncateDigits(dt, digits);\n}\n\n/**\n * Normalized options for HOTP verification\n * @internal\n */\ntype HOTPVerifyOptionsInternal = {\n token: string;\n counterNum: number;\n past: number;\n future: number;\n totalChecks: number;\n crypto: CryptoPlugin;\n\n getGenerateOptions: (counter: number) => HOTPGenerateOptions;\n};\n\n/**\n * Prepare and validate HOTP verification options\n *\n * Extracts defaults, normalizes the secret, validates parameters,\n * and calculates the counter offsets based on tolerance.\n *\n * @param options - HOTP verification options\n * @returns Normalized options with calculated counter offsets\n * @internal\n */\nfunction getHOTPVerifyOptions(options: HOTPVerifyOptions): HOTPVerifyOptionsInternal {\n const {\n secret,\n counter,\n token,\n algorithm = \"sha1\",\n digits = 6,\n crypto,\n base32,\n counterTolerance = 0,\n guardrails = createGuardrails(),\n } = options;\n\n requireSecret(secret);\n requireCryptoPlugin(crypto);\n\n const secretBytes = normalizeSecret(secret, base32);\n validateSecret(secretBytes, guardrails);\n validateCounter(counter, guardrails);\n validateToken(token, digits);\n validateCounterTolerance(counterTolerance, guardrails);\n\n const counterNum = typeof counter === \"bigint\" ? Number(counter) : counter;\n const [past, future] = normalizeCounterTolerance(counterTolerance);\n const totalChecks = past + future + 1;\n\n return {\n token,\n counterNum,\n past,\n future,\n totalChecks,\n crypto,\n getGenerateOptions: (cnt: number) => ({\n secret: secretBytes,\n counter: cnt,\n algorithm,\n digits,\n crypto,\n guardrails,\n }),\n };\n}\n\n/**\n * Verify an HOTP code\n *\n * Compares the provided token against the expected HOTP value\n * using constant-time comparison to prevent timing attacks.\n *\n * @see {@link https://tools.ietf.org/html/rfc4226#section-7.2 | RFC 4226 Section 7.2 - Validation and Verification}\n * @see {@link https://tools.ietf.org/html/rfc4226#section-7.4 | RFC 4226 Section 7.4 - Resynchronization}\n *\n * ## Counter Resynchronization (RFC 4226 Section 7.4)\n *\n * When using a verification window, the `delta` value in the result indicates\n * how many counter steps ahead the token was found. After successful verification,\n * you should update the stored counter to prevent replay attacks:\n *\n * ```ts\n * const nextCounter = counter + result.delta + 1;\n * ```\n *\n * This ensures that the same token cannot be reused.\n *\n * @param options - HOTP verification options\n * @returns Verification result with validity and optional delta\n *\n * @example Basic verification\n * ```ts\n * import { verify } from '@otplib/hotp';\n * import { NodeCryptoPlugin } from '@otplib/plugin-crypto-node';\n *\n * const result = await verify({\n * secret: new Uint8Array([1, 2, 3, 4, 5]),\n * counter: 0,\n * token: '123456',\n * crypto: new NodeCryptoPlugin(),\n * });\n * // Returns: { valid: true, delta: 0 }\n * ```\n *\n * @example Counter resynchronization with counterTolerance\n * ```ts\n * // User's token was generated at counter 5, but server expects counter 3\n * const result = await verify({\n * secret,\n * counter: 3, // Server's stored counter\n * token: userToken,\n * counterTolerance: 5, // Allow up to 5 counters ahead\n * crypto: new NodeCryptoPlugin(),\n * });\n *\n * if (result.valid) {\n * // Token matched at counter 3 + delta\n * // Update stored counter to prevent replay attacks\n * const nextCounter = 3 + result.delta + 1; // = 6\n * await saveCounter(userId, nextCounter);\n * }\n * ```\n */\nexport async function verify(options: HOTPVerifyOptions): Promise<VerifyResult> {\n const { token, counterNum, past, totalChecks, crypto, getGenerateOptions } =\n getHOTPVerifyOptions(options);\n\n // Optimization: Skip iterations that would produce negative counters\n // If counterNum=2 and past=5: startI = 3 (skip first 3 iterations)\n // If counterNum=10 and past=5: startI = 0 (no skip needed)\n const startI = Math.max(0, past - counterNum);\n\n // Use positive loop index to avoid -0 edge cases and negative loop variables\n // Map index [startI...totalChecks-1] to offset [startI-past...future]\n for (let i = startI; i < totalChecks; i++) {\n const offset = i - past;\n const currentCounter = counterNum + offset;\n // currentCounter is guaranteed >= 0 due to startI optimization\n\n const expected = await generate(getGenerateOptions(currentCounter));\n if (crypto.constantTimeEqual(expected, token)) {\n return { valid: true, delta: offset | 0 }; // Bitwise OR converts -0 to +0\n }\n }\n\n return { valid: false };\n}\n\n/**\n * Verify an HOTP code synchronously\n *\n * This is the synchronous version of {@link verify}. It requires a crypto\n * plugin that supports synchronous HMAC operations (e.g., NodeCryptoPlugin\n * or NobleCryptoPlugin). Using this with WebCryptoPlugin will throw an error.\n *\n * @see {@link verify} for the async version\n * @see {@link https://tools.ietf.org/html/rfc4226#section-7.2 | RFC 4226 Section 7.2}\n *\n * @param options - HOTP verification options\n * @returns Verification result with validity and optional delta\n * @throws {HMACError} If the crypto plugin doesn't support sync operations\n *\n * @example\n * ```ts\n * import { verifySync } from '@otplib/hotp';\n * import { NodeCryptoPlugin } from '@otplib/plugin-crypto-node';\n *\n * const result = verifySync({\n * secret: new Uint8Array([1, 2, 3, 4, 5]),\n * counter: 0,\n * token: '123456',\n * crypto: new NodeCryptoPlugin(),\n * });\n * // Returns: { valid: true, delta: 0 }\n * ```\n */\nexport function verifySync(options: HOTPVerifyOptions): VerifyResult {\n const { token, counterNum, past, totalChecks, crypto, getGenerateOptions } =\n getHOTPVerifyOptions(options);\n\n // Optimization: Skip iterations that would produce negative counters\n // If counterNum=2 and past=5: startI = 3 (skip first 3 iterations)\n // If counterNum=10 and past=5: startI = 0 (no skip needed)\n const startI = Math.max(0, past - counterNum);\n\n // Use positive loop index to avoid -0 edge cases and negative loop variables\n // Map index [startI...totalChecks-1] to offset [startI-past...future]\n for (let i = startI; i < totalChecks; i++) {\n const offset = i - past;\n const currentCounter = counterNum + offset;\n // currentCounter is guaranteed >= 0 due to startI optimization\n\n const expected = generateSync(getGenerateOptions(currentCounter));\n if (crypto.constantTimeEqual(expected, token)) {\n return { valid: true, delta: offset | 0 }; // Bitwise OR converts -0 to +0\n }\n }\n\n return { valid: false };\n}\n\nexport type { CryptoPlugin, Digits, HashAlgorithm, OTPResult } from \"@otplib/core\";\nexport type {\n HOTPOptions,\n HOTPGenerateOptions,\n HOTPVerifyOptions,\n VerifyResult,\n VerifyResultValid,\n VerifyResultInvalid,\n} from \"./types.js\";\n\nexport { HOTP } from \"./class.js\";\n\n// Result wrapping utilities for users who want safe variants\nexport { wrapResult, wrapResultAsync } from \"@otplib/core\";\n","/**\n * @otplib/hotp\n *\n * HOTP class wrapper for convenient API\n */\n\nimport {\n generateSecret as generateSecretCore,\n requireCryptoPlugin,\n requireBase32Plugin,\n requireSecret,\n requireLabel,\n requireIssuer,\n requireBase32String,\n createGuardrails,\n} from \"@otplib/core\";\nimport { generateHOTP as generateHOTPURI } from \"@otplib/uri\";\n\nimport { generate as generateCode, verify as verifyCode } from \"./index.js\";\n\nimport type { VerifyResult, HOTPOptions } from \"./types.js\";\nimport type { OTPGuardrails } from \"@otplib/core\";\n\n/**\n * HOTP class for HMAC-based one-time password generation\n *\n * @example\n * ```typescript\n * import { HOTP } from '@otplib/hotp';\n * import { NodeCryptoPlugin } from '@otplib/plugin-crypto-node';\n * import { ScureBase32Plugin } from '@otplib/plugin-base32-scure';\n *\n * const hotp = new HOTP({\n * issuer: 'MyApp',\n * label: 'user@example.com',\n * counter: 0,\n * crypto: new NodeCryptoPlugin(),\n * base32: new ScureBase32Plugin(),\n * });\n *\n * const secret = hotp.generateSecret();\n * const token = await hotp.generate(0);\n * const isValid = await hotp.verify({ token, counter: 0 });\n * ```\n */\nexport class HOTP {\n private readonly options: HOTPOptions;\n private readonly guardrails: OTPGuardrails;\n\n constructor(options: HOTPOptions = {}) {\n this.options = options;\n this.guardrails = createGuardrails(options.guardrails);\n }\n\n /**\n * Generate a random Base32-encoded secret\n *\n * @returns Base32-encoded secret\n */\n generateSecret(): string {\n const { crypto, base32 } = this.options;\n\n requireCryptoPlugin(crypto);\n requireBase32Plugin(base32);\n\n return generateSecretCore({ crypto, base32 });\n }\n\n /**\n * Generate an HOTP code for a specific counter\n *\n * @param counter - The counter value\n * @param options - Optional overrides\n * @returns The HOTP code\n */\n async generate(counter: number, options?: Partial<HOTPOptions>): Promise<string> {\n const mergedOptions = { ...this.options, ...options };\n const { secret, crypto, base32, algorithm = \"sha1\", digits = 6 } = mergedOptions;\n\n requireSecret(secret);\n requireCryptoPlugin(crypto);\n requireBase32Plugin(base32);\n\n // Use class guardrails, or override if provided in options\n const guardrails = options?.guardrails ?? this.guardrails;\n\n return generateCode({\n secret,\n counter,\n algorithm,\n digits,\n crypto,\n base32,\n guardrails,\n });\n }\n\n /**\n * Verify an HOTP code\n *\n * @param params - Verification parameters\n * @param options - Optional verification options\n * @returns Verification result with validity and optional delta\n */\n async verify(\n params: { token: string; counter: number },\n options?: Partial<HOTPOptions & { counterTolerance?: number | [number, number] }>,\n ): Promise<VerifyResult> {\n const mergedOptions = { ...this.options, ...options };\n const {\n secret,\n crypto,\n base32,\n algorithm = \"sha1\",\n digits = 6,\n counterTolerance = 0,\n } = mergedOptions;\n\n requireSecret(secret);\n requireCryptoPlugin(crypto);\n requireBase32Plugin(base32);\n\n // Use class guardrails, or override if provided in options\n const guardrails = options?.guardrails ?? this.guardrails;\n\n return verifyCode({\n secret,\n token: params.token,\n counter: params.counter,\n algorithm,\n digits,\n counterTolerance,\n crypto,\n base32,\n guardrails,\n });\n }\n\n /**\n * Generate an otpauth:// URI for QR codes\n *\n * @param counter - The counter value\n * @returns The otpauth:// URI\n */\n toURI(counter: number = 0): string {\n const { issuer, label, secret, algorithm = \"sha1\", digits = 6 } = this.options;\n\n requireSecret(secret);\n requireLabel(label);\n requireIssuer(issuer);\n requireBase32String(secret);\n\n return generateHOTPURI({\n issuer,\n label,\n secret,\n algorithm,\n digits,\n counter,\n });\n }\n}\n"],"mappings":"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","names":["index_exports","__export","HOTP","generate","generateSync","verify","verifySync","__toCommonJS","import_core","import_core","import_uri","HOTP","options","crypto","base32","generateSecretCore","counter","mergedOptions","secret","algorithm","digits","guardrails","generate","params","counterTolerance","verify","issuer","label","generateHOTPURI","import_core","getHOTPGenerateOptions","options","secret","counter","algorithm","digits","crypto","base32","guardrails","secretBytes","ctx","counterBytes","generate","hmac","dt","generateSync","getHOTPVerifyOptions","token","counterTolerance","counterNum","past","future","totalChecks","cnt","verify","getGenerateOptions","startI","i","offset","currentCounter","expected","verifySync"]}
package/dist/index.d.cts CHANGED
@@ -59,11 +59,11 @@ type HOTPVerifyOptions = HOTPGenerateOptions & {
59
59
  readonly token: string;
60
60
  /**
61
61
  * Counter tolerance for verification (default: 0)
62
- * - Number: symmetric look-ahead window [counter, counter + counterTolerance]
63
- * - Array: asymmetric window, where positive values are look-ahead and negative values are look-back
64
- * Examples: [0, 1] allows counter and counter+1; [-1, 0, 1] allows counter-1, counter, counter+1
62
+ * - Number: creates look-ahead only tolerance [0, n] (secure default per RFC 4226)
63
+ * - Tuple [past, future]: explicit window control
64
+ * Examples: [0, 5] allows 5 future counters; [5, 5] allows ±5 symmetric
65
65
  */
66
- readonly counterTolerance?: number | number[];
66
+ readonly counterTolerance?: number | [number, number];
67
67
  };
68
68
  /**
69
69
  * Successful verification result with delta offset
@@ -159,7 +159,7 @@ declare class HOTP {
159
159
  token: string;
160
160
  counter: number;
161
161
  }, options?: Partial<HOTPOptions & {
162
- counterTolerance?: number | number[];
162
+ counterTolerance?: number | [number, number];
163
163
  }>): Promise<VerifyResult>;
164
164
  /**
165
165
  * Generate an otpauth:// URI for QR codes
package/dist/index.d.ts CHANGED
@@ -59,11 +59,11 @@ type HOTPVerifyOptions = HOTPGenerateOptions & {
59
59
  readonly token: string;
60
60
  /**
61
61
  * Counter tolerance for verification (default: 0)
62
- * - Number: symmetric look-ahead window [counter, counter + counterTolerance]
63
- * - Array: asymmetric window, where positive values are look-ahead and negative values are look-back
64
- * Examples: [0, 1] allows counter and counter+1; [-1, 0, 1] allows counter-1, counter, counter+1
62
+ * - Number: creates look-ahead only tolerance [0, n] (secure default per RFC 4226)
63
+ * - Tuple [past, future]: explicit window control
64
+ * Examples: [0, 5] allows 5 future counters; [5, 5] allows ±5 symmetric
65
65
  */
66
- readonly counterTolerance?: number | number[];
66
+ readonly counterTolerance?: number | [number, number];
67
67
  };
68
68
  /**
69
69
  * Successful verification result with delta offset
@@ -159,7 +159,7 @@ declare class HOTP {
159
159
  token: string;
160
160
  counter: number;
161
161
  }, options?: Partial<HOTPOptions & {
162
- counterTolerance?: number | number[];
162
+ counterTolerance?: number | [number, number];
163
163
  }>): Promise<VerifyResult>;
164
164
  /**
165
165
  * Generate an otpauth:// URI for QR codes
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import{counterToBytes as N,createCryptoContext as w,createGuardrails as D,dynamicTruncate as T,truncateDigits as h,validateCounter as H,validateSecret as b,validateToken as U,validateCounterTolerance as z,normalizeSecret as C,normalizeCounterTolerance as E,requireSecret as x,requireCryptoPlugin as G}from"@otplib/core";import{generateSecret as B,requireCryptoPlugin as y,requireBase32Plugin as g,requireSecret as f,requireLabel as S,requireIssuer as q,requireBase32String as k,createGuardrails as I}from"@otplib/core";import{generateHOTP as A}from"@otplib/uri";var m=class{options;guardrails;constructor(t={}){this.options=t,this.guardrails=I(t.guardrails)}generateSecret(){let{crypto:t,base32:e}=this.options;return y(t),g(e),B({crypto:t,base32:e})}async generate(t,e){let s={...this.options,...e},{secret:n,crypto:o,base32:r,algorithm:i="sha1",digits:a=6}=s;f(n),y(o),g(r);let u=e?.guardrails??this.guardrails;return O({secret:n,counter:t,algorithm:i,digits:a,crypto:o,base32:r,guardrails:u})}async verify(t,e){let s={...this.options,...e},{secret:n,crypto:o,base32:r,algorithm:i="sha1",digits:a=6,counterTolerance:u=0}=s;f(n),y(o),g(r);let p=e?.guardrails??this.guardrails;return P({secret:n,token:t.token,counter:t.counter,algorithm:i,digits:a,counterTolerance:u,crypto:o,base32:r,guardrails:p})}toURI(t=0){let{issuer:e,label:s,secret:n,algorithm:o="sha1",digits:r=6}=this.options;return f(n),S(s),q(e),k(n),A({issuer:e,label:s,secret:n,algorithm:o,digits:r,counter:t})}};import{wrapResult as Z,wrapResultAsync as _}from"@otplib/core";function V(c){let{secret:t,counter:e,algorithm:s="sha1",digits:n=6,crypto:o,base32:r,guardrails:i}=c;x(t),G(o);let a=C(t,r);b(a,i),H(e,i);let u=w(o),p=N(e);return{ctx:u,algorithm:s,digits:n,secretBytes:a,counterBytes:p}}async function O(c){let{ctx:t,algorithm:e,digits:s,secretBytes:n,counterBytes:o}=V(c),r=await t.hmac(e,n,o),i=T(r);return h(i,s)}function L(c){let{ctx:t,algorithm:e,digits:s,secretBytes:n,counterBytes:o}=V(c),r=t.hmacSync(e,n,o),i=T(r);return h(i,s)}function v(c){let{secret:t,counter:e,token:s,algorithm:n="sha1",digits:o=6,crypto:r,base32:i,counterTolerance:a=0,guardrails:u=D()}=c;x(t),G(r);let p=C(t,i);b(p,u),H(e,u),U(s,o),z(a,u);let d=typeof e=="bigint"?Number(e):e,R=E(a).filter(l=>d+l>=0);return{token:s,counterNum:d,offsets:R,crypto:r,getGenerateOptions:l=>({secret:p,counter:l,algorithm:n,digits:o,crypto:r})}}async function P(c){let{token:t,counterNum:e,offsets:s,crypto:n,getGenerateOptions:o}=v(c);for(let r of s){let i=e+r,a=await O(o(i));if(n.constantTimeEqual(a,t))return{valid:!0,delta:r}}return{valid:!1}}function Q(c){let{token:t,counterNum:e,offsets:s,crypto:n,getGenerateOptions:o}=v(c);for(let r of s){let i=e+r,a=L(o(i));if(n.constantTimeEqual(a,t))return{valid:!0,delta:r}}return{valid:!1}}export{m as HOTP,O as generate,L as generateSync,P as verify,Q as verifySync,Z as wrapResult,_ as wrapResultAsync};
1
+ import{counterToBytes as D,createCryptoContext as U,createGuardrails as j,dynamicTruncate as h,truncateDigits as H,validateCounter as b,validateSecret as C,validateToken as z,validateCounterTolerance as E,normalizeSecret as x,normalizeCounterTolerance as M,requireSecret as G,requireCryptoPlugin as V}from"@otplib/core";import{generateSecret as S,requireCryptoPlugin as y,requireBase32Plugin as g,requireSecret as m,requireLabel as q,requireIssuer as I,requireBase32String as A,createGuardrails as N}from"@otplib/core";import{generateHOTP as w}from"@otplib/uri";var O=class{options;guardrails;constructor(e={}){this.options=e,this.guardrails=N(e.guardrails)}generateSecret(){let{crypto:e,base32:t}=this.options;return y(e),g(t),S({crypto:e,base32:t})}async generate(e,t){let r={...this.options,...t},{secret:n,crypto:o,base32:s,algorithm:a="sha1",digits:i=6}=r;m(n),y(o),g(s);let c=t?.guardrails??this.guardrails;return f({secret:n,counter:e,algorithm:a,digits:i,crypto:o,base32:s,guardrails:c})}async verify(e,t){let r={...this.options,...t},{secret:n,crypto:o,base32:s,algorithm:a="sha1",digits:i=6,counterTolerance:c=0}=r;m(n),y(o),g(s);let p=t?.guardrails??this.guardrails;return T({secret:n,token:e.token,counter:e.counter,algorithm:a,digits:i,counterTolerance:c,crypto:o,base32:s,guardrails:p})}toURI(e=0){let{issuer:t,label:r,secret:n,algorithm:o="sha1",digits:s=6}=this.options;return m(n),q(r),I(t),A(n),w({issuer:t,label:r,secret:n,algorithm:o,digits:s,counter:e})}};import{wrapResult as $,wrapResultAsync as tt}from"@otplib/core";function v(u){let{secret:e,counter:t,algorithm:r="sha1",digits:n=6,crypto:o,base32:s,guardrails:a}=u;G(e),V(o);let i=x(e,s);C(i,a),b(t,a);let c=U(o),p=D(t);return{ctx:c,algorithm:r,digits:n,secretBytes:i,counterBytes:p}}async function f(u){let{ctx:e,algorithm:t,digits:r,secretBytes:n,counterBytes:o}=v(u),s=await e.hmac(t,n,o),a=h(s);return H(a,r)}function L(u){let{ctx:e,algorithm:t,digits:r,secretBytes:n,counterBytes:o}=v(u),s=e.hmacSync(t,n,o),a=h(s);return H(a,r)}function R(u){let{secret:e,counter:t,token:r,algorithm:n="sha1",digits:o=6,crypto:s,base32:a,counterTolerance:i=0,guardrails:c=j()}=u;G(e),V(s);let p=x(e,a);C(p,c),b(t,c),z(r,o),E(i,c);let l=typeof t=="bigint"?Number(t):t,[d,P]=M(i),k=d+P+1;return{token:r,counterNum:l,past:d,future:P,totalChecks:k,crypto:s,getGenerateOptions:B=>({secret:p,counter:B,algorithm:n,digits:o,crypto:s,guardrails:c})}}async function T(u){let{token:e,counterNum:t,past:r,totalChecks:n,crypto:o,getGenerateOptions:s}=R(u),a=Math.max(0,r-t);for(let i=a;i<n;i++){let c=i-r,p=t+c,l=await f(s(p));if(o.constantTimeEqual(l,e))return{valid:!0,delta:c|0}}return{valid:!1}}function X(u){let{token:e,counterNum:t,past:r,totalChecks:n,crypto:o,getGenerateOptions:s}=R(u),a=Math.max(0,r-t);for(let i=a;i<n;i++){let c=i-r,p=t+c,l=L(s(p));if(o.constantTimeEqual(l,e))return{valid:!0,delta:c|0}}return{valid:!1}}export{O as HOTP,f as generate,L as generateSync,T as verify,X as verifySync,$ as wrapResult,tt as wrapResultAsync};
2
2
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/class.ts"],"sourcesContent":["/**\n * @otplib/hotp\n *\n * RFC 4226 HOTP (HMAC-Based One-Time Password) implementation.\n *\n * @see {@link https://tools.ietf.org/html/rfc4226 | RFC 4226}\n */\n\nimport {\n counterToBytes,\n createCryptoContext,\n createGuardrails,\n dynamicTruncate,\n truncateDigits,\n validateCounter,\n validateSecret,\n validateToken,\n validateCounterTolerance,\n normalizeSecret,\n normalizeCounterTolerance,\n requireSecret,\n requireCryptoPlugin,\n} from \"@otplib/core\";\n\nimport type { HOTPGenerateOptions, HOTPVerifyOptions, VerifyResult } from \"./types\";\nimport type { CryptoContext } from \"@otplib/core\";\nimport type { Digits, HashAlgorithm, CryptoPlugin } from \"@otplib/core\";\n\n/**\n * Normalized options for HOTP generation\n * @internal\n */\ntype HOTPGenerateOptionsInternal = {\n ctx: CryptoContext;\n algorithm: HashAlgorithm;\n digits: Digits;\n secretBytes: Uint8Array;\n counterBytes: Uint8Array;\n};\n\n/**\n * Prepare and validate HOTP generation options\n *\n * Extracts defaults, normalizes the secret, validates parameters,\n * and creates the crypto context.\n *\n * @param options - HOTP generation options\n * @returns Normalized options with crypto context and counter bytes\n * @internal\n */\nfunction getHOTPGenerateOptions(options: HOTPGenerateOptions): HOTPGenerateOptionsInternal {\n const { secret, counter, algorithm = \"sha1\", digits = 6, crypto, base32, guardrails } = options;\n\n requireSecret(secret);\n requireCryptoPlugin(crypto);\n\n const secretBytes = normalizeSecret(secret, base32);\n validateSecret(secretBytes, guardrails);\n validateCounter(counter, guardrails);\n\n const ctx = createCryptoContext(crypto);\n const counterBytes = counterToBytes(counter);\n\n return { ctx, algorithm, digits, secretBytes, counterBytes };\n}\n\n/**\n * Generate an HMAC-based One-Time Password (HOTP)\n *\n * Implements the HOTP algorithm as specified in RFC 4226 Section 5.3:\n *\n * 1. Convert counter to 8-byte big-endian array (RFC 4226 Section 5.1)\n * 2. Compute HMAC-SHA-1 using the secret key and counter (RFC 4226 Section 5.2)\n * 3. Apply dynamic truncation to extract 4-byte code (RFC 4226 Section 5.3)\n * 4. Reduce modulo 10^digits to get final OTP (RFC 4226 Section 5.3)\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 options - HOTP generation options\n * @returns The HOTP code as a string\n *\n * @example\n * ```ts\n * import { generate } from '@otplib/hotp';\n * import { NodeCryptoPlugin } from '@otplib/plugin-crypto-node';\n *\n * const hotp = generate({\n * secret: new Uint8Array([1, 2, 3, 4, 5]),\n * counter: 0,\n * digits: 6,\n * crypto: new NodeCryptoPlugin(),\n * });\n * // Returns: '123456'\n * ```\n */\nexport async function generate(options: HOTPGenerateOptions): Promise<string> {\n const { ctx, algorithm, digits, secretBytes, counterBytes } = getHOTPGenerateOptions(options);\n const hmac = await ctx.hmac(algorithm, secretBytes, counterBytes);\n const dt = dynamicTruncate(hmac);\n\n return truncateDigits(dt, digits);\n}\n\n/**\n * Generate an HMAC-based One-Time Password (HOTP) synchronously\n *\n * This is the synchronous version of {@link generate}. It requires a crypto\n * plugin that supports synchronous HMAC operations (e.g., NodeCryptoPlugin\n * or NobleCryptoPlugin). Using this with WebCryptoPlugin will throw an error.\n *\n * @see {@link generate} for the async version\n * @see {@link https://tools.ietf.org/html/rfc4226#section-5.3 | RFC 4226 Section 5.3}\n *\n * @param options - HOTP generation options\n * @returns The HOTP code as a string\n * @throws {HMACError} If the crypto plugin doesn't support sync operations\n *\n * @example\n * ```ts\n * import { generateSync } from '@otplib/hotp';\n * import { NodeCryptoPlugin } from '@otplib/plugin-crypto-node';\n *\n * const hotp = generateSync({\n * secret: new Uint8Array([1, 2, 3, 4, 5]),\n * counter: 0,\n * digits: 6,\n * crypto: new NodeCryptoPlugin(),\n * });\n * // Returns: '123456'\n * ```\n */\nexport function generateSync(options: HOTPGenerateOptions): string {\n const { ctx, algorithm, digits, secretBytes, counterBytes } = getHOTPGenerateOptions(options);\n const hmac = ctx.hmacSync(algorithm, secretBytes, counterBytes);\n const dt = dynamicTruncate(hmac);\n\n return truncateDigits(dt, digits);\n}\n\n/**\n * Normalized options for HOTP verification\n * @internal\n */\ntype HOTPVerifyOptionsInternal = {\n token: string;\n counterNum: number;\n offsets: number[];\n crypto: CryptoPlugin;\n\n getGenerateOptions: (counter: number) => HOTPGenerateOptions;\n};\n\n/**\n * Prepare and validate HOTP verification options\n *\n * Extracts defaults, normalizes the secret, validates parameters,\n * and calculates the counter offsets based on tolerance.\n *\n * @param options - HOTP verification options\n * @returns Normalized options with calculated counter offsets\n * @internal\n */\nfunction getHOTPVerifyOptions(options: HOTPVerifyOptions): HOTPVerifyOptionsInternal {\n const {\n secret,\n counter,\n token,\n algorithm = \"sha1\",\n digits = 6,\n crypto,\n base32,\n counterTolerance = 0,\n guardrails = createGuardrails(),\n } = options;\n\n requireSecret(secret);\n requireCryptoPlugin(crypto);\n\n const secretBytes = normalizeSecret(secret, base32);\n validateSecret(secretBytes, guardrails);\n validateCounter(counter, guardrails);\n validateToken(token, digits);\n validateCounterTolerance(counterTolerance, guardrails);\n\n const counterNum = typeof counter === \"bigint\" ? Number(counter) : counter;\n // Pre-filter offsets that would result in invalid counters (e.g., negative values)\n const offsets = normalizeCounterTolerance(counterTolerance).filter(\n (offset) => counterNum + offset >= 0,\n );\n\n return {\n token,\n counterNum,\n offsets,\n crypto,\n getGenerateOptions: (cnt: number) => ({\n secret: secretBytes,\n counter: cnt,\n algorithm,\n digits,\n crypto,\n }),\n };\n}\n\n/**\n * Verify an HOTP code\n *\n * Compares the provided token against the expected HOTP value\n * using constant-time comparison to prevent timing attacks.\n *\n * @see {@link https://tools.ietf.org/html/rfc4226#section-7.2 | RFC 4226 Section 7.2 - Validation and Verification}\n * @see {@link https://tools.ietf.org/html/rfc4226#section-7.4 | RFC 4226 Section 7.4 - Resynchronization}\n *\n * ## Counter Resynchronization (RFC 4226 Section 7.4)\n *\n * When using a verification window, the `delta` value in the result indicates\n * how many counter steps ahead the token was found. After successful verification,\n * you should update the stored counter to prevent replay attacks:\n *\n * ```ts\n * const nextCounter = counter + result.delta + 1;\n * ```\n *\n * This ensures that the same token cannot be reused.\n *\n * @param options - HOTP verification options\n * @returns Verification result with validity and optional delta\n *\n * @example Basic verification\n * ```ts\n * import { verify } from '@otplib/hotp';\n * import { NodeCryptoPlugin } from '@otplib/plugin-crypto-node';\n *\n * const result = await verify({\n * secret: new Uint8Array([1, 2, 3, 4, 5]),\n * counter: 0,\n * token: '123456',\n * crypto: new NodeCryptoPlugin(),\n * });\n * // Returns: { valid: true, delta: 0 }\n * ```\n *\n * @example Counter resynchronization with counterTolerance\n * ```ts\n * // User's token was generated at counter 5, but server expects counter 3\n * const result = await verify({\n * secret,\n * counter: 3, // Server's stored counter\n * token: userToken,\n * counterTolerance: 5, // Allow up to 5 counters ahead\n * crypto: new NodeCryptoPlugin(),\n * });\n *\n * if (result.valid) {\n * // Token matched at counter 3 + delta\n * // Update stored counter to prevent replay attacks\n * const nextCounter = 3 + result.delta + 1; // = 6\n * await saveCounter(userId, nextCounter);\n * }\n * ```\n */\nexport async function verify(options: HOTPVerifyOptions): Promise<VerifyResult> {\n const { token, counterNum, offsets, crypto, getGenerateOptions } = getHOTPVerifyOptions(options);\n\n for (const offset of offsets) {\n const currentCounter = counterNum + offset;\n const expected = await generate(getGenerateOptions(currentCounter));\n if (crypto.constantTimeEqual(expected, token)) {\n return { valid: true, delta: offset };\n }\n }\n\n return { valid: false };\n}\n\n/**\n * Verify an HOTP code synchronously\n *\n * This is the synchronous version of {@link verify}. It requires a crypto\n * plugin that supports synchronous HMAC operations (e.g., NodeCryptoPlugin\n * or NobleCryptoPlugin). Using this with WebCryptoPlugin will throw an error.\n *\n * @see {@link verify} for the async version\n * @see {@link https://tools.ietf.org/html/rfc4226#section-7.2 | RFC 4226 Section 7.2}\n *\n * @param options - HOTP verification options\n * @returns Verification result with validity and optional delta\n * @throws {HMACError} If the crypto plugin doesn't support sync operations\n *\n * @example\n * ```ts\n * import { verifySync } from '@otplib/hotp';\n * import { NodeCryptoPlugin } from '@otplib/plugin-crypto-node';\n *\n * const result = verifySync({\n * secret: new Uint8Array([1, 2, 3, 4, 5]),\n * counter: 0,\n * token: '123456',\n * crypto: new NodeCryptoPlugin(),\n * });\n * // Returns: { valid: true, delta: 0 }\n * ```\n */\nexport function verifySync(options: HOTPVerifyOptions): VerifyResult {\n const { token, counterNum, offsets, crypto, getGenerateOptions } = getHOTPVerifyOptions(options);\n\n for (const offset of offsets) {\n const currentCounter = counterNum + offset;\n const expected = generateSync(getGenerateOptions(currentCounter));\n if (crypto.constantTimeEqual(expected, token)) {\n return { valid: true, delta: offset };\n }\n }\n\n return { valid: false };\n}\n\nexport type { CryptoPlugin, Digits, HashAlgorithm, OTPResult } from \"@otplib/core\";\nexport type {\n HOTPOptions,\n HOTPGenerateOptions,\n HOTPVerifyOptions,\n VerifyResult,\n VerifyResultValid,\n VerifyResultInvalid,\n} from \"./types\";\n\nexport { HOTP } from \"./class\";\n\n// Result wrapping utilities for users who want safe variants\nexport { wrapResult, wrapResultAsync } from \"@otplib/core\";\n","/**\n * @otplib/hotp\n *\n * HOTP class wrapper for convenient API\n */\n\nimport {\n generateSecret as generateSecretCore,\n requireCryptoPlugin,\n requireBase32Plugin,\n requireSecret,\n requireLabel,\n requireIssuer,\n requireBase32String,\n createGuardrails,\n} from \"@otplib/core\";\nimport { generateHOTP as generateHOTPURI } from \"@otplib/uri\";\n\nimport { generate as generateCode, verify as verifyCode } from \"./index\";\n\nimport type { VerifyResult, HOTPOptions } from \"./types\";\nimport type { OTPGuardrails } from \"@otplib/core\";\n\n/**\n * HOTP class for HMAC-based one-time password generation\n *\n * @example\n * ```typescript\n * import { HOTP } from '@otplib/hotp';\n * import { NodeCryptoPlugin } from '@otplib/plugin-crypto-node';\n * import { ScureBase32Plugin } from '@otplib/plugin-base32-scure';\n *\n * const hotp = new HOTP({\n * issuer: 'MyApp',\n * label: 'user@example.com',\n * counter: 0,\n * crypto: new NodeCryptoPlugin(),\n * base32: new ScureBase32Plugin(),\n * });\n *\n * const secret = hotp.generateSecret();\n * const token = await hotp.generate(0);\n * const isValid = await hotp.verify({ token, counter: 0 });\n * ```\n */\nexport class HOTP {\n private readonly options: HOTPOptions;\n private readonly guardrails: OTPGuardrails;\n\n constructor(options: HOTPOptions = {}) {\n this.options = options;\n this.guardrails = createGuardrails(options.guardrails);\n }\n\n /**\n * Generate a random Base32-encoded secret\n *\n * @returns Base32-encoded secret\n */\n generateSecret(): string {\n const { crypto, base32 } = this.options;\n\n requireCryptoPlugin(crypto);\n requireBase32Plugin(base32);\n\n return generateSecretCore({ crypto, base32 });\n }\n\n /**\n * Generate an HOTP code for a specific counter\n *\n * @param counter - The counter value\n * @param options - Optional overrides\n * @returns The HOTP code\n */\n async generate(counter: number, options?: Partial<HOTPOptions>): Promise<string> {\n const mergedOptions = { ...this.options, ...options };\n const { secret, crypto, base32, algorithm = \"sha1\", digits = 6 } = mergedOptions;\n\n requireSecret(secret);\n requireCryptoPlugin(crypto);\n requireBase32Plugin(base32);\n\n // Use class guardrails, or override if provided in options\n const guardrails = options?.guardrails ?? this.guardrails;\n\n return generateCode({\n secret,\n counter,\n algorithm,\n digits,\n crypto,\n base32,\n guardrails,\n });\n }\n\n /**\n * Verify an HOTP code\n *\n * @param params - Verification parameters\n * @param options - Optional verification options\n * @returns Verification result with validity and optional delta\n */\n async verify(\n params: { token: string; counter: number },\n options?: Partial<HOTPOptions & { counterTolerance?: number | number[] }>,\n ): Promise<VerifyResult> {\n const mergedOptions = { ...this.options, ...options };\n const {\n secret,\n crypto,\n base32,\n algorithm = \"sha1\",\n digits = 6,\n counterTolerance = 0,\n } = mergedOptions;\n\n requireSecret(secret);\n requireCryptoPlugin(crypto);\n requireBase32Plugin(base32);\n\n // Use class guardrails, or override if provided in options\n const guardrails = options?.guardrails ?? this.guardrails;\n\n return verifyCode({\n secret,\n token: params.token,\n counter: params.counter,\n algorithm,\n digits,\n counterTolerance,\n crypto,\n base32,\n guardrails,\n });\n }\n\n /**\n * Generate an otpauth:// URI for QR codes\n *\n * @param counter - The counter value\n * @returns The otpauth:// URI\n */\n toURI(counter: number = 0): string {\n const { issuer, label, secret, algorithm = \"sha1\", digits = 6 } = this.options;\n\n requireSecret(secret);\n requireLabel(label);\n requireIssuer(issuer);\n requireBase32String(secret);\n\n return generateHOTPURI({\n issuer,\n label,\n secret,\n algorithm,\n digits,\n counter,\n });\n }\n}\n"],"mappings":"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","names":["counterToBytes","createCryptoContext","createGuardrails","dynamicTruncate","truncateDigits","validateCounter","validateSecret","validateToken","validateCounterTolerance","normalizeSecret","normalizeCounterTolerance","requireSecret","requireCryptoPlugin","generateSecretCore","requireCryptoPlugin","requireBase32Plugin","requireSecret","requireLabel","requireIssuer","requireBase32String","createGuardrails","generateHOTPURI","HOTP","options","createGuardrails","crypto","base32","requireCryptoPlugin","requireBase32Plugin","generateSecretCore","counter","mergedOptions","secret","algorithm","digits","requireSecret","guardrails","generate","params","counterTolerance","verify","issuer","label","requireLabel","requireIssuer","requireBase32String","generateHOTPURI","wrapResult","wrapResultAsync","getHOTPGenerateOptions","options","secret","counter","algorithm","digits","crypto","base32","guardrails","requireSecret","requireCryptoPlugin","secretBytes","normalizeSecret","validateSecret","validateCounter","ctx","createCryptoContext","counterBytes","counterToBytes","generate","hmac","dt","dynamicTruncate","truncateDigits","generateSync","getHOTPVerifyOptions","token","counterTolerance","createGuardrails","validateToken","validateCounterTolerance","counterNum","offsets","normalizeCounterTolerance","offset","cnt","verify","getGenerateOptions","currentCounter","expected","verifySync"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/class.ts"],"sourcesContent":["/**\n * @otplib/hotp\n *\n * RFC 4226 HOTP (HMAC-Based One-Time Password) implementation.\n *\n * @see {@link https://tools.ietf.org/html/rfc4226 | RFC 4226}\n */\n\nimport {\n counterToBytes,\n createCryptoContext,\n createGuardrails,\n dynamicTruncate,\n truncateDigits,\n validateCounter,\n validateSecret,\n validateToken,\n validateCounterTolerance,\n normalizeSecret,\n normalizeCounterTolerance,\n requireSecret,\n requireCryptoPlugin,\n} from \"@otplib/core\";\n\nimport type { HOTPGenerateOptions, HOTPVerifyOptions, VerifyResult } from \"./types.js\";\nimport type { CryptoContext } from \"@otplib/core\";\nimport type { Digits, HashAlgorithm, CryptoPlugin } from \"@otplib/core\";\n\n/**\n * Normalized options for HOTP generation\n * @internal\n */\ntype HOTPGenerateOptionsInternal = {\n ctx: CryptoContext;\n algorithm: HashAlgorithm;\n digits: Digits;\n secretBytes: Uint8Array;\n counterBytes: Uint8Array;\n};\n\n/**\n * Prepare and validate HOTP generation options\n *\n * Extracts defaults, normalizes the secret, validates parameters,\n * and creates the crypto context.\n *\n * @param options - HOTP generation options\n * @returns Normalized options with crypto context and counter bytes\n * @internal\n */\nfunction getHOTPGenerateOptions(options: HOTPGenerateOptions): HOTPGenerateOptionsInternal {\n const { secret, counter, algorithm = \"sha1\", digits = 6, crypto, base32, guardrails } = options;\n\n requireSecret(secret);\n requireCryptoPlugin(crypto);\n\n const secretBytes = normalizeSecret(secret, base32);\n validateSecret(secretBytes, guardrails);\n validateCounter(counter, guardrails);\n\n const ctx = createCryptoContext(crypto);\n const counterBytes = counterToBytes(counter);\n\n return { ctx, algorithm, digits, secretBytes, counterBytes };\n}\n\n/**\n * Generate an HMAC-based One-Time Password (HOTP)\n *\n * Implements the HOTP algorithm as specified in RFC 4226 Section 5.3:\n *\n * 1. Convert counter to 8-byte big-endian array (RFC 4226 Section 5.1)\n * 2. Compute HMAC-SHA-1 using the secret key and counter (RFC 4226 Section 5.2)\n * 3. Apply dynamic truncation to extract 4-byte code (RFC 4226 Section 5.3)\n * 4. Reduce modulo 10^digits to get final OTP (RFC 4226 Section 5.3)\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 options - HOTP generation options\n * @returns The HOTP code as a string\n *\n * @example\n * ```ts\n * import { generate } from '@otplib/hotp';\n * import { NodeCryptoPlugin } from '@otplib/plugin-crypto-node';\n *\n * const hotp = generate({\n * secret: new Uint8Array([1, 2, 3, 4, 5]),\n * counter: 0,\n * digits: 6,\n * crypto: new NodeCryptoPlugin(),\n * });\n * // Returns: '123456'\n * ```\n */\nexport async function generate(options: HOTPGenerateOptions): Promise<string> {\n const { ctx, algorithm, digits, secretBytes, counterBytes } = getHOTPGenerateOptions(options);\n const hmac = await ctx.hmac(algorithm, secretBytes, counterBytes);\n const dt = dynamicTruncate(hmac);\n\n return truncateDigits(dt, digits);\n}\n\n/**\n * Generate an HMAC-based One-Time Password (HOTP) synchronously\n *\n * This is the synchronous version of {@link generate}. It requires a crypto\n * plugin that supports synchronous HMAC operations (e.g., NodeCryptoPlugin\n * or NobleCryptoPlugin). Using this with WebCryptoPlugin will throw an error.\n *\n * @see {@link generate} for the async version\n * @see {@link https://tools.ietf.org/html/rfc4226#section-5.3 | RFC 4226 Section 5.3}\n *\n * @param options - HOTP generation options\n * @returns The HOTP code as a string\n * @throws {HMACError} If the crypto plugin doesn't support sync operations\n *\n * @example\n * ```ts\n * import { generateSync } from '@otplib/hotp';\n * import { NodeCryptoPlugin } from '@otplib/plugin-crypto-node';\n *\n * const hotp = generateSync({\n * secret: new Uint8Array([1, 2, 3, 4, 5]),\n * counter: 0,\n * digits: 6,\n * crypto: new NodeCryptoPlugin(),\n * });\n * // Returns: '123456'\n * ```\n */\nexport function generateSync(options: HOTPGenerateOptions): string {\n const { ctx, algorithm, digits, secretBytes, counterBytes } = getHOTPGenerateOptions(options);\n const hmac = ctx.hmacSync(algorithm, secretBytes, counterBytes);\n const dt = dynamicTruncate(hmac);\n\n return truncateDigits(dt, digits);\n}\n\n/**\n * Normalized options for HOTP verification\n * @internal\n */\ntype HOTPVerifyOptionsInternal = {\n token: string;\n counterNum: number;\n past: number;\n future: number;\n totalChecks: number;\n crypto: CryptoPlugin;\n\n getGenerateOptions: (counter: number) => HOTPGenerateOptions;\n};\n\n/**\n * Prepare and validate HOTP verification options\n *\n * Extracts defaults, normalizes the secret, validates parameters,\n * and calculates the counter offsets based on tolerance.\n *\n * @param options - HOTP verification options\n * @returns Normalized options with calculated counter offsets\n * @internal\n */\nfunction getHOTPVerifyOptions(options: HOTPVerifyOptions): HOTPVerifyOptionsInternal {\n const {\n secret,\n counter,\n token,\n algorithm = \"sha1\",\n digits = 6,\n crypto,\n base32,\n counterTolerance = 0,\n guardrails = createGuardrails(),\n } = options;\n\n requireSecret(secret);\n requireCryptoPlugin(crypto);\n\n const secretBytes = normalizeSecret(secret, base32);\n validateSecret(secretBytes, guardrails);\n validateCounter(counter, guardrails);\n validateToken(token, digits);\n validateCounterTolerance(counterTolerance, guardrails);\n\n const counterNum = typeof counter === \"bigint\" ? Number(counter) : counter;\n const [past, future] = normalizeCounterTolerance(counterTolerance);\n const totalChecks = past + future + 1;\n\n return {\n token,\n counterNum,\n past,\n future,\n totalChecks,\n crypto,\n getGenerateOptions: (cnt: number) => ({\n secret: secretBytes,\n counter: cnt,\n algorithm,\n digits,\n crypto,\n guardrails,\n }),\n };\n}\n\n/**\n * Verify an HOTP code\n *\n * Compares the provided token against the expected HOTP value\n * using constant-time comparison to prevent timing attacks.\n *\n * @see {@link https://tools.ietf.org/html/rfc4226#section-7.2 | RFC 4226 Section 7.2 - Validation and Verification}\n * @see {@link https://tools.ietf.org/html/rfc4226#section-7.4 | RFC 4226 Section 7.4 - Resynchronization}\n *\n * ## Counter Resynchronization (RFC 4226 Section 7.4)\n *\n * When using a verification window, the `delta` value in the result indicates\n * how many counter steps ahead the token was found. After successful verification,\n * you should update the stored counter to prevent replay attacks:\n *\n * ```ts\n * const nextCounter = counter + result.delta + 1;\n * ```\n *\n * This ensures that the same token cannot be reused.\n *\n * @param options - HOTP verification options\n * @returns Verification result with validity and optional delta\n *\n * @example Basic verification\n * ```ts\n * import { verify } from '@otplib/hotp';\n * import { NodeCryptoPlugin } from '@otplib/plugin-crypto-node';\n *\n * const result = await verify({\n * secret: new Uint8Array([1, 2, 3, 4, 5]),\n * counter: 0,\n * token: '123456',\n * crypto: new NodeCryptoPlugin(),\n * });\n * // Returns: { valid: true, delta: 0 }\n * ```\n *\n * @example Counter resynchronization with counterTolerance\n * ```ts\n * // User's token was generated at counter 5, but server expects counter 3\n * const result = await verify({\n * secret,\n * counter: 3, // Server's stored counter\n * token: userToken,\n * counterTolerance: 5, // Allow up to 5 counters ahead\n * crypto: new NodeCryptoPlugin(),\n * });\n *\n * if (result.valid) {\n * // Token matched at counter 3 + delta\n * // Update stored counter to prevent replay attacks\n * const nextCounter = 3 + result.delta + 1; // = 6\n * await saveCounter(userId, nextCounter);\n * }\n * ```\n */\nexport async function verify(options: HOTPVerifyOptions): Promise<VerifyResult> {\n const { token, counterNum, past, totalChecks, crypto, getGenerateOptions } =\n getHOTPVerifyOptions(options);\n\n // Optimization: Skip iterations that would produce negative counters\n // If counterNum=2 and past=5: startI = 3 (skip first 3 iterations)\n // If counterNum=10 and past=5: startI = 0 (no skip needed)\n const startI = Math.max(0, past - counterNum);\n\n // Use positive loop index to avoid -0 edge cases and negative loop variables\n // Map index [startI...totalChecks-1] to offset [startI-past...future]\n for (let i = startI; i < totalChecks; i++) {\n const offset = i - past;\n const currentCounter = counterNum + offset;\n // currentCounter is guaranteed >= 0 due to startI optimization\n\n const expected = await generate(getGenerateOptions(currentCounter));\n if (crypto.constantTimeEqual(expected, token)) {\n return { valid: true, delta: offset | 0 }; // Bitwise OR converts -0 to +0\n }\n }\n\n return { valid: false };\n}\n\n/**\n * Verify an HOTP code synchronously\n *\n * This is the synchronous version of {@link verify}. It requires a crypto\n * plugin that supports synchronous HMAC operations (e.g., NodeCryptoPlugin\n * or NobleCryptoPlugin). Using this with WebCryptoPlugin will throw an error.\n *\n * @see {@link verify} for the async version\n * @see {@link https://tools.ietf.org/html/rfc4226#section-7.2 | RFC 4226 Section 7.2}\n *\n * @param options - HOTP verification options\n * @returns Verification result with validity and optional delta\n * @throws {HMACError} If the crypto plugin doesn't support sync operations\n *\n * @example\n * ```ts\n * import { verifySync } from '@otplib/hotp';\n * import { NodeCryptoPlugin } from '@otplib/plugin-crypto-node';\n *\n * const result = verifySync({\n * secret: new Uint8Array([1, 2, 3, 4, 5]),\n * counter: 0,\n * token: '123456',\n * crypto: new NodeCryptoPlugin(),\n * });\n * // Returns: { valid: true, delta: 0 }\n * ```\n */\nexport function verifySync(options: HOTPVerifyOptions): VerifyResult {\n const { token, counterNum, past, totalChecks, crypto, getGenerateOptions } =\n getHOTPVerifyOptions(options);\n\n // Optimization: Skip iterations that would produce negative counters\n // If counterNum=2 and past=5: startI = 3 (skip first 3 iterations)\n // If counterNum=10 and past=5: startI = 0 (no skip needed)\n const startI = Math.max(0, past - counterNum);\n\n // Use positive loop index to avoid -0 edge cases and negative loop variables\n // Map index [startI...totalChecks-1] to offset [startI-past...future]\n for (let i = startI; i < totalChecks; i++) {\n const offset = i - past;\n const currentCounter = counterNum + offset;\n // currentCounter is guaranteed >= 0 due to startI optimization\n\n const expected = generateSync(getGenerateOptions(currentCounter));\n if (crypto.constantTimeEqual(expected, token)) {\n return { valid: true, delta: offset | 0 }; // Bitwise OR converts -0 to +0\n }\n }\n\n return { valid: false };\n}\n\nexport type { CryptoPlugin, Digits, HashAlgorithm, OTPResult } from \"@otplib/core\";\nexport type {\n HOTPOptions,\n HOTPGenerateOptions,\n HOTPVerifyOptions,\n VerifyResult,\n VerifyResultValid,\n VerifyResultInvalid,\n} from \"./types.js\";\n\nexport { HOTP } from \"./class.js\";\n\n// Result wrapping utilities for users who want safe variants\nexport { wrapResult, wrapResultAsync } from \"@otplib/core\";\n","/**\n * @otplib/hotp\n *\n * HOTP class wrapper for convenient API\n */\n\nimport {\n generateSecret as generateSecretCore,\n requireCryptoPlugin,\n requireBase32Plugin,\n requireSecret,\n requireLabel,\n requireIssuer,\n requireBase32String,\n createGuardrails,\n} from \"@otplib/core\";\nimport { generateHOTP as generateHOTPURI } from \"@otplib/uri\";\n\nimport { generate as generateCode, verify as verifyCode } from \"./index.js\";\n\nimport type { VerifyResult, HOTPOptions } from \"./types.js\";\nimport type { OTPGuardrails } from \"@otplib/core\";\n\n/**\n * HOTP class for HMAC-based one-time password generation\n *\n * @example\n * ```typescript\n * import { HOTP } from '@otplib/hotp';\n * import { NodeCryptoPlugin } from '@otplib/plugin-crypto-node';\n * import { ScureBase32Plugin } from '@otplib/plugin-base32-scure';\n *\n * const hotp = new HOTP({\n * issuer: 'MyApp',\n * label: 'user@example.com',\n * counter: 0,\n * crypto: new NodeCryptoPlugin(),\n * base32: new ScureBase32Plugin(),\n * });\n *\n * const secret = hotp.generateSecret();\n * const token = await hotp.generate(0);\n * const isValid = await hotp.verify({ token, counter: 0 });\n * ```\n */\nexport class HOTP {\n private readonly options: HOTPOptions;\n private readonly guardrails: OTPGuardrails;\n\n constructor(options: HOTPOptions = {}) {\n this.options = options;\n this.guardrails = createGuardrails(options.guardrails);\n }\n\n /**\n * Generate a random Base32-encoded secret\n *\n * @returns Base32-encoded secret\n */\n generateSecret(): string {\n const { crypto, base32 } = this.options;\n\n requireCryptoPlugin(crypto);\n requireBase32Plugin(base32);\n\n return generateSecretCore({ crypto, base32 });\n }\n\n /**\n * Generate an HOTP code for a specific counter\n *\n * @param counter - The counter value\n * @param options - Optional overrides\n * @returns The HOTP code\n */\n async generate(counter: number, options?: Partial<HOTPOptions>): Promise<string> {\n const mergedOptions = { ...this.options, ...options };\n const { secret, crypto, base32, algorithm = \"sha1\", digits = 6 } = mergedOptions;\n\n requireSecret(secret);\n requireCryptoPlugin(crypto);\n requireBase32Plugin(base32);\n\n // Use class guardrails, or override if provided in options\n const guardrails = options?.guardrails ?? this.guardrails;\n\n return generateCode({\n secret,\n counter,\n algorithm,\n digits,\n crypto,\n base32,\n guardrails,\n });\n }\n\n /**\n * Verify an HOTP code\n *\n * @param params - Verification parameters\n * @param options - Optional verification options\n * @returns Verification result with validity and optional delta\n */\n async verify(\n params: { token: string; counter: number },\n options?: Partial<HOTPOptions & { counterTolerance?: number | [number, number] }>,\n ): Promise<VerifyResult> {\n const mergedOptions = { ...this.options, ...options };\n const {\n secret,\n crypto,\n base32,\n algorithm = \"sha1\",\n digits = 6,\n counterTolerance = 0,\n } = mergedOptions;\n\n requireSecret(secret);\n requireCryptoPlugin(crypto);\n requireBase32Plugin(base32);\n\n // Use class guardrails, or override if provided in options\n const guardrails = options?.guardrails ?? this.guardrails;\n\n return verifyCode({\n secret,\n token: params.token,\n counter: params.counter,\n algorithm,\n digits,\n counterTolerance,\n crypto,\n base32,\n guardrails,\n });\n }\n\n /**\n * Generate an otpauth:// URI for QR codes\n *\n * @param counter - The counter value\n * @returns The otpauth:// URI\n */\n toURI(counter: number = 0): string {\n const { issuer, label, secret, algorithm = \"sha1\", digits = 6 } = this.options;\n\n requireSecret(secret);\n requireLabel(label);\n requireIssuer(issuer);\n requireBase32String(secret);\n\n return generateHOTPURI({\n issuer,\n label,\n secret,\n algorithm,\n digits,\n counter,\n });\n }\n}\n"],"mappings":"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","names":["counterToBytes","createCryptoContext","createGuardrails","dynamicTruncate","truncateDigits","validateCounter","validateSecret","validateToken","validateCounterTolerance","normalizeSecret","normalizeCounterTolerance","requireSecret","requireCryptoPlugin","generateSecretCore","requireCryptoPlugin","requireBase32Plugin","requireSecret","requireLabel","requireIssuer","requireBase32String","createGuardrails","generateHOTPURI","HOTP","options","createGuardrails","crypto","base32","requireCryptoPlugin","requireBase32Plugin","generateSecretCore","counter","mergedOptions","secret","algorithm","digits","requireSecret","guardrails","generate","params","counterTolerance","verify","issuer","label","requireLabel","requireIssuer","requireBase32String","generateHOTPURI","wrapResult","wrapResultAsync","getHOTPGenerateOptions","options","secret","counter","algorithm","digits","crypto","base32","guardrails","requireSecret","requireCryptoPlugin","secretBytes","normalizeSecret","validateSecret","validateCounter","ctx","createCryptoContext","counterBytes","counterToBytes","generate","hmac","dt","dynamicTruncate","truncateDigits","generateSync","getHOTPVerifyOptions","token","counterTolerance","createGuardrails","validateToken","validateCounterTolerance","counterNum","past","future","normalizeCounterTolerance","totalChecks","cnt","verify","getGenerateOptions","startI","offset","currentCounter","expected","verifySync"]}
@@ -1 +1 @@
1
- {"inputs":{"src/class.ts":{"bytes":3972,"imports":[{"path":"@otplib/core","kind":"import-statement","external":true},{"path":"@otplib/uri","kind":"import-statement","external":true},{"path":"src/index.ts","kind":"import-statement","original":"./index"}],"format":"esm"},"src/index.ts":{"bytes":10051,"imports":[{"path":"@otplib/core","kind":"import-statement","external":true},{"path":"src/class.ts","kind":"import-statement","original":"./class"},{"path":"@otplib/core","kind":"import-statement","external":true}],"format":"esm"}},"outputs":{"dist/index.cjs.map":{"imports":[],"exports":[],"inputs":{},"bytes":19011},"dist/index.cjs":{"imports":[{"path":"@otplib/core","kind":"require-call","external":true},{"path":"@otplib/core","kind":"require-call","external":true},{"path":"@otplib/uri","kind":"require-call","external":true},{"path":"@otplib/core","kind":"require-call","external":true}],"exports":[],"entryPoint":"src/index.ts","inputs":{"src/index.ts":{"bytesInOutput":1890},"src/class.ts":{"bytesInOutput":1282}},"bytes":3692}}}
1
+ {"inputs":{"src/class.ts":{"bytes":3986,"imports":[{"path":"@otplib/core","kind":"import-statement","external":true},{"path":"@otplib/uri","kind":"import-statement","external":true},{"path":"src/index.ts","kind":"import-statement","original":"./index.js"}],"format":"esm"},"src/index.ts":{"bytes":11184,"imports":[{"path":"@otplib/core","kind":"import-statement","external":true},{"path":"src/class.ts","kind":"import-statement","original":"./class.js"},{"path":"@otplib/core","kind":"import-statement","external":true}],"format":"esm"}},"outputs":{"dist/index.cjs.map":{"imports":[],"exports":[],"inputs":{},"bytes":20447},"dist/index.cjs":{"imports":[{"path":"@otplib/core","kind":"require-call","external":true},{"path":"@otplib/core","kind":"require-call","external":true},{"path":"@otplib/uri","kind":"require-call","external":true},{"path":"@otplib/core","kind":"require-call","external":true}],"exports":[],"entryPoint":"src/index.ts","inputs":{"src/index.ts":{"bytesInOutput":2001},"src/class.ts":{"bytesInOutput":1282}},"bytes":3803}}}
@@ -1 +1 @@
1
- {"inputs":{"src/class.ts":{"bytes":3972,"imports":[{"path":"@otplib/core","kind":"import-statement","external":true},{"path":"@otplib/uri","kind":"import-statement","external":true},{"path":"src/index.ts","kind":"import-statement","original":"./index"}],"format":"esm"},"src/index.ts":{"bytes":10051,"imports":[{"path":"@otplib/core","kind":"import-statement","external":true},{"path":"src/class.ts","kind":"import-statement","original":"./class"},{"path":"@otplib/core","kind":"import-statement","external":true}],"format":"esm"}},"outputs":{"dist/index.js.map":{"imports":[],"exports":[],"inputs":{},"bytes":19931},"dist/index.js":{"imports":[{"path":"@otplib/core","kind":"import-statement","external":true},{"path":"@otplib/core","kind":"import-statement","external":true},{"path":"@otplib/uri","kind":"import-statement","external":true},{"path":"@otplib/core","kind":"import-statement","external":true}],"exports":["HOTP","generate","generateSync","verify","verifySync","wrapResult","wrapResultAsync"],"entryPoint":"src/index.ts","inputs":{"src/index.ts":{"bytesInOutput":1617},"src/class.ts":{"bytesInOutput":1157}},"bytes":2890}}}
1
+ {"inputs":{"src/class.ts":{"bytes":3986,"imports":[{"path":"@otplib/core","kind":"import-statement","external":true},{"path":"@otplib/uri","kind":"import-statement","external":true},{"path":"src/index.ts","kind":"import-statement","original":"./index.js"}],"format":"esm"},"src/index.ts":{"bytes":11184,"imports":[{"path":"@otplib/core","kind":"import-statement","external":true},{"path":"src/class.ts","kind":"import-statement","original":"./class.js"},{"path":"@otplib/core","kind":"import-statement","external":true}],"format":"esm"}},"outputs":{"dist/index.js.map":{"imports":[],"exports":[],"inputs":{},"bytes":21353},"dist/index.js":{"imports":[{"path":"@otplib/core","kind":"import-statement","external":true},{"path":"@otplib/core","kind":"import-statement","external":true},{"path":"@otplib/uri","kind":"import-statement","external":true},{"path":"@otplib/core","kind":"import-statement","external":true}],"exports":["HOTP","generate","generateSync","verify","verifySync","wrapResult","wrapResultAsync"],"entryPoint":"src/index.ts","inputs":{"src/index.ts":{"bytesInOutput":1729},"src/class.ts":{"bytesInOutput":1157}},"bytes":3003}}}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@otplib/hotp",
3
- "version": "13.1.0",
3
+ "version": "13.2.0",
4
4
  "description": "RFC 4226 HOTP implementation for otplib",
5
5
  "license": "MIT",
6
6
  "author": "Gerald Yeo <support@yeojz.dev>",
@@ -45,15 +45,16 @@
45
45
  "LICENSE"
46
46
  ],
47
47
  "dependencies": {
48
- "@otplib/core": "13.1.0",
49
- "@otplib/uri": "13.1.0"
48
+ "@otplib/core": "13.2.0",
49
+ "@otplib/uri": "13.2.0"
50
50
  },
51
51
  "devDependencies": {
52
52
  "tsup": "^8.0.1",
53
53
  "typescript": "^5.3.3",
54
- "vitest": "^4.0.16",
54
+ "vitest": "^4.0.18",
55
55
  "@repo/testing": "13.0.1",
56
- "@otplib/plugin-crypto-node": "13.1.0"
56
+ "@otplib/plugin-base32-scure": "13.2.0",
57
+ "@otplib/plugin-crypto-node": "13.2.0"
57
58
  },
58
59
  "publishConfig": {
59
60
  "access": "public"