@otplib/hotp 13.0.2 → 13.1.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/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/metafile-cjs.json +1 -1
- package/dist/metafile-esm.json +1 -1
- package/package.json +5 -5
package/dist/index.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";var
|
|
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});
|
|
2
2
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.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 dynamicTruncate,\n truncateDigits,\n validateCounter,\n validateSecret,\n validateToken,\n validateCounterTolerance,\n normalizeSecret,\n normalizeCounterTolerance,\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 } = options;\n\n const secretBytes = normalizeSecret(secret, base32);\n validateSecret(secretBytes);\n validateCounter(counter);\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 } = options;\n\n const secretBytes = normalizeSecret(secret, base32);\n validateSecret(secretBytes);\n validateCounter(counter);\n validateToken(token, digits);\n validateCounterTolerance(counterTolerance);\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} 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\";\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\n constructor(options: HOTPOptions = {}) {\n this.options = options;\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\n const { secret, crypto, base32, algorithm = \"sha1\", digits = 6 } = mergedOptions;\n\n requireSecret(secret);\n requireCryptoPlugin(crypto);\n requireBase32Plugin(base32);\n\n return generateCode({\n secret,\n counter,\n algorithm,\n digits,\n crypto,\n base32,\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\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 return verifyCode({\n secret,\n token: params.token,\n counter: params.counter,\n algorithm,\n digits,\n counterTolerance,\n crypto,\n base32,\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","generate","params","counterTolerance","verify","issuer","label","generateHOTPURI","import_core","getHOTPGenerateOptions","options","secret","counter","algorithm","digits","crypto","base32","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\";\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"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { HashAlgorithm, Digits, CryptoPlugin, Base32Plugin } from '@otplib/core';
|
|
1
|
+
import { HashAlgorithm, Digits, CryptoPlugin, Base32Plugin, OTPGuardrails } from '@otplib/core';
|
|
2
2
|
export { CryptoPlugin, Digits, HashAlgorithm, OTPResult, wrapResult, wrapResultAsync } from '@otplib/core';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -31,11 +31,18 @@ type HOTPOptions = {
|
|
|
31
31
|
readonly issuer?: string;
|
|
32
32
|
/** User identifier/email/username (for URI generation) */
|
|
33
33
|
readonly label?: string;
|
|
34
|
+
/**
|
|
35
|
+
* Custom guardrails to override validation limits
|
|
36
|
+
* Must be created via createGuardrails() factory
|
|
37
|
+
* Use this carefully - see danger-zone documentation
|
|
38
|
+
*/
|
|
39
|
+
readonly guardrails?: OTPGuardrails;
|
|
34
40
|
};
|
|
35
41
|
/**
|
|
36
42
|
* Required options for HOTP generation
|
|
37
43
|
*
|
|
38
44
|
* Requires `secret`, `counter`, and `crypto` for OTP generation.
|
|
45
|
+
* Optional `guardrails` must be created via createGuardrails() factory.
|
|
39
46
|
*/
|
|
40
47
|
type HOTPGenerateOptions = HOTPOptions & {
|
|
41
48
|
readonly secret: string | Uint8Array;
|
|
@@ -125,6 +132,7 @@ type VerifyResult = VerifyResultValid | VerifyResultInvalid;
|
|
|
125
132
|
*/
|
|
126
133
|
declare class HOTP {
|
|
127
134
|
private readonly options;
|
|
135
|
+
private readonly guardrails;
|
|
128
136
|
constructor(options?: HOTPOptions);
|
|
129
137
|
/**
|
|
130
138
|
* Generate a random Base32-encoded secret
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { HashAlgorithm, Digits, CryptoPlugin, Base32Plugin } from '@otplib/core';
|
|
1
|
+
import { HashAlgorithm, Digits, CryptoPlugin, Base32Plugin, OTPGuardrails } from '@otplib/core';
|
|
2
2
|
export { CryptoPlugin, Digits, HashAlgorithm, OTPResult, wrapResult, wrapResultAsync } from '@otplib/core';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -31,11 +31,18 @@ type HOTPOptions = {
|
|
|
31
31
|
readonly issuer?: string;
|
|
32
32
|
/** User identifier/email/username (for URI generation) */
|
|
33
33
|
readonly label?: string;
|
|
34
|
+
/**
|
|
35
|
+
* Custom guardrails to override validation limits
|
|
36
|
+
* Must be created via createGuardrails() factory
|
|
37
|
+
* Use this carefully - see danger-zone documentation
|
|
38
|
+
*/
|
|
39
|
+
readonly guardrails?: OTPGuardrails;
|
|
34
40
|
};
|
|
35
41
|
/**
|
|
36
42
|
* Required options for HOTP generation
|
|
37
43
|
*
|
|
38
44
|
* Requires `secret`, `counter`, and `crypto` for OTP generation.
|
|
45
|
+
* Optional `guardrails` must be created via createGuardrails() factory.
|
|
39
46
|
*/
|
|
40
47
|
type HOTPGenerateOptions = HOTPOptions & {
|
|
41
48
|
readonly secret: string | Uint8Array;
|
|
@@ -125,6 +132,7 @@ type VerifyResult = VerifyResultValid | VerifyResultInvalid;
|
|
|
125
132
|
*/
|
|
126
133
|
declare class HOTP {
|
|
127
134
|
private readonly options;
|
|
135
|
+
private readonly guardrails;
|
|
128
136
|
constructor(options?: HOTPOptions);
|
|
129
137
|
/**
|
|
130
138
|
* Generate a random Base32-encoded secret
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{counterToBytes as
|
|
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};
|
|
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 dynamicTruncate,\n truncateDigits,\n validateCounter,\n validateSecret,\n validateToken,\n validateCounterTolerance,\n normalizeSecret,\n normalizeCounterTolerance,\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 } = options;\n\n const secretBytes = normalizeSecret(secret, base32);\n validateSecret(secretBytes);\n validateCounter(counter);\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 } = options;\n\n const secretBytes = normalizeSecret(secret, base32);\n validateSecret(secretBytes);\n validateCounter(counter);\n validateToken(token, digits);\n validateCounterTolerance(counterTolerance);\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} 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\";\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\n constructor(options: HOTPOptions = {}) {\n this.options = options;\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\n const { secret, crypto, base32, algorithm = \"sha1\", digits = 6 } = mergedOptions;\n\n requireSecret(secret);\n requireCryptoPlugin(crypto);\n requireBase32Plugin(base32);\n\n return generateCode({\n secret,\n counter,\n algorithm,\n digits,\n crypto,\n base32,\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\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 return verifyCode({\n secret,\n token: params.token,\n counter: params.counter,\n algorithm,\n digits,\n counterTolerance,\n crypto,\n base32,\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","dynamicTruncate","truncateDigits","validateCounter","validateSecret","validateToken","validateCounterTolerance","normalizeSecret","normalizeCounterTolerance","generateSecretCore","requireCryptoPlugin","requireBase32Plugin","requireSecret","requireLabel","requireIssuer","requireBase32String","generateHOTPURI","HOTP","options","crypto","base32","requireCryptoPlugin","requireBase32Plugin","generateSecretCore","counter","mergedOptions","secret","algorithm","digits","requireSecret","generate","params","counterTolerance","verify","issuer","label","requireLabel","requireIssuer","requireBase32String","generateHOTPURI","wrapResult","wrapResultAsync","getHOTPGenerateOptions","options","secret","counter","algorithm","digits","crypto","base32","secretBytes","normalizeSecret","validateSecret","validateCounter","ctx","createCryptoContext","counterBytes","counterToBytes","generate","hmac","dt","dynamicTruncate","truncateDigits","generateSync","getHOTPVerifyOptions","token","counterTolerance","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\";\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"]}
|
package/dist/metafile-cjs.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"inputs":{"src/class.ts":{"bytes":
|
|
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}}}
|
package/dist/metafile-esm.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"inputs":{"src/class.ts":{"bytes":
|
|
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}}}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@otplib/hotp",
|
|
3
|
-
"version": "13.0
|
|
3
|
+
"version": "13.1.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,15 @@
|
|
|
45
45
|
"LICENSE"
|
|
46
46
|
],
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@otplib/core": "13.0
|
|
49
|
-
"@otplib/uri": "13.0
|
|
48
|
+
"@otplib/core": "13.1.0",
|
|
49
|
+
"@otplib/uri": "13.1.0"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"tsup": "^8.0.1",
|
|
53
53
|
"typescript": "^5.3.3",
|
|
54
54
|
"vitest": "^4.0.16",
|
|
55
|
-
"@
|
|
56
|
-
"@
|
|
55
|
+
"@repo/testing": "13.0.1",
|
|
56
|
+
"@otplib/plugin-crypto-node": "13.1.0"
|
|
57
57
|
},
|
|
58
58
|
"publishConfig": {
|
|
59
59
|
"access": "public"
|