quickotp 2.1.0 → 2.1.1
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 +6 -2
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +12 -10
- package/dist/index.d.ts +12 -10
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -83,11 +83,13 @@ Returns an `otpauth://totp/` URI with the base32-encoded secret. Throws `TypeErr
|
|
|
83
83
|
### `TOTP.verify(key, token, window?): boolean`
|
|
84
84
|
|
|
85
85
|
Verifies a TOTP token against the current time. `window` controls how many 30-second steps in either direction are accepted (default: `1`).
|
|
86
|
-
Throws `TypeError` or `RangeError` if `window` is not
|
|
86
|
+
Throws `TypeError` or `RangeError` if `key` is empty or `window` is not an integer from `0` through `10`.
|
|
87
|
+
Returns `false` for tokens that are not exactly 6 digits.
|
|
87
88
|
|
|
88
89
|
### `TOTP.qrcode(uri): Promise<string>`
|
|
89
90
|
|
|
90
91
|
Returns the URI encoded as a PNG data URL. Requires `qrcode` to be installed.
|
|
92
|
+
Throws if `uri` is not a valid `otpauth://totp/` or `otpauth://hotp/` URI with a `secret` parameter.
|
|
91
93
|
|
|
92
94
|
### `HOTP.create(key, label): string`
|
|
93
95
|
|
|
@@ -96,11 +98,13 @@ Returns an `otpauth://hotp/` URI with the base32-encoded secret. Throws `TypeErr
|
|
|
96
98
|
### `HOTP.verify(key, token, counter): boolean`
|
|
97
99
|
|
|
98
100
|
Verifies an HOTP token against the given counter value.
|
|
99
|
-
Throws `TypeError` or `RangeError` if `counter` is not a non-negative safe integer.
|
|
101
|
+
Throws `TypeError` or `RangeError` if `key` is empty or `counter` is not a non-negative safe integer.
|
|
102
|
+
Returns `false` for tokens that are not exactly 6 digits.
|
|
100
103
|
|
|
101
104
|
### `HOTP.qrcode(uri): Promise<string>`
|
|
102
105
|
|
|
103
106
|
Returns the URI encoded as a PNG data URL. Requires `qrcode` to be installed.
|
|
107
|
+
Throws if `uri` is not a valid `otpauth://totp/` or `otpauth://hotp/` URI with a `secret` parameter.
|
|
104
108
|
|
|
105
109
|
---
|
|
106
110
|
|
package/dist/index.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var y=Object.create;var i=Object.defineProperty;var b=Object.getOwnPropertyDescriptor;var l=Object.getOwnPropertyNames;var T=Object.getPrototypeOf,I=Object.prototype.hasOwnProperty;var P=(t,r)=>{for(var e in r)i(t,e,{get:r[e],enumerable:!0})},f=(t,r,e,n)=>{if(r&&typeof r=="object"||typeof r=="function")for(let o of l(r))!I.call(t,o)&&o!==e&&i(t,o,{get:()=>r[o],enumerable:!(n=b(r,o))||n.enumerable});return t};var E=(t,r,e)=>(e=t!=null?y(T(t)):{},f(r||!t||!t.__esModule?i(e,"default",{value:t,enumerable:!0}):e,t)),R=t=>f(i({},"__esModule",{value:!0}),t);var S={};P(S,{HOTP:()=>B,TOTP:()=>x});module.exports=R(S);var a=require("crypto"),u="ABCDEFGHIJKLMNOPQRSTUVWXYZ234567",U=6,q=30,p=10,c=2048,A=/^\d{6}$/;function O(t){let r="",e=0,n=0;for(let o of Buffer.from(t,"utf8"))for(n=n<<8|o,e+=8;e>=5;)r+=u[n>>>(e-=5)&31];return e>0&&(r+=u[n<<5-e&31]),r}function g(t){if(typeof t!="string"||t.length===0)throw new TypeError("key must be a non-empty string")}function m(t,r,e){if(typeof t!="number"||Number.isNaN(t))throw new TypeError(`${r} must be a number`);if(t<0||!(e?Number.isSafeInteger(t):Number.isInteger(t)))throw new RangeError(`${r} must be a non-negative${e?" safe":""} integer`)}function v(t){if(m(t,"window"),t>p)throw new RangeError(`window must be less than or equal to ${p}`)}function N(t){if(typeof t!="string")throw new TypeError("uri must be an otpauth URI string");if(t.length>c)throw new RangeError(`uri must be ${c} characters or fewer`);let r;try{r=new URL(t)}catch{throw new TypeError("uri must be a valid otpauth URI")}if(r.protocol!=="otpauth:")throw new TypeError("uri must use the otpauth protocol");if(r.hostname!=="totp"&&r.hostname!=="hotp")throw new TypeError("uri must be an otpauth totp or hotp URI");if(!r.searchParams.get("secret"))throw new TypeError("uri must include a secret parameter")}function h(t,r,e){g(r);let n={secret:O(r)};t==="hotp"&&(n.counter="0");let o=new URLSearchParams(n);return`otpauth://${t}/${encodeURIComponent(e)}?${o}`}async function d(t){N(t);let r;try{r=await import("qrcode")}catch{throw new Error('qrcode is not installed. Run "pnpm add qrcode" to use this feature.')}return r.toDataURL(t)}function w(t,r,e){return g(t),typeof r!="string"||!A.test(r)?!1:(0,a.timingSafeEqual)(Buffer.from($(t,e)),Buffer.from(r))}function $(t,r,e=U){m(r,"counter",!0);let n=Buffer.alloc(8);n.writeBigUInt64BE(BigInt(r));let o=(0,a.createHmac)("sha1",t).update(n).digest(),s=o[o.length-1]&15;return(((o[s]&127)<<24|(o[s+1]&255)<<16|(o[s+2]&255)<<8|o[s+3]&255)%10**e).toString().padStart(e,"0")}var x={create:(t,r)=>h("totp",t,r),qrcode:d,verify(t,r,e=1){v(e);let n=Math.floor(Date.now()/1e3/q);for(let o=-e;o<=e;o++)if(n+o>=0&&w(t,r,n+o))return!0;return!1}},B={create:(t,r)=>h("hotp",t,r),qrcode:d,verify:w};0&&(module.exports={HOTP,TOTP});
|
|
2
2
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { createHmac } from \"node:crypto\"\n\nconst base32Chars = \"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567\"\nconst defaultDigits = 6\nconst totpPeriodSeconds = 30\n\ntype OtpType = \"totp\" | \"hotp\"\n\n/**\n * Shared methods for both {@link TotpAPI} and {@link HotpAPI}.\n */\ntype OtpAPI = {\n /**\n * Generates an `otpauth://` URI to register the secret with an authenticator app.\n * @param key - The shared secret (plain text; encoded to Base32 internally).\n * @param label - Account identifier shown in the authenticator (e.g. `\"alice@example.com\"`).\n * @returns An `otpauth://` URI string.\n * @throws {TypeError} If `key` is empty.\n */\n create(key: string, label: string): string\n\n /**\n * Converts an `otpauth://` URI into a Base64-encoded PNG data URL (QR code).\n * Requires the optional `qrcode` package (`pnpm add qrcode`).\n * @param uri - An `otpauth://` URI returned by `create`.\n * @returns A `data:image/png;base64,...` string ready for use in an `<img>` tag.\n * @throws {Error} If the `qrcode` package is not installed.\n * @example\n * const dataUrl = await TOTP.qrcode(uri)\n * // \"data:image/png;base64,...\"\n */\n qrcode(uri: string): Promise<string>\n}\n\n/**\n * API for Time-based One-Time Password (TOTP, RFC 6238).\n * Tokens rotate every 30 seconds based on the current time.\n */\ntype TotpAPI = OtpAPI & {\n /**\n * Checks whether a token is valid for the current time window.\n * @param key - The shared secret used when the URI was created.\n * @param token - The 6-digit token entered by the user.\n * @param window - Number of 30-second periods to accept on either side of the current period (default `1`).\n * @returns `true` if the token matches any counter in the allowed window.\n * @throws {TypeError} If `window` is not a number.\n * @throws {RangeError} If `window` is not a non-negative integer.\n * @example\n * const ok = TOTP.verify(\"mysecret\", \"123456\")\n * const lenient = TOTP.verify(\"mysecret\", \"123456\", 2) // ±2 periods\n */\n verify(key: string, token: string, window?: number): boolean\n}\n\n/**\n * API for HMAC-based One-Time Password (HOTP, RFC 4226).\n * Tokens are derived from an incrementing counter rather than the clock.\n */\ntype HotpAPI = OtpAPI & {\n /**\n * Checks whether a token is valid for a specific counter value.\n * @param key - The shared secret used when the URI was created.\n * @param token - The 6-digit token to verify.\n * @param counter - The counter value that was used to generate the token.\n * @returns `true` if the token matches the given counter.\n * @throws {TypeError} If `counter` is not a number.\n * @throws {RangeError} If `counter` is not a non-negative safe integer.\n * @example\n * const ok = HOTP.verify(\"mysecret\", \"123456\", 42)\n */\n verify(key: string, token: string, counter: number): boolean\n}\n\n/** Encodes a UTF-8 string to Base32 (RFC 4648) without padding. */\nfunction base32Encode(input: string): string {\n let result = \"\", bits = 0, value = 0\n for (const byte of Buffer.from(input, \"utf8\")) {\n value = (value << 8) | byte\n bits += 8\n while (bits >= 5) result += base32Chars[(value >>> (bits -= 5)) & 31]\n }\n if (bits > 0) result += base32Chars[(value << (5 - bits)) & 31]\n return result\n}\n\nfunction assertNonEmptyKey(key: string): void {\n if (typeof key !== \"string\" || key.length === 0)\n throw new TypeError(\"key must be a non-empty string\")\n}\n\nfunction assertNonNegativeInt(value: number, name: string, safe?: boolean): void {\n if (typeof value !== \"number\" || Number.isNaN(value))\n throw new TypeError(`${name} must be a number`)\n if (value < 0 || !(safe ? Number.isSafeInteger(value) : Number.isInteger(value)))\n throw new RangeError(`${name} must be a non-negative${safe ? \" safe\" : \"\"} integer`)\n}\n\nfunction createOtpAuthUri(type: OtpType, key: string, label: string): string {\n assertNonEmptyKey(key)\n const searchParams = new URLSearchParams({ secret: base32Encode(key) })\n return `otpauth://${type}/${encodeURIComponent(label)}?${searchParams}`\n}\n\nasync function createQrCode(uri: string): Promise<string> {\n let qrCode: typeof import(\"qrcode\")\n try {\n qrCode = (await import(\"qrcode\")) as typeof import(\"qrcode\")\n } catch {\n throw new Error(\n 'qrcode is not installed. Run \"pnpm add qrcode\" to use this feature.',\n )\n }\n return qrCode.toDataURL(uri)\n}\n\n/**\n * Computes an HOTP value per RFC 4226 §5.\n * @param key - Plain-text secret used as the HMAC-SHA1 key.\n * @param counter - 8-byte big-endian counter value.\n * @param digits - Number of digits in the output (default 6).\n */\nfunction computeHotp(key: string, counter: number, digits = defaultDigits): string {\n assertNonNegativeInt(counter, \"counter\", true)\n const buf = Buffer.alloc(8)\n buf.writeBigUInt64BE(BigInt(counter))\n const digest = createHmac(\"sha1\", key).update(buf).digest()\n const offset = digest[digest.length - 1] & 0x0f\n const code = ((digest[offset] & 0x7f) << 24) | ((digest[offset + 1] & 0xff) << 16) | ((digest[offset + 2] & 0xff) << 8) | (digest[offset + 3] & 0xff)\n return (code % 10 ** digits).toString().padStart(digits, \"0\")\n}\n\n/**\n * Time-based One-Time Password helpers (RFC 6238).\n * @example\n * import { TOTP } from \"quickotp\"\n * const uri = TOTP.create(\"mysecret\", \"alice@example.com\")\n * const ok = TOTP.verify(\"mysecret\", userInput)\n */\nconst totp: TotpAPI = {\n create: (key, label) => createOtpAuthUri(\"totp\", key, label),\n qrcode: createQrCode,\n verify(key: string, token: string, window = 1): boolean {\n assertNonNegativeInt(window, \"window\")\n const counter = Math.floor(Date.now() / 1000 / totpPeriodSeconds)\n for (let i = -window; i <= window; i++)\n if (counter + i >= 0 && computeHotp(key, counter + i) === token) return true\n return false\n },\n}\n\n/**\n * HMAC-based One-Time Password helpers (RFC 4226).\n * @example\n * import { HOTP } from \"quickotp\"\n * const uri = HOTP.create(\"mysecret\", \"alice@example.com\")\n * const ok = HOTP.verify(\"mysecret\", userInput, counter)\n */\nconst hotp: HotpAPI = {\n create: (key, label) => createOtpAuthUri(\"hotp\", key, label),\n qrcode: createQrCode,\n verify: (key, token, counter) => computeHotp(key, counter) === token,\n}\n\nexport { totp as TOTP, hotp as HOTP }\n"],"mappings":"0jBAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,UAAAE,EAAA,SAAAC,IAAA,eAAAC,EAAAJ,GAAA,IAAAK,EAA2B,kBAErBC,EAAc,mCACdC,EAAgB,EAChBC,EAAoB,GAsE1B,SAASC,EAAaC,EAAuB,CAC3C,IAAIC,EAAS,GAAIC,EAAO,EAAGC,EAAQ,EACnC,QAAWC,KAAQ,OAAO,KAAKJ,EAAO,MAAM,EAG1C,IAFAG,EAASA,GAAS,EAAKC,EACvBF,GAAQ,EACDA,GAAQ,GAAGD,GAAUL,EAAaO,KAAWD,GAAQ,GAAM,EAAE,EAEtE,OAAIA,EAAO,IAAGD,GAAUL,EAAaO,GAAU,EAAID,EAAS,EAAE,GACvDD,CACT,CAEA,SAASI,EAAkBC,EAAmB,CAC5C,GAAI,OAAOA,GAAQ,UAAYA,EAAI,SAAW,EAC5C,MAAM,IAAI,UAAU,gCAAgC,CACxD,CAEA,SAASC,EAAqBJ,EAAeK,EAAcC,EAAsB,CAC/E,GAAI,OAAON,GAAU,UAAY,OAAO,MAAMA,CAAK,EACjD,MAAM,IAAI,UAAU,GAAGK,CAAI,mBAAmB,EAChD,GAAIL,EAAQ,GAAK,EAAEM,EAAO,OAAO,cAAcN,CAAK,EAAI,OAAO,UAAUA,CAAK,GAC5E,MAAM,IAAI,WAAW,GAAGK,CAAI,0BAA0BC,EAAO,QAAU,EAAE,UAAU,CACvF,CAEA,SAASC,EAAiBC,EAAeL,EAAaM,EAAuB,CAC3EP,EAAkBC,CAAG,EACrB,IAAMO,EAAe,IAAI,gBAAgB,CAAE,OAAQd,EAAaO,CAAG,CAAE,CAAC,EACtE,MAAO,aAAaK,CAAI,IAAI,mBAAmBC,CAAK,CAAC,IAAIC,CAAY,EACvE,CAEA,eAAeC,EAAaC,EAA8B,CACxD,IAAIC,EACJ,GAAI,CACFA,EAAU,KAAM,QAAO,QAAQ,CACjC,MAAQ,CACN,MAAM,IAAI,MACR,qEACF,CACF,CACA,OAAOA,EAAO,UAAUD,CAAG,CAC7B,CAQA,SAASE,EAAYX,EAAaY,EAAiBC,EAAStB,EAAuB,CACjFU,EAAqBW,EAAS,UAAW,EAAI,EAC7C,IAAME,EAAM,OAAO,MAAM,CAAC,EAC1BA,EAAI,iBAAiB,OAAOF,CAAO,CAAC,EACpC,IAAMG,KAAS,cAAW,OAAQf,CAAG,EAAE,OAAOc,CAAG,EAAE,OAAO,EACpDE,EAASD,EAAOA,EAAO,OAAS,CAAC,EAAI,GAE3C,SADeA,EAAOC,CAAM,EAAI,MAAS,IAAQD,EAAOC,EAAS,CAAC,EAAI,MAAS,IAAQD,EAAOC,EAAS,CAAC,EAAI,MAAS,EAAMD,EAAOC,EAAS,CAAC,EAAI,KACjI,IAAMH,GAAQ,SAAS,EAAE,SAASA,EAAQ,GAAG,CAC9D,CASA,IAAM1B,EAAgB,CACpB,OAAQ,CAACa,EAAKM,IAAUF,EAAiB,OAAQJ,EAAKM,CAAK,EAC3D,OAAQE,EACR,OAAOR,EAAaiB,EAAeC,EAAS,EAAY,CACtDjB,EAAqBiB,EAAQ,QAAQ,EACrC,IAAMN,EAAU,KAAK,MAAM,KAAK,IAAI,EAAI,IAAOpB,CAAiB,EAChE,QAAS2B,EAAI,CAACD,EAAQC,GAAKD,EAAQC,IACjC,GAAIP,EAAUO,GAAK,GAAKR,EAAYX,EAAKY,EAAUO,CAAC,IAAMF,EAAO,MAAO,GAC1E,MAAO,EACT,CACF,EASM/B,EAAgB,CACpB,OAAQ,CAACc,EAAKM,IAAUF,EAAiB,OAAQJ,EAAKM,CAAK,EAC3D,OAAQE,EACR,OAAQ,CAACR,EAAKiB,EAAOL,IAAYD,EAAYX,EAAKY,CAAO,IAAMK,CACjE","names":["index_exports","__export","hotp","totp","__toCommonJS","import_node_crypto","base32Chars","defaultDigits","totpPeriodSeconds","base32Encode","input","result","bits","value","byte","assertNonEmptyKey","key","assertNonNegativeInt","name","safe","createOtpAuthUri","type","label","searchParams","createQrCode","uri","qrCode","computeHotp","counter","digits","buf","digest","offset","token","window","i"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { createHmac, timingSafeEqual } from \"node:crypto\"\n\nconst base32Chars = \"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567\"\nconst defaultDigits = 6\nconst totpPeriodSeconds = 30\nconst maxTotpWindow = 10\nconst maxQrCodeUriLength = 2048\nconst tokenPattern = /^\\d{6}$/\n\ntype OtpType = \"totp\" | \"hotp\"\n\n/**\n * Shared methods for both {@link TotpAPI} and {@link HotpAPI}.\n */\ntype OtpAPI = {\n /**\n * Generates an `otpauth://` URI to register the secret with an authenticator app.\n * @param key - The shared secret (plain text; encoded to Base32 internally).\n * @param label - Account identifier shown in the authenticator (e.g. `\"alice@example.com\"`).\n * @returns An `otpauth://` URI string.\n * @throws {TypeError} If `key` is empty.\n */\n create(key: string, label: string): string\n\n /**\n * Converts an `otpauth://` URI into a Base64-encoded PNG data URL (QR code).\n * Requires the optional `qrcode` package (`pnpm add qrcode`).\n * @param uri - An `otpauth://` URI returned by `create`.\n * @returns A `data:image/png;base64,...` string ready for use in an `<img>` tag.\n * @throws {TypeError} If `uri` is not a valid `otpauth://` URI.\n * @throws {RangeError} If `uri` is too long.\n * @throws {Error} If the `qrcode` package is not installed.\n * @example\n * const dataUrl = await TOTP.qrcode(uri)\n * // \"data:image/png;base64,...\"\n */\n qrcode(uri: string): Promise<string>\n}\n\n/**\n * API for Time-based One-Time Password (TOTP, RFC 6238).\n * Tokens rotate every 30 seconds based on the current time.\n */\ntype TotpAPI = OtpAPI & {\n /**\n * Checks whether a token is valid for the current time window.\n * @param key - The shared secret used when the URI was created.\n * @param token - The 6-digit token entered by the user.\n * @param window - Number of 30-second periods to accept on either side of the current period (default `1`).\n * @returns `true` if the token matches any counter in the allowed window.\n * @throws {TypeError} If `key` is empty or `window` is not a number.\n * @throws {RangeError} If `window` is not an integer from `0` through `10`.\n * @example\n * const ok = TOTP.verify(sharedSecret, \"123456\")\n * const lenient = TOTP.verify(sharedSecret, \"123456\", 2) // ±2 periods\n */\n verify(key: string, token: string, window?: number): boolean\n}\n\n/**\n * API for HMAC-based One-Time Password (HOTP, RFC 4226).\n * Tokens are derived from an incrementing counter rather than the clock.\n */\ntype HotpAPI = OtpAPI & {\n /**\n * Checks whether a token is valid for a specific counter value.\n * @param key - The shared secret used when the URI was created.\n * @param token - The 6-digit token to verify.\n * @param counter - The counter value that was used to generate the token.\n * @returns `true` if the token matches the given counter.\n * @throws {TypeError} If `key` is empty or `counter` is not a number.\n * @throws {RangeError} If `counter` is not a non-negative safe integer.\n * @example\n * const ok = HOTP.verify(sharedSecret, \"123456\", 42)\n */\n verify(key: string, token: string, counter: number): boolean\n}\n\n/** Encodes a UTF-8 string to Base32 (RFC 4648) without padding. */\nfunction base32Encode(input: string): string {\n let result = \"\",\n bits = 0,\n value = 0\n for (const byte of Buffer.from(input, \"utf8\")) {\n value = (value << 8) | byte\n bits += 8\n while (bits >= 5) result += base32Chars[(value >>> (bits -= 5)) & 31]\n }\n if (bits > 0) result += base32Chars[(value << (5 - bits)) & 31]\n return result\n}\n\nfunction assertNonEmptyKey(key: string): void {\n if (typeof key !== \"string\" || key.length === 0)\n throw new TypeError(\"key must be a non-empty string\")\n}\n\nfunction assertNonNegativeInt(\n value: number,\n name: string,\n safe?: boolean,\n): void {\n if (typeof value !== \"number\" || Number.isNaN(value))\n throw new TypeError(`${name} must be a number`)\n if (\n value < 0 ||\n !(safe ? Number.isSafeInteger(value) : Number.isInteger(value))\n )\n throw new RangeError(\n `${name} must be a non-negative${safe ? \" safe\" : \"\"} integer`,\n )\n}\n\nfunction assertTotpWindow(window: number): void {\n assertNonNegativeInt(window, \"window\")\n if (window > maxTotpWindow)\n throw new RangeError(`window must be less than or equal to ${maxTotpWindow}`)\n}\n\nfunction assertOtpAuthUri(uri: string): void {\n if (typeof uri !== \"string\")\n throw new TypeError(\"uri must be an otpauth URI string\")\n if (uri.length > maxQrCodeUriLength)\n throw new RangeError(\n `uri must be ${maxQrCodeUriLength} characters or fewer`,\n )\n\n let parsedUri: URL\n try {\n parsedUri = new URL(uri)\n } catch {\n throw new TypeError(\"uri must be a valid otpauth URI\")\n }\n\n if (parsedUri.protocol !== \"otpauth:\")\n throw new TypeError(\"uri must use the otpauth protocol\")\n if (parsedUri.hostname !== \"totp\" && parsedUri.hostname !== \"hotp\")\n throw new TypeError(\"uri must be an otpauth totp or hotp URI\")\n if (!parsedUri.searchParams.get(\"secret\"))\n throw new TypeError(\"uri must include a secret parameter\")\n}\n\nfunction createOtpAuthUri(type: OtpType, key: string, label: string): string {\n assertNonEmptyKey(key)\n const params: Record<string, string> = { secret: base32Encode(key) }\n if (type === \"hotp\") params.counter = \"0\"\n const searchParams = new URLSearchParams(params)\n return `otpauth://${type}/${encodeURIComponent(label)}?${searchParams}`\n}\n\nasync function createQrCode(uri: string): Promise<string> {\n assertOtpAuthUri(uri)\n let qrCode: typeof import(\"qrcode\")\n try {\n qrCode = (await import(\"qrcode\")) as typeof import(\"qrcode\")\n } catch {\n throw new Error(\n 'qrcode is not installed. Run \"pnpm add qrcode\" to use this feature.',\n )\n }\n return qrCode.toDataURL(uri)\n}\n\nfunction verifyToken(key: string, token: string, counter: number): boolean {\n assertNonEmptyKey(key)\n if (typeof token !== \"string\") return false\n if (!tokenPattern.test(token)) return false\n return timingSafeEqual(\n Buffer.from(computeHotp(key, counter)),\n Buffer.from(token),\n )\n}\n\n/**\n * Computes an HOTP value per RFC 4226 §5.\n * @param key - Plain-text secret used as the HMAC-SHA1 key.\n * @param counter - 8-byte big-endian counter value.\n * @param digits - Number of digits in the output (default 6).\n */\nfunction computeHotp(\n key: string,\n counter: number,\n digits = defaultDigits,\n): string {\n assertNonNegativeInt(counter, \"counter\", true)\n const buf = Buffer.alloc(8)\n buf.writeBigUInt64BE(BigInt(counter))\n const digest = createHmac(\"sha1\", key).update(buf).digest()\n const offset = digest[digest.length - 1] & 0x0f\n const code =\n ((digest[offset] & 0x7f) << 24) |\n ((digest[offset + 1] & 0xff) << 16) |\n ((digest[offset + 2] & 0xff) << 8) |\n (digest[offset + 3] & 0xff)\n return (code % 10 ** digits).toString().padStart(digits, \"0\")\n}\n\n/**\n * Time-based One-Time Password helpers (RFC 6238).\n * @example\n * import { TOTP } from \"quickotp\"\n * const uri = TOTP.create(sharedSecret, \"alice@example.com\")\n * const ok = TOTP.verify(sharedSecret, userInput)\n */\nconst totp: TotpAPI = {\n create: (key, label) => createOtpAuthUri(\"totp\", key, label),\n qrcode: createQrCode,\n verify(key: string, token: string, window = 1): boolean {\n assertTotpWindow(window)\n const counter = Math.floor(Date.now() / 1000 / totpPeriodSeconds)\n for (let i = -window; i <= window; i++)\n if (counter + i >= 0 && verifyToken(key, token, counter + i)) return true\n return false\n },\n}\n\n/**\n * HMAC-based One-Time Password helpers (RFC 4226).\n * @example\n * import { HOTP } from \"quickotp\"\n * const uri = HOTP.create(sharedSecret, \"alice@example.com\")\n * const ok = HOTP.verify(sharedSecret, userInput, counter)\n */\nconst hotp: HotpAPI = {\n create: (key, label) => createOtpAuthUri(\"hotp\", key, label),\n qrcode: createQrCode,\n verify: verifyToken,\n}\n\nexport { totp as TOTP, hotp as HOTP }\n"],"mappings":"0jBAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,UAAAE,EAAA,SAAAC,IAAA,eAAAC,EAAAJ,GAAA,IAAAK,EAA4C,kBAEtCC,EAAc,mCACdC,EAAgB,EAChBC,EAAoB,GACpBC,EAAgB,GAChBC,EAAqB,KACrBC,EAAe,UAwErB,SAASC,EAAaC,EAAuB,CAC3C,IAAIC,EAAS,GACXC,EAAO,EACPC,EAAQ,EACV,QAAWC,KAAQ,OAAO,KAAKJ,EAAO,MAAM,EAG1C,IAFAG,EAASA,GAAS,EAAKC,EACvBF,GAAQ,EACDA,GAAQ,GAAGD,GAAUR,EAAaU,KAAWD,GAAQ,GAAM,EAAE,EAEtE,OAAIA,EAAO,IAAGD,GAAUR,EAAaU,GAAU,EAAID,EAAS,EAAE,GACvDD,CACT,CAEA,SAASI,EAAkBC,EAAmB,CAC5C,GAAI,OAAOA,GAAQ,UAAYA,EAAI,SAAW,EAC5C,MAAM,IAAI,UAAU,gCAAgC,CACxD,CAEA,SAASC,EACPJ,EACAK,EACAC,EACM,CACN,GAAI,OAAON,GAAU,UAAY,OAAO,MAAMA,CAAK,EACjD,MAAM,IAAI,UAAU,GAAGK,CAAI,mBAAmB,EAChD,GACEL,EAAQ,GACR,EAAEM,EAAO,OAAO,cAAcN,CAAK,EAAI,OAAO,UAAUA,CAAK,GAE7D,MAAM,IAAI,WACR,GAAGK,CAAI,0BAA0BC,EAAO,QAAU,EAAE,UACtD,CACJ,CAEA,SAASC,EAAiBC,EAAsB,CAE9C,GADAJ,EAAqBI,EAAQ,QAAQ,EACjCA,EAASf,EACX,MAAM,IAAI,WAAW,wCAAwCA,CAAa,EAAE,CAChF,CAEA,SAASgB,EAAiBC,EAAmB,CAC3C,GAAI,OAAOA,GAAQ,SACjB,MAAM,IAAI,UAAU,mCAAmC,EACzD,GAAIA,EAAI,OAAShB,EACf,MAAM,IAAI,WACR,eAAeA,CAAkB,sBACnC,EAEF,IAAIiB,EACJ,GAAI,CACFA,EAAY,IAAI,IAAID,CAAG,CACzB,MAAQ,CACN,MAAM,IAAI,UAAU,iCAAiC,CACvD,CAEA,GAAIC,EAAU,WAAa,WACzB,MAAM,IAAI,UAAU,mCAAmC,EACzD,GAAIA,EAAU,WAAa,QAAUA,EAAU,WAAa,OAC1D,MAAM,IAAI,UAAU,yCAAyC,EAC/D,GAAI,CAACA,EAAU,aAAa,IAAI,QAAQ,EACtC,MAAM,IAAI,UAAU,qCAAqC,CAC7D,CAEA,SAASC,EAAiBC,EAAeV,EAAaW,EAAuB,CAC3EZ,EAAkBC,CAAG,EACrB,IAAMY,EAAiC,CAAE,OAAQnB,EAAaO,CAAG,CAAE,EAC/DU,IAAS,SAAQE,EAAO,QAAU,KACtC,IAAMC,EAAe,IAAI,gBAAgBD,CAAM,EAC/C,MAAO,aAAaF,CAAI,IAAI,mBAAmBC,CAAK,CAAC,IAAIE,CAAY,EACvE,CAEA,eAAeC,EAAaP,EAA8B,CACxDD,EAAiBC,CAAG,EACpB,IAAIQ,EACJ,GAAI,CACFA,EAAU,KAAM,QAAO,QAAQ,CACjC,MAAQ,CACN,MAAM,IAAI,MACR,qEACF,CACF,CACA,OAAOA,EAAO,UAAUR,CAAG,CAC7B,CAEA,SAASS,EAAYhB,EAAaiB,EAAeC,EAA0B,CAGzE,OAFAnB,EAAkBC,CAAG,EACjB,OAAOiB,GAAU,UACjB,CAACzB,EAAa,KAAKyB,CAAK,EAAU,MAC/B,mBACL,OAAO,KAAKE,EAAYnB,EAAKkB,CAAO,CAAC,EACrC,OAAO,KAAKD,CAAK,CACnB,CACF,CAQA,SAASE,EACPnB,EACAkB,EACAE,EAAShC,EACD,CACRa,EAAqBiB,EAAS,UAAW,EAAI,EAC7C,IAAMG,EAAM,OAAO,MAAM,CAAC,EAC1BA,EAAI,iBAAiB,OAAOH,CAAO,CAAC,EACpC,IAAMI,KAAS,cAAW,OAAQtB,CAAG,EAAE,OAAOqB,CAAG,EAAE,OAAO,EACpDE,EAASD,EAAOA,EAAO,OAAS,CAAC,EAAI,GAM3C,SAJIA,EAAOC,CAAM,EAAI,MAAS,IAC1BD,EAAOC,EAAS,CAAC,EAAI,MAAS,IAC9BD,EAAOC,EAAS,CAAC,EAAI,MAAS,EAC/BD,EAAOC,EAAS,CAAC,EAAI,KACT,IAAMH,GAAQ,SAAS,EAAE,SAASA,EAAQ,GAAG,CAC9D,CASA,IAAMpC,EAAgB,CACpB,OAAQ,CAACgB,EAAKW,IAAUF,EAAiB,OAAQT,EAAKW,CAAK,EAC3D,OAAQG,EACR,OAAOd,EAAaiB,EAAeZ,EAAS,EAAY,CACtDD,EAAiBC,CAAM,EACvB,IAAMa,EAAU,KAAK,MAAM,KAAK,IAAI,EAAI,IAAO7B,CAAiB,EAChE,QAASmC,EAAI,CAACnB,EAAQmB,GAAKnB,EAAQmB,IACjC,GAAIN,EAAUM,GAAK,GAAKR,EAAYhB,EAAKiB,EAAOC,EAAUM,CAAC,EAAG,MAAO,GACvE,MAAO,EACT,CACF,EASMzC,EAAgB,CACpB,OAAQ,CAACiB,EAAKW,IAAUF,EAAiB,OAAQT,EAAKW,CAAK,EAC3D,OAAQG,EACR,OAAQE,CACV","names":["index_exports","__export","hotp","totp","__toCommonJS","import_node_crypto","base32Chars","defaultDigits","totpPeriodSeconds","maxTotpWindow","maxQrCodeUriLength","tokenPattern","base32Encode","input","result","bits","value","byte","assertNonEmptyKey","key","assertNonNegativeInt","name","safe","assertTotpWindow","window","assertOtpAuthUri","uri","parsedUri","createOtpAuthUri","type","label","params","searchParams","createQrCode","qrCode","verifyToken","token","counter","computeHotp","digits","buf","digest","offset","i"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -15,6 +15,8 @@ type OtpAPI = {
|
|
|
15
15
|
* Requires the optional `qrcode` package (`pnpm add qrcode`).
|
|
16
16
|
* @param uri - An `otpauth://` URI returned by `create`.
|
|
17
17
|
* @returns A `data:image/png;base64,...` string ready for use in an `<img>` tag.
|
|
18
|
+
* @throws {TypeError} If `uri` is not a valid `otpauth://` URI.
|
|
19
|
+
* @throws {RangeError} If `uri` is too long.
|
|
18
20
|
* @throws {Error} If the `qrcode` package is not installed.
|
|
19
21
|
* @example
|
|
20
22
|
* const dataUrl = await TOTP.qrcode(uri)
|
|
@@ -33,11 +35,11 @@ type TotpAPI = OtpAPI & {
|
|
|
33
35
|
* @param token - The 6-digit token entered by the user.
|
|
34
36
|
* @param window - Number of 30-second periods to accept on either side of the current period (default `1`).
|
|
35
37
|
* @returns `true` if the token matches any counter in the allowed window.
|
|
36
|
-
* @throws {TypeError} If `window` is not a number.
|
|
37
|
-
* @throws {RangeError} If `window` is not
|
|
38
|
+
* @throws {TypeError} If `key` is empty or `window` is not a number.
|
|
39
|
+
* @throws {RangeError} If `window` is not an integer from `0` through `10`.
|
|
38
40
|
* @example
|
|
39
|
-
* const ok = TOTP.verify(
|
|
40
|
-
* const lenient = TOTP.verify(
|
|
41
|
+
* const ok = TOTP.verify(sharedSecret, "123456")
|
|
42
|
+
* const lenient = TOTP.verify(sharedSecret, "123456", 2) // ±2 periods
|
|
41
43
|
*/
|
|
42
44
|
verify(key: string, token: string, window?: number): boolean;
|
|
43
45
|
};
|
|
@@ -52,10 +54,10 @@ type HotpAPI = OtpAPI & {
|
|
|
52
54
|
* @param token - The 6-digit token to verify.
|
|
53
55
|
* @param counter - The counter value that was used to generate the token.
|
|
54
56
|
* @returns `true` if the token matches the given counter.
|
|
55
|
-
* @throws {TypeError} If `counter` is not a number.
|
|
57
|
+
* @throws {TypeError} If `key` is empty or `counter` is not a number.
|
|
56
58
|
* @throws {RangeError} If `counter` is not a non-negative safe integer.
|
|
57
59
|
* @example
|
|
58
|
-
* const ok = HOTP.verify(
|
|
60
|
+
* const ok = HOTP.verify(sharedSecret, "123456", 42)
|
|
59
61
|
*/
|
|
60
62
|
verify(key: string, token: string, counter: number): boolean;
|
|
61
63
|
};
|
|
@@ -63,16 +65,16 @@ type HotpAPI = OtpAPI & {
|
|
|
63
65
|
* Time-based One-Time Password helpers (RFC 6238).
|
|
64
66
|
* @example
|
|
65
67
|
* import { TOTP } from "quickotp"
|
|
66
|
-
* const uri = TOTP.create(
|
|
67
|
-
* const ok = TOTP.verify(
|
|
68
|
+
* const uri = TOTP.create(sharedSecret, "alice@example.com")
|
|
69
|
+
* const ok = TOTP.verify(sharedSecret, userInput)
|
|
68
70
|
*/
|
|
69
71
|
declare const totp: TotpAPI;
|
|
70
72
|
/**
|
|
71
73
|
* HMAC-based One-Time Password helpers (RFC 4226).
|
|
72
74
|
* @example
|
|
73
75
|
* import { HOTP } from "quickotp"
|
|
74
|
-
* const uri = HOTP.create(
|
|
75
|
-
* const ok = HOTP.verify(
|
|
76
|
+
* const uri = HOTP.create(sharedSecret, "alice@example.com")
|
|
77
|
+
* const ok = HOTP.verify(sharedSecret, userInput, counter)
|
|
76
78
|
*/
|
|
77
79
|
declare const hotp: HotpAPI;
|
|
78
80
|
|
package/dist/index.d.ts
CHANGED
|
@@ -15,6 +15,8 @@ type OtpAPI = {
|
|
|
15
15
|
* Requires the optional `qrcode` package (`pnpm add qrcode`).
|
|
16
16
|
* @param uri - An `otpauth://` URI returned by `create`.
|
|
17
17
|
* @returns A `data:image/png;base64,...` string ready for use in an `<img>` tag.
|
|
18
|
+
* @throws {TypeError} If `uri` is not a valid `otpauth://` URI.
|
|
19
|
+
* @throws {RangeError} If `uri` is too long.
|
|
18
20
|
* @throws {Error} If the `qrcode` package is not installed.
|
|
19
21
|
* @example
|
|
20
22
|
* const dataUrl = await TOTP.qrcode(uri)
|
|
@@ -33,11 +35,11 @@ type TotpAPI = OtpAPI & {
|
|
|
33
35
|
* @param token - The 6-digit token entered by the user.
|
|
34
36
|
* @param window - Number of 30-second periods to accept on either side of the current period (default `1`).
|
|
35
37
|
* @returns `true` if the token matches any counter in the allowed window.
|
|
36
|
-
* @throws {TypeError} If `window` is not a number.
|
|
37
|
-
* @throws {RangeError} If `window` is not
|
|
38
|
+
* @throws {TypeError} If `key` is empty or `window` is not a number.
|
|
39
|
+
* @throws {RangeError} If `window` is not an integer from `0` through `10`.
|
|
38
40
|
* @example
|
|
39
|
-
* const ok = TOTP.verify(
|
|
40
|
-
* const lenient = TOTP.verify(
|
|
41
|
+
* const ok = TOTP.verify(sharedSecret, "123456")
|
|
42
|
+
* const lenient = TOTP.verify(sharedSecret, "123456", 2) // ±2 periods
|
|
41
43
|
*/
|
|
42
44
|
verify(key: string, token: string, window?: number): boolean;
|
|
43
45
|
};
|
|
@@ -52,10 +54,10 @@ type HotpAPI = OtpAPI & {
|
|
|
52
54
|
* @param token - The 6-digit token to verify.
|
|
53
55
|
* @param counter - The counter value that was used to generate the token.
|
|
54
56
|
* @returns `true` if the token matches the given counter.
|
|
55
|
-
* @throws {TypeError} If `counter` is not a number.
|
|
57
|
+
* @throws {TypeError} If `key` is empty or `counter` is not a number.
|
|
56
58
|
* @throws {RangeError} If `counter` is not a non-negative safe integer.
|
|
57
59
|
* @example
|
|
58
|
-
* const ok = HOTP.verify(
|
|
60
|
+
* const ok = HOTP.verify(sharedSecret, "123456", 42)
|
|
59
61
|
*/
|
|
60
62
|
verify(key: string, token: string, counter: number): boolean;
|
|
61
63
|
};
|
|
@@ -63,16 +65,16 @@ type HotpAPI = OtpAPI & {
|
|
|
63
65
|
* Time-based One-Time Password helpers (RFC 6238).
|
|
64
66
|
* @example
|
|
65
67
|
* import { TOTP } from "quickotp"
|
|
66
|
-
* const uri = TOTP.create(
|
|
67
|
-
* const ok = TOTP.verify(
|
|
68
|
+
* const uri = TOTP.create(sharedSecret, "alice@example.com")
|
|
69
|
+
* const ok = TOTP.verify(sharedSecret, userInput)
|
|
68
70
|
*/
|
|
69
71
|
declare const totp: TotpAPI;
|
|
70
72
|
/**
|
|
71
73
|
* HMAC-based One-Time Password helpers (RFC 4226).
|
|
72
74
|
* @example
|
|
73
75
|
* import { HOTP } from "quickotp"
|
|
74
|
-
* const uri = HOTP.create(
|
|
75
|
-
* const ok = HOTP.verify(
|
|
76
|
+
* const uri = HOTP.create(sharedSecret, "alice@example.com")
|
|
77
|
+
* const ok = HOTP.verify(sharedSecret, userInput, counter)
|
|
76
78
|
*/
|
|
77
79
|
declare const hotp: HotpAPI;
|
|
78
80
|
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{createHmac as
|
|
1
|
+
import{createHmac as h,timingSafeEqual as d}from"crypto";var i="ABCDEFGHIJKLMNOPQRSTUVWXYZ234567",w=6,y=30,a=10,f=2048,b=/^\d{6}$/;function l(t){let r="",e=0,n=0;for(let o of Buffer.from(t,"utf8"))for(n=n<<8|o,e+=8;e>=5;)r+=i[n>>>(e-=5)&31];return e>0&&(r+=i[n<<5-e&31]),r}function u(t){if(typeof t!="string"||t.length===0)throw new TypeError("key must be a non-empty string")}function p(t,r,e){if(typeof t!="number"||Number.isNaN(t))throw new TypeError(`${r} must be a number`);if(t<0||!(e?Number.isSafeInteger(t):Number.isInteger(t)))throw new RangeError(`${r} must be a non-negative${e?" safe":""} integer`)}function T(t){if(p(t,"window"),t>a)throw new RangeError(`window must be less than or equal to ${a}`)}function I(t){if(typeof t!="string")throw new TypeError("uri must be an otpauth URI string");if(t.length>f)throw new RangeError(`uri must be ${f} characters or fewer`);let r;try{r=new URL(t)}catch{throw new TypeError("uri must be a valid otpauth URI")}if(r.protocol!=="otpauth:")throw new TypeError("uri must use the otpauth protocol");if(r.hostname!=="totp"&&r.hostname!=="hotp")throw new TypeError("uri must be an otpauth totp or hotp URI");if(!r.searchParams.get("secret"))throw new TypeError("uri must include a secret parameter")}function c(t,r,e){u(r);let n={secret:l(r)};t==="hotp"&&(n.counter="0");let o=new URLSearchParams(n);return`otpauth://${t}/${encodeURIComponent(e)}?${o}`}async function g(t){I(t);let r;try{r=await import("qrcode")}catch{throw new Error('qrcode is not installed. Run "pnpm add qrcode" to use this feature.')}return r.toDataURL(t)}function m(t,r,e){return u(t),typeof r!="string"||!b.test(r)?!1:d(Buffer.from(P(t,e)),Buffer.from(r))}function P(t,r,e=w){p(r,"counter",!0);let n=Buffer.alloc(8);n.writeBigUInt64BE(BigInt(r));let o=h("sha1",t).update(n).digest(),s=o[o.length-1]&15;return(((o[s]&127)<<24|(o[s+1]&255)<<16|(o[s+2]&255)<<8|o[s+3]&255)%10**e).toString().padStart(e,"0")}var U={create:(t,r)=>c("totp",t,r),qrcode:g,verify(t,r,e=1){T(e);let n=Math.floor(Date.now()/1e3/y);for(let o=-e;o<=e;o++)if(n+o>=0&&m(t,r,n+o))return!0;return!1}},q={create:(t,r)=>c("hotp",t,r),qrcode:g,verify:m};export{q as HOTP,U as TOTP};
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { createHmac } from \"node:crypto\"\n\nconst base32Chars = \"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567\"\nconst defaultDigits = 6\nconst totpPeriodSeconds = 30\n\ntype OtpType = \"totp\" | \"hotp\"\n\n/**\n * Shared methods for both {@link TotpAPI} and {@link HotpAPI}.\n */\ntype OtpAPI = {\n /**\n * Generates an `otpauth://` URI to register the secret with an authenticator app.\n * @param key - The shared secret (plain text; encoded to Base32 internally).\n * @param label - Account identifier shown in the authenticator (e.g. `\"alice@example.com\"`).\n * @returns An `otpauth://` URI string.\n * @throws {TypeError} If `key` is empty.\n */\n create(key: string, label: string): string\n\n /**\n * Converts an `otpauth://` URI into a Base64-encoded PNG data URL (QR code).\n * Requires the optional `qrcode` package (`pnpm add qrcode`).\n * @param uri - An `otpauth://` URI returned by `create`.\n * @returns A `data:image/png;base64,...` string ready for use in an `<img>` tag.\n * @throws {Error} If the `qrcode` package is not installed.\n * @example\n * const dataUrl = await TOTP.qrcode(uri)\n * // \"data:image/png;base64,...\"\n */\n qrcode(uri: string): Promise<string>\n}\n\n/**\n * API for Time-based One-Time Password (TOTP, RFC 6238).\n * Tokens rotate every 30 seconds based on the current time.\n */\ntype TotpAPI = OtpAPI & {\n /**\n * Checks whether a token is valid for the current time window.\n * @param key - The shared secret used when the URI was created.\n * @param token - The 6-digit token entered by the user.\n * @param window - Number of 30-second periods to accept on either side of the current period (default `1`).\n * @returns `true` if the token matches any counter in the allowed window.\n * @throws {TypeError} If `window` is not a number.\n * @throws {RangeError} If `window` is not a non-negative integer.\n * @example\n * const ok = TOTP.verify(\"mysecret\", \"123456\")\n * const lenient = TOTP.verify(\"mysecret\", \"123456\", 2) // ±2 periods\n */\n verify(key: string, token: string, window?: number): boolean\n}\n\n/**\n * API for HMAC-based One-Time Password (HOTP, RFC 4226).\n * Tokens are derived from an incrementing counter rather than the clock.\n */\ntype HotpAPI = OtpAPI & {\n /**\n * Checks whether a token is valid for a specific counter value.\n * @param key - The shared secret used when the URI was created.\n * @param token - The 6-digit token to verify.\n * @param counter - The counter value that was used to generate the token.\n * @returns `true` if the token matches the given counter.\n * @throws {TypeError} If `counter` is not a number.\n * @throws {RangeError} If `counter` is not a non-negative safe integer.\n * @example\n * const ok = HOTP.verify(\"mysecret\", \"123456\", 42)\n */\n verify(key: string, token: string, counter: number): boolean\n}\n\n/** Encodes a UTF-8 string to Base32 (RFC 4648) without padding. */\nfunction base32Encode(input: string): string {\n let result = \"\", bits = 0, value = 0\n for (const byte of Buffer.from(input, \"utf8\")) {\n value = (value << 8) | byte\n bits += 8\n while (bits >= 5) result += base32Chars[(value >>> (bits -= 5)) & 31]\n }\n if (bits > 0) result += base32Chars[(value << (5 - bits)) & 31]\n return result\n}\n\nfunction assertNonEmptyKey(key: string): void {\n if (typeof key !== \"string\" || key.length === 0)\n throw new TypeError(\"key must be a non-empty string\")\n}\n\nfunction assertNonNegativeInt(value: number, name: string, safe?: boolean): void {\n if (typeof value !== \"number\" || Number.isNaN(value))\n throw new TypeError(`${name} must be a number`)\n if (value < 0 || !(safe ? Number.isSafeInteger(value) : Number.isInteger(value)))\n throw new RangeError(`${name} must be a non-negative${safe ? \" safe\" : \"\"} integer`)\n}\n\nfunction createOtpAuthUri(type: OtpType, key: string, label: string): string {\n assertNonEmptyKey(key)\n const searchParams = new URLSearchParams({ secret: base32Encode(key) })\n return `otpauth://${type}/${encodeURIComponent(label)}?${searchParams}`\n}\n\nasync function createQrCode(uri: string): Promise<string> {\n let qrCode: typeof import(\"qrcode\")\n try {\n qrCode = (await import(\"qrcode\")) as typeof import(\"qrcode\")\n } catch {\n throw new Error(\n 'qrcode is not installed. Run \"pnpm add qrcode\" to use this feature.',\n )\n }\n return qrCode.toDataURL(uri)\n}\n\n/**\n * Computes an HOTP value per RFC 4226 §5.\n * @param key - Plain-text secret used as the HMAC-SHA1 key.\n * @param counter - 8-byte big-endian counter value.\n * @param digits - Number of digits in the output (default 6).\n */\nfunction computeHotp(key: string, counter: number, digits = defaultDigits): string {\n assertNonNegativeInt(counter, \"counter\", true)\n const buf = Buffer.alloc(8)\n buf.writeBigUInt64BE(BigInt(counter))\n const digest = createHmac(\"sha1\", key).update(buf).digest()\n const offset = digest[digest.length - 1] & 0x0f\n const code = ((digest[offset] & 0x7f) << 24) | ((digest[offset + 1] & 0xff) << 16) | ((digest[offset + 2] & 0xff) << 8) | (digest[offset + 3] & 0xff)\n return (code % 10 ** digits).toString().padStart(digits, \"0\")\n}\n\n/**\n * Time-based One-Time Password helpers (RFC 6238).\n * @example\n * import { TOTP } from \"quickotp\"\n * const uri = TOTP.create(\"mysecret\", \"alice@example.com\")\n * const ok = TOTP.verify(\"mysecret\", userInput)\n */\nconst totp: TotpAPI = {\n create: (key, label) => createOtpAuthUri(\"totp\", key, label),\n qrcode: createQrCode,\n verify(key: string, token: string, window = 1): boolean {\n assertNonNegativeInt(window, \"window\")\n const counter = Math.floor(Date.now() / 1000 / totpPeriodSeconds)\n for (let i = -window; i <= window; i++)\n if (counter + i >= 0 && computeHotp(key, counter + i) === token) return true\n return false\n },\n}\n\n/**\n * HMAC-based One-Time Password helpers (RFC 4226).\n * @example\n * import { HOTP } from \"quickotp\"\n * const uri = HOTP.create(\"mysecret\", \"alice@example.com\")\n * const ok = HOTP.verify(\"mysecret\", userInput, counter)\n */\nconst hotp: HotpAPI = {\n create: (key, label) => createOtpAuthUri(\"hotp\", key, label),\n qrcode: createQrCode,\n verify: (key, token, counter) => computeHotp(key, counter) === token,\n}\n\nexport { totp as TOTP, hotp as HOTP }\n"],"mappings":"AAAA,OAAS,cAAAA,MAAkB,SAE3B,IAAMC,EAAc,mCACdC,EAAgB,EAChBC,EAAoB,GAsE1B,SAASC,EAAaC,EAAuB,CAC3C,IAAIC,EAAS,GAAIC,EAAO,EAAGC,EAAQ,EACnC,QAAWC,KAAQ,OAAO,KAAKJ,EAAO,MAAM,EAG1C,IAFAG,EAASA,GAAS,EAAKC,EACvBF,GAAQ,EACDA,GAAQ,GAAGD,GAAUL,EAAaO,KAAWD,GAAQ,GAAM,EAAE,EAEtE,OAAIA,EAAO,IAAGD,GAAUL,EAAaO,GAAU,EAAID,EAAS,EAAE,GACvDD,CACT,CAEA,SAASI,EAAkBC,EAAmB,CAC5C,GAAI,OAAOA,GAAQ,UAAYA,EAAI,SAAW,EAC5C,MAAM,IAAI,UAAU,gCAAgC,CACxD,CAEA,SAASC,EAAqBJ,EAAeK,EAAcC,EAAsB,CAC/E,GAAI,OAAON,GAAU,UAAY,OAAO,MAAMA,CAAK,EACjD,MAAM,IAAI,UAAU,GAAGK,CAAI,mBAAmB,EAChD,GAAIL,EAAQ,GAAK,EAAEM,EAAO,OAAO,cAAcN,CAAK,EAAI,OAAO,UAAUA,CAAK,GAC5E,MAAM,IAAI,WAAW,GAAGK,CAAI,0BAA0BC,EAAO,QAAU,EAAE,UAAU,CACvF,CAEA,SAASC,EAAiBC,EAAeL,EAAaM,EAAuB,CAC3EP,EAAkBC,CAAG,EACrB,IAAMO,EAAe,IAAI,gBAAgB,CAAE,OAAQd,EAAaO,CAAG,CAAE,CAAC,EACtE,MAAO,aAAaK,CAAI,IAAI,mBAAmBC,CAAK,CAAC,IAAIC,CAAY,EACvE,CAEA,eAAeC,EAAaC,EAA8B,CACxD,IAAIC,EACJ,GAAI,CACFA,EAAU,KAAM,QAAO,QAAQ,CACjC,MAAQ,CACN,MAAM,IAAI,MACR,qEACF,CACF,CACA,OAAOA,EAAO,UAAUD,CAAG,CAC7B,CAQA,SAASE,EAAYX,EAAaY,EAAiBC,EAAStB,EAAuB,CACjFU,EAAqBW,EAAS,UAAW,EAAI,EAC7C,IAAME,EAAM,OAAO,MAAM,CAAC,EAC1BA,EAAI,iBAAiB,OAAOF,CAAO,CAAC,EACpC,IAAMG,EAAS1B,EAAW,OAAQW,CAAG,EAAE,OAAOc,CAAG,EAAE,OAAO,EACpDE,EAASD,EAAOA,EAAO,OAAS,CAAC,EAAI,GAE3C,SADeA,EAAOC,CAAM,EAAI,MAAS,IAAQD,EAAOC,EAAS,CAAC,EAAI,MAAS,IAAQD,EAAOC,EAAS,CAAC,EAAI,MAAS,EAAMD,EAAOC,EAAS,CAAC,EAAI,KACjI,IAAMH,GAAQ,SAAS,EAAE,SAASA,EAAQ,GAAG,CAC9D,CASA,IAAMI,EAAgB,CACpB,OAAQ,CAACjB,EAAKM,IAAUF,EAAiB,OAAQJ,EAAKM,CAAK,EAC3D,OAAQE,EACR,OAAOR,EAAakB,EAAeC,EAAS,EAAY,CACtDlB,EAAqBkB,EAAQ,QAAQ,EACrC,IAAMP,EAAU,KAAK,MAAM,KAAK,IAAI,EAAI,IAAOpB,CAAiB,EAChE,QAAS4B,EAAI,CAACD,EAAQC,GAAKD,EAAQC,IACjC,GAAIR,EAAUQ,GAAK,GAAKT,EAAYX,EAAKY,EAAUQ,CAAC,IAAMF,EAAO,MAAO,GAC1E,MAAO,EACT,CACF,EASMG,EAAgB,CACpB,OAAQ,CAACrB,EAAKM,IAAUF,EAAiB,OAAQJ,EAAKM,CAAK,EAC3D,OAAQE,EACR,OAAQ,CAACR,EAAKkB,EAAON,IAAYD,EAAYX,EAAKY,CAAO,IAAMM,CACjE","names":["createHmac","base32Chars","defaultDigits","totpPeriodSeconds","base32Encode","input","result","bits","value","byte","assertNonEmptyKey","key","assertNonNegativeInt","name","safe","createOtpAuthUri","type","label","searchParams","createQrCode","uri","qrCode","computeHotp","counter","digits","buf","digest","offset","totp","token","window","i","hotp"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { createHmac, timingSafeEqual } from \"node:crypto\"\n\nconst base32Chars = \"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567\"\nconst defaultDigits = 6\nconst totpPeriodSeconds = 30\nconst maxTotpWindow = 10\nconst maxQrCodeUriLength = 2048\nconst tokenPattern = /^\\d{6}$/\n\ntype OtpType = \"totp\" | \"hotp\"\n\n/**\n * Shared methods for both {@link TotpAPI} and {@link HotpAPI}.\n */\ntype OtpAPI = {\n /**\n * Generates an `otpauth://` URI to register the secret with an authenticator app.\n * @param key - The shared secret (plain text; encoded to Base32 internally).\n * @param label - Account identifier shown in the authenticator (e.g. `\"alice@example.com\"`).\n * @returns An `otpauth://` URI string.\n * @throws {TypeError} If `key` is empty.\n */\n create(key: string, label: string): string\n\n /**\n * Converts an `otpauth://` URI into a Base64-encoded PNG data URL (QR code).\n * Requires the optional `qrcode` package (`pnpm add qrcode`).\n * @param uri - An `otpauth://` URI returned by `create`.\n * @returns A `data:image/png;base64,...` string ready for use in an `<img>` tag.\n * @throws {TypeError} If `uri` is not a valid `otpauth://` URI.\n * @throws {RangeError} If `uri` is too long.\n * @throws {Error} If the `qrcode` package is not installed.\n * @example\n * const dataUrl = await TOTP.qrcode(uri)\n * // \"data:image/png;base64,...\"\n */\n qrcode(uri: string): Promise<string>\n}\n\n/**\n * API for Time-based One-Time Password (TOTP, RFC 6238).\n * Tokens rotate every 30 seconds based on the current time.\n */\ntype TotpAPI = OtpAPI & {\n /**\n * Checks whether a token is valid for the current time window.\n * @param key - The shared secret used when the URI was created.\n * @param token - The 6-digit token entered by the user.\n * @param window - Number of 30-second periods to accept on either side of the current period (default `1`).\n * @returns `true` if the token matches any counter in the allowed window.\n * @throws {TypeError} If `key` is empty or `window` is not a number.\n * @throws {RangeError} If `window` is not an integer from `0` through `10`.\n * @example\n * const ok = TOTP.verify(sharedSecret, \"123456\")\n * const lenient = TOTP.verify(sharedSecret, \"123456\", 2) // ±2 periods\n */\n verify(key: string, token: string, window?: number): boolean\n}\n\n/**\n * API for HMAC-based One-Time Password (HOTP, RFC 4226).\n * Tokens are derived from an incrementing counter rather than the clock.\n */\ntype HotpAPI = OtpAPI & {\n /**\n * Checks whether a token is valid for a specific counter value.\n * @param key - The shared secret used when the URI was created.\n * @param token - The 6-digit token to verify.\n * @param counter - The counter value that was used to generate the token.\n * @returns `true` if the token matches the given counter.\n * @throws {TypeError} If `key` is empty or `counter` is not a number.\n * @throws {RangeError} If `counter` is not a non-negative safe integer.\n * @example\n * const ok = HOTP.verify(sharedSecret, \"123456\", 42)\n */\n verify(key: string, token: string, counter: number): boolean\n}\n\n/** Encodes a UTF-8 string to Base32 (RFC 4648) without padding. */\nfunction base32Encode(input: string): string {\n let result = \"\",\n bits = 0,\n value = 0\n for (const byte of Buffer.from(input, \"utf8\")) {\n value = (value << 8) | byte\n bits += 8\n while (bits >= 5) result += base32Chars[(value >>> (bits -= 5)) & 31]\n }\n if (bits > 0) result += base32Chars[(value << (5 - bits)) & 31]\n return result\n}\n\nfunction assertNonEmptyKey(key: string): void {\n if (typeof key !== \"string\" || key.length === 0)\n throw new TypeError(\"key must be a non-empty string\")\n}\n\nfunction assertNonNegativeInt(\n value: number,\n name: string,\n safe?: boolean,\n): void {\n if (typeof value !== \"number\" || Number.isNaN(value))\n throw new TypeError(`${name} must be a number`)\n if (\n value < 0 ||\n !(safe ? Number.isSafeInteger(value) : Number.isInteger(value))\n )\n throw new RangeError(\n `${name} must be a non-negative${safe ? \" safe\" : \"\"} integer`,\n )\n}\n\nfunction assertTotpWindow(window: number): void {\n assertNonNegativeInt(window, \"window\")\n if (window > maxTotpWindow)\n throw new RangeError(`window must be less than or equal to ${maxTotpWindow}`)\n}\n\nfunction assertOtpAuthUri(uri: string): void {\n if (typeof uri !== \"string\")\n throw new TypeError(\"uri must be an otpauth URI string\")\n if (uri.length > maxQrCodeUriLength)\n throw new RangeError(\n `uri must be ${maxQrCodeUriLength} characters or fewer`,\n )\n\n let parsedUri: URL\n try {\n parsedUri = new URL(uri)\n } catch {\n throw new TypeError(\"uri must be a valid otpauth URI\")\n }\n\n if (parsedUri.protocol !== \"otpauth:\")\n throw new TypeError(\"uri must use the otpauth protocol\")\n if (parsedUri.hostname !== \"totp\" && parsedUri.hostname !== \"hotp\")\n throw new TypeError(\"uri must be an otpauth totp or hotp URI\")\n if (!parsedUri.searchParams.get(\"secret\"))\n throw new TypeError(\"uri must include a secret parameter\")\n}\n\nfunction createOtpAuthUri(type: OtpType, key: string, label: string): string {\n assertNonEmptyKey(key)\n const params: Record<string, string> = { secret: base32Encode(key) }\n if (type === \"hotp\") params.counter = \"0\"\n const searchParams = new URLSearchParams(params)\n return `otpauth://${type}/${encodeURIComponent(label)}?${searchParams}`\n}\n\nasync function createQrCode(uri: string): Promise<string> {\n assertOtpAuthUri(uri)\n let qrCode: typeof import(\"qrcode\")\n try {\n qrCode = (await import(\"qrcode\")) as typeof import(\"qrcode\")\n } catch {\n throw new Error(\n 'qrcode is not installed. Run \"pnpm add qrcode\" to use this feature.',\n )\n }\n return qrCode.toDataURL(uri)\n}\n\nfunction verifyToken(key: string, token: string, counter: number): boolean {\n assertNonEmptyKey(key)\n if (typeof token !== \"string\") return false\n if (!tokenPattern.test(token)) return false\n return timingSafeEqual(\n Buffer.from(computeHotp(key, counter)),\n Buffer.from(token),\n )\n}\n\n/**\n * Computes an HOTP value per RFC 4226 §5.\n * @param key - Plain-text secret used as the HMAC-SHA1 key.\n * @param counter - 8-byte big-endian counter value.\n * @param digits - Number of digits in the output (default 6).\n */\nfunction computeHotp(\n key: string,\n counter: number,\n digits = defaultDigits,\n): string {\n assertNonNegativeInt(counter, \"counter\", true)\n const buf = Buffer.alloc(8)\n buf.writeBigUInt64BE(BigInt(counter))\n const digest = createHmac(\"sha1\", key).update(buf).digest()\n const offset = digest[digest.length - 1] & 0x0f\n const code =\n ((digest[offset] & 0x7f) << 24) |\n ((digest[offset + 1] & 0xff) << 16) |\n ((digest[offset + 2] & 0xff) << 8) |\n (digest[offset + 3] & 0xff)\n return (code % 10 ** digits).toString().padStart(digits, \"0\")\n}\n\n/**\n * Time-based One-Time Password helpers (RFC 6238).\n * @example\n * import { TOTP } from \"quickotp\"\n * const uri = TOTP.create(sharedSecret, \"alice@example.com\")\n * const ok = TOTP.verify(sharedSecret, userInput)\n */\nconst totp: TotpAPI = {\n create: (key, label) => createOtpAuthUri(\"totp\", key, label),\n qrcode: createQrCode,\n verify(key: string, token: string, window = 1): boolean {\n assertTotpWindow(window)\n const counter = Math.floor(Date.now() / 1000 / totpPeriodSeconds)\n for (let i = -window; i <= window; i++)\n if (counter + i >= 0 && verifyToken(key, token, counter + i)) return true\n return false\n },\n}\n\n/**\n * HMAC-based One-Time Password helpers (RFC 4226).\n * @example\n * import { HOTP } from \"quickotp\"\n * const uri = HOTP.create(sharedSecret, \"alice@example.com\")\n * const ok = HOTP.verify(sharedSecret, userInput, counter)\n */\nconst hotp: HotpAPI = {\n create: (key, label) => createOtpAuthUri(\"hotp\", key, label),\n qrcode: createQrCode,\n verify: verifyToken,\n}\n\nexport { totp as TOTP, hotp as HOTP }\n"],"mappings":"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","names":["createHmac","timingSafeEqual","base32Chars","defaultDigits","totpPeriodSeconds","maxTotpWindow","maxQrCodeUriLength","tokenPattern","base32Encode","input","result","bits","value","byte","assertNonEmptyKey","key","assertNonNegativeInt","name","safe","assertTotpWindow","window","assertOtpAuthUri","uri","parsedUri","createOtpAuthUri","type","label","params","searchParams","createQrCode","qrCode","verifyToken","token","counter","computeHotp","digits","buf","digest","offset","totp","i","hotp"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "quickotp",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.1",
|
|
4
4
|
"description": "A lightweight OTP library for Node.js with zero runtime dependencies.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@eslint/js": "^10.0.1",
|
|
49
|
-
"@types/node": "^
|
|
49
|
+
"@types/node": "^25.8.0",
|
|
50
50
|
"@types/qrcode": "^1.5.5",
|
|
51
51
|
"eslint": "^10.3.0",
|
|
52
52
|
"eslint-config-prettier": "^10.1.8",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"prettier": "3.8.3",
|
|
55
55
|
"tsup": "^8.5.1",
|
|
56
56
|
"tsx": "^4.0.0",
|
|
57
|
-
"typescript": "^
|
|
57
|
+
"typescript": "^6.0.3",
|
|
58
58
|
"typescript-eslint": "^8.59.2"
|
|
59
59
|
},
|
|
60
60
|
"scripts": {
|