{"_id":"@chax-at/totp","_rev":"7-4bd5e0d70be7002c4d221b2a9e459626","name":"@chax-at/totp","dist-tags":{"latest":"1.0.0"},"versions":{"0.1.0":{"name":"@chax-at/totp","version":"0.1.0","author":{"name":"chax.at - Challenge Accepted"},"license":"MIT","_id":"@chax-at/totp@0.1.0","maintainers":[{"name":"jan-chax","email":"jan.muratha@chax.at"},{"name":"chartinger","email":"c.k.hartinger@gmail.com"},{"name":"simonjimenez","email":"simon@chax.at"},{"name":"ian.luca","email":"email@ianluca.com"},{"name":"valerionn","email":"mattis@chax.at"}],"dist":{"shasum":"10f076a27094902ae6bbb1ff746dd185c6c48b76","tarball":"https://registry.npmjs.org/@chax-at/totp/-/totp-0.1.0.tgz","fileCount":12,"integrity":"sha512-I4mrQusQbkzKECdKNrKv1qyMHkSjyyKG3sljr3p/MabJXfPEtGJZOI1W/IBR9u0bdg9lk9f803OCOlQPaj/GSA==","signatures":[{"sig":"MEUCIQDoguJ/4Z9qSMOI9NpB1SFr9dHKRlwTl/KKrG+4SA3I0AIgSOIelPy3g6fyxndIglN0JjhOEjD7HbyyJtoVL0JHzzA=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":19182},"main":"lib/index.js","gitHead":"3332955908d785e179b3b53421998cff4c53a8a7","scripts":{"lint":"eslint \"{src,test}/**/*.ts\"","test":"jest","audit":"better-npm-audit audit -l high -r https://registry.npmjs.org/","build":"tsc","check":"npm run lint && npm run test","ci:lint":"npm run lint","ci:test":"npm run test","prepack":"npm run check","prepare":"npm run build","ci:build":"npm run build","prebuild":"rimraf lib","ci:version":"node -p \"require('./package.json').version\"","preversion":"npm run check","prettier:check":"prettier \"{src,test}/**/*.ts\" --check"},"typings":"lib/index.d.ts","_npmUser":{"name":"valerionn","email":"mattis@chax.at"},"_npmVersion":"9.8.1","description":"A simple TOTP package","directories":{},"lint-staged":{"{src,test}/**/*.ts":"prettier --write"},"_nodeVersion":"18.18.0","dependencies":{"rfc4648":"^1.5.3"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^29.6.1","eslint":"^8.45.0","rimraf":"^5.0.1","prettier":"3.0.3","@swc/core":"^1.3.69","@swc/jest":"^0.2.26","typescript":"^5.1.6","@types/jest":"^29.5.3","lint-staged":"^15.0.2","@chax-at/eslint-config":"^0.1.0","eslint-config-prettier":"^9.0.0","@typescript-eslint/parser":"^6.0.0","@typescript-eslint/eslint-plugin":"^6.0.0"},"_npmOperationalInternal":{"tmp":"tmp/totp_0.1.0_1709261826793_0.40569132526525054","host":"s3://npm-registry-packages"}},"0.1.1":{"name":"@chax-at/totp","version":"0.1.1","author":{"name":"chax.at - Challenge Accepted"},"license":"MIT","_id":"@chax-at/totp@0.1.1","maintainers":[{"name":"jan-chax","email":"jan.muratha@chax.at"},{"name":"chartinger","email":"c.k.hartinger@gmail.com"},{"name":"simonjimenez","email":"simon@chax.at"},{"name":"ian.luca","email":"email@ianluca.com"},{"name":"valerionn","email":"mattis@chax.at"}],"homepage":"https://github.com/chax-at/totp#readme","bugs":{"url":"https://github.com/chax-at/totp/issues"},"dist":{"shasum":"0a85dc0f888e061f18398fd7c539dc46ca799854","tarball":"https://registry.npmjs.org/@chax-at/totp/-/totp-0.1.1.tgz","fileCount":12,"integrity":"sha512-JY9zHKlHh12XZCqR1yRyoeBDcy9w644g/8LuI3FxFmsRqy/CK4ww0BCGbVZuHy+6f+pxUxXgCPqgnIfGAaN+ZQ==","signatures":[{"sig":"MEQCIQDNrt3ACykUYytc64UwBOD3e0KAviIaM6mrVHVgCXYwqQIfNTk7rFQParhpC3v+vmxx6h77siq8nIdNm6scRUCAwg==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":19739},"main":"lib/index.js","gitHead":"d1bc8ffdaa078bd6ab4ba59b1ebfe9515726969f","scripts":{"lint":"eslint \"{src,test}/**/*.ts\"","test":"jest","audit":"better-npm-audit audit -l high -r https://registry.npmjs.org/","build":"tsc","check":"npm run lint && npm run test","ci:lint":"npm run lint","ci:test":"npm run test","prepack":"npm run check","prepare":"npm run build","ci:build":"npm run build","prebuild":"rimraf lib","ci:version":"node -p \"require('./package.json').version\"","preversion":"npm run check","prettier:check":"prettier \"{src,test}/**/*.ts\" --check"},"typings":"lib/index.d.ts","_npmUser":{"name":"valerionn","email":"mattis@chax.at"},"repository":{"url":"git+https://github.com/chax-at/totp.git","type":"git"},"_npmVersion":"9.8.1","description":"A simple TOTP package","directories":{},"lint-staged":{"{src,test}/**/*.ts":"prettier --write"},"_nodeVersion":"18.18.0","dependencies":{"rfc4648":"^1.5.3"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^29.6.1","eslint":"^8.45.0","rimraf":"^5.0.1","prettier":"3.0.3","@swc/core":"^1.3.69","@swc/jest":"^0.2.26","typescript":"^5.1.6","@types/jest":"^29.5.3","lint-staged":"^15.0.2","@chax-at/eslint-config":"^0.1.0","eslint-config-prettier":"^9.0.0","@typescript-eslint/parser":"^6.0.0","@typescript-eslint/eslint-plugin":"^6.0.0"},"_npmOperationalInternal":{"tmp":"tmp/totp_0.1.1_1709414789683_0.2230829649715944","host":"s3://npm-registry-packages"}},"0.2.0":{"name":"@chax-at/totp","version":"0.2.0","author":{"name":"chax.at - Challenge Accepted"},"license":"MIT","_id":"@chax-at/totp@0.2.0","maintainers":[{"name":"jan-chax","email":"jan.muratha@chax.at"},{"name":"chartinger","email":"c.k.hartinger@gmail.com"},{"name":"simonjimenez","email":"simon@chax.at"},{"name":"ian.luca","email":"email@ianluca.com"},{"name":"valerionn","email":"mattis@chax.at"}],"homepage":"https://github.com/chax-at/totp#readme","bugs":{"url":"https://github.com/chax-at/totp/issues"},"dist":{"shasum":"6b35add18140ef818ec6bf8ad94d06d336f7e725","tarball":"https://registry.npmjs.org/@chax-at/totp/-/totp-0.2.0.tgz","fileCount":15,"integrity":"sha512-FBv4/1UEOPUuBZBOS2umhRJtHS3yGLtNKDzlpe0phwTiwwqPq4Z/Q+71amgqxDYG98Z8fRa58rMUyxyCpNxEgw==","signatures":[{"sig":"MEUCIQCMIYEMuo/QiAPCTYvqXKpd2BmQMzLOe74mmCePI+fTZQIgXzkEkypn7T0lQ7h3V/vG/AKZNZ3VvhqyYLyPGMJLU6s=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":28251},"main":"lib/index.js","gitHead":"9a9ec2ebcaaf976fc713611ee6952b297ca3fd61","scripts":{"lint":"eslint \"{src,test}/**/*.ts\"","test":"jest","audit":"better-npm-audit audit -l high -r https://registry.npmjs.org/","build":"tsc","check":"npm run lint && npm run test","ci:lint":"npm run lint","ci:test":"npm run test","prepack":"npm run check","prepare":"npm run build","ci:build":"npm run build","prebuild":"rimraf lib","ci:version":"node -p \"require('./package.json').version\"","preversion":"npm run check","prettier:check":"prettier \"{src,test}/**/*.ts\" --check"},"typings":"lib/index.d.ts","_npmUser":{"name":"valerionn","email":"mattis@chax.at"},"repository":{"url":"git+https://github.com/chax-at/totp.git","type":"git"},"_npmVersion":"9.8.1","description":"A simple TOTP package","directories":{},"lint-staged":{"{src,test}/**/*.ts":"prettier --write"},"_nodeVersion":"18.18.0","_hasShrinkwrap":false,"devDependencies":{"jest":"^29.6.1","eslint":"^8.45.0","rimraf":"^5.0.1","prettier":"3.0.3","@swc/core":"^1.3.69","@swc/jest":"^0.2.26","typescript":"^5.1.6","@types/jest":"^29.5.3","lint-staged":"^15.0.2","@chax-at/eslint-config":"^0.1.0","eslint-config-prettier":"^9.0.0","@typescript-eslint/parser":"^6.0.0","@typescript-eslint/eslint-plugin":"^6.0.0"},"_npmOperationalInternal":{"tmp":"tmp/totp_0.2.0_1709859440746_0.6718680728401727","host":"s3://npm-registry-packages"}},"0.2.1":{"name":"@chax-at/totp","version":"0.2.1","author":{"name":"chax.at - Challenge Accepted"},"license":"MIT","_id":"@chax-at/totp@0.2.1","maintainers":[{"name":"jan-chax","email":"jan.muratha@chax.at"},{"name":"chartinger","email":"c.k.hartinger@gmail.com"},{"name":"simonjimenez","email":"simon@chax.at"},{"name":"ian.luca","email":"email@ianluca.com"},{"name":"valerionn","email":"mattis@chax.at"}],"homepage":"https://github.com/chax-at/totp#readme","bugs":{"url":"https://github.com/chax-at/totp/issues"},"dist":{"shasum":"22778217ec402302639aee0bb7c8dd09c6c8eb7f","tarball":"https://registry.npmjs.org/@chax-at/totp/-/totp-0.2.1.tgz","fileCount":11,"integrity":"sha512-XJtQbMfQEaUMOpjlpBkcXD2+7/G4aR3r+CGeK3zeKNkaFJZ4Ff9yQJwI6/43OJYkHtDRHA+621xRacJbIKGkIQ==","signatures":[{"sig":"MEQCIBWPiCQAVhwudOFZz2TiCqLIdjkiL6QuWLkh8CUoBMzSAiAwTAjTAST19LVptlmOyF4y6Koua8EzvJrGaL3u07i5lw==","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":22205},"main":"lib/index.js","gitHead":"9a9ec2ebcaaf976fc713611ee6952b297ca3fd61","scripts":{"lint":"eslint \"{src,test}/**/*.ts\"","test":"jest","audit":"better-npm-audit audit -l high -r https://registry.npmjs.org/","build":"tsc","check":"npm run lint && npm run test","ci:lint":"npm run lint","ci:test":"npm run test","prepack":"npm run check","prepare":"npm run build","ci:build":"npm run build","prebuild":"rimraf lib","ci:version":"node -p \"require('./package.json').version\"","preversion":"npm run check","prettier:check":"prettier \"{src,test}/**/*.ts\" --check"},"typings":"lib/index.d.ts","_npmUser":{"name":"valerionn","email":"mattis@chax.at"},"repository":{"url":"git+https://github.com/chax-at/totp.git","type":"git"},"_npmVersion":"9.8.1","description":"A simple TOTP package","directories":{},"lint-staged":{"{src,test}/**/*.ts":"prettier --write"},"_nodeVersion":"18.18.0","_hasShrinkwrap":false,"devDependencies":{"jest":"^29.6.1","eslint":"^8.45.0","rimraf":"^5.0.1","prettier":"3.0.3","@swc/core":"^1.3.69","@swc/jest":"^0.2.26","typescript":"^5.1.6","@types/jest":"^29.5.3","lint-staged":"^15.0.2","@chax-at/eslint-config":"^0.1.0","eslint-config-prettier":"^9.0.0","@typescript-eslint/parser":"^6.0.0","@typescript-eslint/eslint-plugin":"^6.0.0"},"_npmOperationalInternal":{"tmp":"tmp/totp_0.2.1_1709859894385_0.8669425942236448","host":"s3://npm-registry-packages"}},"1.0.0":{"name":"@chax-at/totp","version":"1.0.0","author":{"name":"chax.at - Challenge Accepted"},"license":"MIT","_id":"@chax-at/totp@1.0.0","maintainers":[{"name":"jan-chax","email":"jan.muratha@chax.at"},{"name":"chartinger","email":"c.k.hartinger@gmail.com"},{"name":"simonjimenez","email":"simon@chax.at"},{"name":"valerionn","email":"mattis@chax.at"}],"homepage":"https://github.com/chax-at/totp#readme","bugs":{"url":"https://github.com/chax-at/totp/issues"},"dist":{"shasum":"ac35eb354e87a51c2121e9df4f1ffd0b288fc367","tarball":"https://registry.npmjs.org/@chax-at/totp/-/totp-1.0.0.tgz","fileCount":11,"integrity":"sha512-Uv+TU/P+o0iPGLkZr7lsi0xOFhVtr8shp8m2R9M8FKp5sSQC4URrzjFGoY/P4CSwv+293s3zeWdmrH5wAGRYOg==","signatures":[{"sig":"MEYCIQDWaqC/xM0pUa+0Sgubo22czM9ycc7ImmXUS1P7b6QWVwIhAJpN2GWkssF2Kdtt6EJoQgvcfhCZZkIPFl/I8laJefiu","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@chax-at%2ftotp@1.0.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":24139},"main":"lib/index.js","engines":{"node":">= 20"},"gitHead":"4926b7a3d2f7e20045617486fce76eb2eb123cfd","scripts":{"lint":"eslint \"{src,test}/**/*.ts\"","test":"jest","audit":"better-npm-audit audit -l high -r https://registry.npmjs.org/","build":"tsc","check":"npm run lint && npm run test","ci:lint":"npm run lint","ci:test":"npm run test","prepack":"npm run check","prepare":"npm run build","ci:build":"npm run build","prebuild":"rimraf lib","ci:version":"node -p \"require('./package.json').version\"","preversion":"npm run check","prettier:check":"prettier \"{src,test}/**/*.ts\" --check"},"typings":"lib/index.d.ts","_npmUser":{"name":"valerionn","actor":{"name":"valerionn","type":"user","email":"mattis@chax.at"},"email":"mattis@chax.at"},"repository":{"url":"git+https://github.com/chax-at/totp.git","type":"git"},"_npmVersion":"10.9.2","description":"A simple TOTP package","directories":{},"lint-staged":{"{src,test}/**/*.ts":"prettier --write"},"_nodeVersion":"22.16.0","_hasShrinkwrap":false,"devDependencies":{"jest":"^29.7.0","eslint":"^9.29.0","rimraf":"^6.0.1","prettier":"3.5.3","@swc/core":"^1.12.1","@swc/jest":"^0.2.38","typescript":"^5.8.3","@types/jest":"^29.5.14","lint-staged":"^15.5.2","@chax-at/eslint-config":"^0.1.1","eslint-config-prettier":"^10.1.5","@typescript-eslint/parser":"^8.34.1","@typescript-eslint/eslint-plugin":"^8.34.1"},"_npmOperationalInternal":{"tmp":"tmp/totp_1.0.0_1750161344091_0.20805310991382608","host":"s3://npm-registry-packages-npm-production"}}},"time":{"created":"2024-03-01T02:57:06.685Z","modified":"2025-09-24T11:13:30.336Z","0.1.0":"2024-03-01T02:57:06.944Z","0.1.1":"2024-03-02T21:26:29.862Z","0.2.0":"2024-03-08T00:57:20.903Z","0.2.1":"2024-03-08T01:04:54.579Z","1.0.0":"2025-06-17T11:55:44.274Z"},"bugs":{"url":"https://github.com/chax-at/totp/issues"},"author":{"name":"chax.at - Challenge Accepted"},"license":"MIT","homepage":"https://github.com/chax-at/totp#readme","repository":{"url":"git+https://github.com/chax-at/totp.git","type":"git"},"description":"A simple TOTP package","maintainers":[{"email":"c.k.hartinger@gmail.com","name":"chartinger"},{"email":"simon@chax.at","name":"simonjimenez"},{"email":"mattis@chax.at","name":"valerionn"}],"readme":"# @chax-at/totp\n\nA simple package which can be used to implement TOTP two-factor codes.\nImplements the default use case (6-digit codes, 30s time steps, 20-byte/160-bit base32-encoded secret, using SHA1) but doesn't provide much customization.\n\nThese default values are the most common and should be supported in all authenticator apps.\nIf you're wondering why you may want to use this package instead of others, check the section [Why yet another TOTP package](#why-yet-another-totp-package) at the bottom.\n\n## Usage\nInstall the package by running\n```\nnpm i @chax-at/totp\n```\n\n\n### TotpManager\nThen, you can create a `TotpManager` to generate secrets and verify codes. You can create a `TotpManager` like this: \n\n```ts\nimport { TotpManager } from '@chax-at/totp';\n\nconst totpManager = new TotpManager({\n  // This issuer will be shown in the user's authenticator app and should be something like your company/service name\n  issuer: 'ExampleWebsite',\n  // You can also specify `verifyWindow` here. Defaults to 2 which means that the TOTP code at current time is valid as well as the previous 2 and next 2\n});\n```\n\n### Creating a secret\nAssuming user `user@example.com` wants to enable two factor using their TOTP app. You now can generate a secret for this user:\n```ts\n// Include the user's account name/email here which is shown in their authenticator app when scanning the QR code\nconst { secret, otpauthUri } = totpManager.generateSecret('user@example.com');\n```\n* Save the returned `secret` for the user in your database. It is recommended to encrypt this secret (note that you can't hash it because you need it in plaintext to verify codes).\n > We also RECOMMEND storing the keys securely in the validation system,\n       and, more specifically, encrypting them using tamper-resistant\n       hardware encryption and exposing them only when required: for\n       example, the key is decrypted when needed to verify an OTP value, and\n       re-encrypted immediately to limit exposure in the RAM to a short\n       period of time.\n[RFC 6238, 5.1](https://datatracker.ietf.org/doc/html/rfc6238#section-5.1)\n* Generate a QR code containing the returned `otpauthUri` and show it to your user. They can then scan it with their authenticator app.\n* You may also show the `secret` so they can manually register your application in case the QR code doesn't work.\n* You should require a user to enter one valid code before actually enabling two-factor authentication.\n\n### Verifying a TOTP code\nAssuming the user entered the code `123456`, then you can verify it like this:\n```ts\n// Returns whether the code is valid at the current time. Also returns true if it matches the previous/next [verifyWindow] codes as defined above\nconst isValid = totpManager.verify(secret, '123456');\n```\n* You should rate limit the TOTP verification so that attackers can't brute-force TOTP codes (there are only 1 000 000 possible codes)\n* You should save (and deny) \"used\" correct tokens per user for at least `30s + 30s * verifyWindow [default 2]` so that your one-time password (the \"OTP\" part of TOTP) can actually only be used one time\n>    Note that a prover may send the same OTP inside a given time-step\nwindow multiple times to a verifier.  The verifier MUST NOT accept\nthe second attempt of the OTP after the successful validation has\nbeen issued for the first OTP, which ensures one-time only use of an\nOTP.\n[RFC 6238, 5.2](https://datatracker.ietf.org/doc/html/rfc6238#section-5.2)\n\n## Why yet another TOTP package?\nThere are some widely used node TOTP/2FA packages available. However, when I researched them, all of them seemed to be unmaintained and have some areas that could be improved (including security-critical issues).\n\nYour use-case might vary (e.g. if you need more customization than this package can offer), but here are the reasons why I didn't use other packages and decided to write my own:\n\n* **speakeasy** seems to be the most recommended package in tutorials. However, it doesn't generate secure secrets.\n  * speakeasy forgot that the letter \"j\" exists, therefore it doesn't appear in the secret at all \\[[source](https://github.com/speakeasyjs/speakeasy/blob/cff2bb42cde5e74c43493a8f26b20e52960df531/index.js#L563)\\]\n  * speakeasy introduces a bias in the secret which prevents it from being completey random, making attacks easier. There is a [PR](https://github.com/speakeasyjs/speakeasy/pull/92) attempting to fix it - but the package is not maintained.\n* **otplib** has even more weekly downloads and doesn't have a bias in their secret generation. However, their generated secrets are way too short (unless you specify a length) and don't even meet the minimum defined in the TOTP specification.\n  * RFC 4226 states that \"The length of the shared secret MUST be at least 128 bits. This document RECOMMENDs a shared secret length of 160 bits.\"\n  * This means that a secret must be at least 16 bytes long, and is recommended to be 20 bytes long (which this @chax-at/totp uses)\n  * However, otplib changed their default (in a [20k lines changed commit](https://github.com/yeojz/otplib/commit/b088efe9da45e102e59b5cd2c0df5bddf80c5a92)) in 12.0.0 from a secure 20 bytes to just 10 bytes (80 bits) \\[[source](https://github.com/yeojz/otplib/blob/v12.0.0/packages/otplib-core/src/authenticator.ts#L265-L267)]\n    * This was not mentioned in the upgrade guide\n    * The [issue mentioning this](https://github.com/yeojz/otplib/issues/671) from 2022 has no response from the maintainer \n  * I can't say much about the rest of the library because the code is split up in 12 packages (compared to the 2 files with 40/70 lines each in @chax-at/totp) and I couldn't track all those abstraction layers by clicking through files in GitHub\n* **node-2fa** doesn't have obvious security issues. However, there are still some things stopping me from using it.\n  * `generateSecret` also returns a helpful QR code link - that immediately sends your secret to Google. You should really generate your own QR codes instead of sending your secrets to Google.\n  * It depends on `notp` for key generation\n    * `notp` contains code that has been deprecated in node, e.g. `new Buffer(...)`. This is not a security issue - but will show a deprecation warning for everyone using their package. There is a [PR](https://github.com/guyht/notp/pull/59) from 2021 which has not been merged yet.\n  * This package doesn't have a changelog (or at least I couldn't find it)\n* No other package uses constant-time comparisons for checking the code. I don't think that timing attacks are very likely, but using a constant-time comparison function prevents these attacks.\n","readmeFilename":"README.md"}