Comparing version 11.1.0-0 to 12.0.0-0
{ | ||
"name": "otplib", | ||
"version": "11.1.0-0", | ||
"version": "12.0.0-0", | ||
"description": "HMAC-based (HOTP) and Time-based (TOTP) One-Time Password library", | ||
"main": "./index.js", | ||
"typings": "./index.d.ts", | ||
"main": "./core/index.js", | ||
"typings": "./core/index.d.ts", | ||
"scripts": { | ||
"build": "./scripts/build.sh", | ||
"build:bundles": "webpack --config ./scripts/webpack.config.js", | ||
"build:docs": "jsdoc --configure jsdoc.json", | ||
"build:modules": "rollup -c scripts/rollup.config.js", | ||
"build:site": "./scripts/build-site.sh", | ||
"clean": "rimraf dist coverage docs", | ||
"lint:js": "eslint \"packages/**/**?(.spec|).js\"", | ||
"lint:format": "prettier --write \"{packages,scripts,site/scripts,site/public}/**/**.js\"", | ||
"lint:ts": "dtslint ./packages/types-ts", | ||
"clean": "rimraf builds coverage", | ||
"format": "prettier --write \"{packages,scripts,configs}/**/*.{ts,tsx,js,jsx,json,md,mdx}\"", | ||
"lint": "tsc --noEmit", | ||
"setup": "./scripts/setup.sh", | ||
"test": "jest --coverage --runInBand", | ||
"test:nocover": "jest", | ||
"test:watch": "jest --coverage --watch", | ||
"test:browser": "./scripts/test-browser.sh", | ||
"test:module": "./scripts/test-module.sh", | ||
"test:node8": "./scripts/test-node8.sh", | ||
"test:watch": "jest --watch", | ||
"update:release-notes": "npx conventional-github-releaser -p angular", | ||
"upload:coverage": "cat ./coverage/lcov.info | coveralls" | ||
"upload:coverage": "cat ./coverage/lcov.info | npx coveralls" | ||
}, | ||
@@ -47,30 +46,43 @@ "repository": { | ||
"devDependencies": { | ||
"@babel/cli": "^7.0.0", | ||
"@babel/core": "^7.0.0", | ||
"@babel/preset-env": "^7.0.0", | ||
"babel-loader": "^8.0.0", | ||
"babel-plugin-module-resolver": "^3.1.0", | ||
"coveralls": "^3.0.0", | ||
"create-hmac": "^1.1.4", | ||
"dtslint": "^0.3.0", | ||
"eslint": "^5.0.0", | ||
"eslint-config-prettier": "^3.0.0", | ||
"eslint-plugin-prettier": "^3.0.0", | ||
"jest": "^24.0.0", | ||
"jsdoc": "^3.4.3", | ||
"minami": "^1.1.1", | ||
"prettier": "1.16.1", | ||
"rimraf": "^2.6.1", | ||
"rollup": "^1.0.0", | ||
"rollup-plugin-cleanup": "^3.0.0", | ||
"rollup-plugin-node-resolve": "^4.0.0", | ||
"webpack": "^4.29.6", | ||
"webpack-cli": "^3.2.3" | ||
"@babel/core": "^7.5.5", | ||
"@babel/plugin-transform-runtime": "^7.5.5", | ||
"@babel/preset-env": "^7.5.5", | ||
"@babel/preset-typescript": "^7.3.3", | ||
"@babel/runtime": "^7.5.5", | ||
"@types/crypto-js": "^3.1.43", | ||
"@types/jest": "^24.0.18", | ||
"@types/node": "^12.7.2", | ||
"@typescript-eslint/eslint-plugin": "^2.0.0", | ||
"@typescript-eslint/parser": "^2.0.0", | ||
"babel-jest": "^24.9.0", | ||
"babel-loader": "^8.0.6", | ||
"base32-decode": "^1.0.0", | ||
"base32-encode": "^1.1.1", | ||
"create-hmac": "^1.1.7", | ||
"crypto-js": "^3.1.9-1", | ||
"eslint": "^6.2.2", | ||
"eslint-config-prettier": "^6.1.0", | ||
"eslint-plugin-prettier": "^3.1.0", | ||
"jest": "^24.9.0", | ||
"prettier": "^1.18.2", | ||
"rimraf": "^3.0.0", | ||
"rollup": "^1.20.1", | ||
"rollup-plugin-babel": "^4.3.3", | ||
"rollup-plugin-cleanup": "^3.1.1", | ||
"rollup-plugin-commonjs": "^10.0.2", | ||
"rollup-plugin-node-resolve": "^5.2.0", | ||
"thirty-two": "^1.0.2", | ||
"typescript": "^3.5.3", | ||
"webpack": "^4.39.2", | ||
"webpack-cli": "^3.3.7" | ||
}, | ||
"dependencies": { | ||
"thirty-two": "1.0.2" | ||
}, | ||
"dependencies": {}, | ||
"jest": { | ||
"collectCoverageFrom": [ | ||
"packages/**/*.{js,ts}", | ||
"!**/node_modules/**", | ||
"!packages/tests-*/*", | ||
"!packages/otplib-preset-browser/*" | ||
], | ||
"coverageDirectory": "./coverage/", | ||
"coveragePathIgnorePatterns": [], | ||
"modulePaths": [ | ||
@@ -89,5 +101,9 @@ "<rootDir>/packages/" | ||
"/node_modules/", | ||
"/packages/types-ts/" | ||
"/packages/tests-data/", | ||
"/packages/tests-suites/" | ||
], | ||
"testURL": "http://localhost" | ||
"testURL": "http://localhost", | ||
"transform": { | ||
"^.+\\.(js|ts)$": "babel-jest" | ||
} | ||
}, | ||
@@ -97,5 +113,5 @@ "repl": [ | ||
"name": "otplib", | ||
"module": "./dist/otplib/index" | ||
"module": "./builds/otplib/node" | ||
} | ||
] | ||
} |
761
README.md
@@ -5,7 +5,7 @@ # otplib | ||
[![npm][npm-badge]][npm-link] | ||
[![Build Status][circle-badge]][circle-link] | ||
[![Coverage Status][coveralls-badge]][coveralls-link] | ||
[![npm downloads][npm-downloads-badge]][npm-link] | ||
[![TypeScript Support][type-ts-badge]][type-ts-link] | ||
[![npm][badge-npm]][project-npm] | ||
[![Build Status][badge-circle]][project-circle] | ||
[![Coverage Status][badge-coveralls]][project-coveralls] | ||
[![npm downloads][badge-npm-downloads]][project-npm] | ||
[![TypeScript Support][badge-type-ts]][project-docs] | ||
@@ -17,27 +17,44 @@ --- | ||
- [About](#about) | ||
- [Demo and Documentation](#demo-and-documentation) | ||
- [Installation](#installation) | ||
- [Type Definitions](#type-definitions) | ||
- [Upgrading](#upgrading) | ||
- [Features](#features) | ||
- [Quick Start](#quick-start) | ||
- [In Node.js](#in-nodejs) | ||
- [In Browser](#in-browser) | ||
- [Migration Guide](#migration-guide) | ||
- [Migrating from v11.x](#migrating-from-v11x) | ||
- [Getting Started](#getting-started) | ||
- [In node](#in-node) | ||
- [Using specific OTP implementations](#using-specific-otp-implementations) | ||
- [Using classes](#using-classes) | ||
- [Using with Expo.io](#using-with-expoio) | ||
- [In browser](#in-browser) | ||
- [Browser Compatibility](#browser-compatibility) | ||
- [Advanced Usage](#advanced-usage) | ||
- [Install the Package](#install-the-package) | ||
- [Choose Your Plugins](#choose-your-plugins) | ||
- [Adding Crypto](#adding-crypto) | ||
- [Adding Base32](#adding-base32) | ||
- [Initialise your Instance](#initialise-your-instance) | ||
- [Using Classes](#using-classes) | ||
- [Using Functions](#using-functions) | ||
- [Available Options](#available-options) | ||
- [HOTP Options](#hotp-options) | ||
- [TOTP Options](#totp-options) | ||
- [Authenticator Options](#authenticator-options) | ||
- [Async Options](#async-options) | ||
- [Available Packages](#available-packages) | ||
- [Core](#core) | ||
- [Other Bundles](#other-bundles) | ||
- [Notes](#notes) | ||
- [Setting Custom Options](#setting-custom-options) | ||
- [Available Options](#available-options) | ||
- [Seed / secret length](#seed--secret-length) | ||
- [Core (Async)](#core-async) | ||
- [Plugins](#plugins) | ||
- [Crypto Plugins](#crypto-plugins) | ||
- [Base32 Plugins](#base32-plugins) | ||
- [Presets](#presets) | ||
- [Appendix](#appendix) | ||
- [Type Definitions](#type-definitions) | ||
- [Async Support](#async-support) | ||
- [Using Async Replacements](#using-async-replacements) | ||
- [Async over Sync Methods](#async-over-sync-methods) | ||
- [Browser Compatiblity](#browser-compatiblity) | ||
- [Browser bundle size](#browser-bundle-size) | ||
- [Length of Secrets](#length-of-secrets) | ||
- [Google Authenticator](#google-authenticator) | ||
- [Difference between Authenticator and TOTP](#difference-between-authenticator-and-totp) | ||
- [Base32 Keys and RFC3548](#base32-keys-and-rfc3548) | ||
- [RFC3548 Base32](#rfc3548-base32) | ||
- [Displaying a QR code](#displaying-a-qr-code) | ||
- [Getting Time Remaining / Time Used](#getting-time-remaining--time-used) | ||
- [Getting Time Remaining / Time Used](#getting-time-remaining--time-used) | ||
- [Using with Expo](#using-with-expo) | ||
- [Exploring with local-repl](#exploring-with-local-repl) | ||
- [Contributing](#contributing) | ||
- [Contributors](#contributors) | ||
- [License](#license) | ||
@@ -49,5 +66,3 @@ | ||
`otplib` is a JavaScript One Time Password (OTP) library. | ||
It provides both functional and class based interfaces for | ||
dealing with OTP generation and verification. | ||
`otplib` is a JavaScript One Time Password (OTP) library for OTP generation and verification. | ||
@@ -57,6 +72,6 @@ It implements both [HOTP][rfc-4226-wiki] - [RFC 4226][rfc-4226] | ||
and are tested against the test vectors provided in their respective RFC specifications. | ||
These datasets can be found in the `packages/tests` folder. | ||
These datasets can be found in the `packages/tests-data` folder. | ||
- [RFC 4226 Dataset](https://github.com/yeojz/otplib/blob/master/packages/tests/rfc4226.js) | ||
- [RFC 6238 Dataset](https://github.com/yeojz/otplib/blob/master/packages/tests/rfc6238.js) | ||
- [RFC 4226 Dataset][rfc-4226-dataset] | ||
- [RFC 6238 Dataset][rfc-6238-dataset] | ||
@@ -66,37 +81,78 @@ This library is also compatible with [Google Authenticator](https://github.com/google/google-authenticator), | ||
## Demo and Documentation | ||
## Features | ||
- [Documentation][project-docs] | ||
- [Demo][project-web] | ||
- [FAQ / Common Issues](https://github.com/yeojz/otplib/wiki/FAQ) | ||
- [List of available methods][type-ts-file] (documented via TypeScript) | ||
- [Examples][project-examples] | ||
- Typescript support | ||
- [Class][link-mdn-classes] interfaces | ||
- [Function][link-mdn-functions] interfaces | ||
- [Async][link-mdn-async] interfaces | ||
- Pluggable modules (crypto / base32) | ||
- `crypto (node)` | ||
- `crypto-js` | ||
- `@ronomon/crypto-async` | ||
- `thirty-two` | ||
- `base32-encode` + `base32-decode` | ||
- Presets provided | ||
- `browser` | ||
- `default (node)` | ||
- `default-async (same as default, but with async methods)` | ||
- `v11 (adapter for previous version)` | ||
## Installation | ||
## Quick Start | ||
Install the library via: | ||
References: | ||
- [API Documentation][project-api] | ||
- [Demo Website][project-web] | ||
### In Node.js | ||
```bash | ||
$ npm install otplib --save | ||
npm install otplib thirty-two | ||
``` | ||
# To install the Release Candidates: | ||
$ npm install otplib@next --save | ||
```js | ||
import { authenticator } from 'otplib/preset-default'; | ||
const secret = 'KVKFKRCPNZQUYMLXOVYDSQKJKZDTSRLD'; | ||
// Alternative: const secret = authenticator.generateSecret(); | ||
const token = authenticator.generate(secret); | ||
try { | ||
const isValid = authenticator.check(token, secret); | ||
// or | ||
const isValid = authenticator.verify({ token, secret }); | ||
} catch (err) { | ||
// Possible errors | ||
// - options validation | ||
// - "Invalid input - it is not base32 encoded string" (if thiry-two is used) | ||
console.error(err); | ||
} | ||
``` | ||
| Release Type | Version | | ||
| :---------------- | :--------------------------------- | | ||
| Current / Stable | [![npm][npm-badge]][npm-link] | | ||
| Release Candidate | [![npm][npm-next-badge]][npm-link] | | ||
### In Browser | ||
### Type Definitions | ||
The browser preset is a self contained `umd` module with `Buffer` split out as an external dependency. | ||
As such, there are 2 scripts required: `preset-browser/index.js` and `preset-browser/buffer.js`. | ||
`TypeScript` support was introduced in `v10.0.0` | ||
```html | ||
<script src="https://unpkg.com/otplib@^12.0.0/preset-browser/buffer.js"></script> | ||
<script src="https://unpkg.com/otplib@^12.0.0/preset-browser/index.js"></script> | ||
```bash | ||
# Additional dependencies needed for TypeScript | ||
$ npm install @types/node | ||
<script type="text/javascript"> | ||
// window.otplib.authenticator | ||
// window.otplib.hotp | ||
// window.otplib.totp | ||
</script> | ||
``` | ||
## Upgrading | ||
The `buffer.js` provided by this library is a cached copy | ||
from [https://www.npmjs.com/package/buffer][link-npm-buffer]. | ||
You can also download and include the latest version via their project page. | ||
In the above example, we are directly using the scripts hosted by `unpkg.com`. | ||
You can also `npm install otplib` and get a copy from the `node_modules/otplib/preset-browser` folder. | ||
## Migration Guide | ||
This library follows `semver`. As such, major version bumps usually mean API changes or behavior changes. | ||
@@ -106,201 +162,420 @@ Please check [upgrade notes](https://github.com/yeojz/otplib/wiki/upgrade-notes) for more information, | ||
You might also want to check out the release notes associated with each tagged versions | ||
Check out the release notes associated with each tagged versions | ||
in the [releases](https://github.com/yeojz/otplib/releases) page. | ||
### Migrating from v11.x | ||
> v12.x is a huge architectural and language rewrite. Please check out the docs if you are migrating. | ||
> A preset adapter is available to provide methods that behave like `v11.x` of `otplib`. | ||
Link to [v11.x README.md][project-v11-readme] and [v11.x API Docs][project-v11-api]. | ||
```js | ||
// Update | ||
import { authenticator } from 'otplib'; // v11.x | ||
// to | ||
import { authenticator } from 'otplib/preset-v11'; | ||
// There should be no changes to your current code. | ||
// However, deprecated or modified class methods will have console.warn. | ||
``` | ||
## Getting Started | ||
### In node | ||
This is a more in-depth setup guide for installing, configuring and customising | ||
your dependencies for the library. | ||
Check out the [Quick Start][docs-quick-start] guide if you do need / want | ||
to customise any dependencies from the presets. | ||
### Install the Package | ||
```bash | ||
npm install otplib | ||
``` | ||
| Release Type | Version | Command | | ||
| :---------------- | :------------------------------------ | ------------------------- | | ||
| Current / Stable | [![npm][badge-npm]][project-npm] | `npm install otplib` | | ||
| Release Candidate | [![npm][badge-npm-next]][project-npm] | `npm install otplib@next` | | ||
### Choose Your Plugins | ||
#### Adding Crypto | ||
The crypto modules are used to generate the digest used to derive the OTP tokens from. | ||
By default, Node.js has inbuilt `crypto` functionality, but you might want to replace it | ||
for certain environments that do not support it. | ||
Currently out-of-the-box, there are some [Crypto Plugins][docs-plugins-crypto] included. | ||
Install the dependencies for one of them. | ||
```bash | ||
# Choose either | ||
# Node.js crypto (you don't need to install anything else - http://nodejs.org/api/crypto.html) | ||
# or | ||
npm install crypto-js | ||
``` | ||
#### Adding Base32 | ||
If you're using Google Authenticator, you'll need a base32 module for | ||
encoding and decoding your secrets. | ||
Currently out-of-the-box, there are some [Base32 Plugins][docs-plugins-base32] included. | ||
Install the dependencies for one of them. | ||
```bash | ||
# Choose either | ||
npm install thirty-two | ||
# or | ||
npm install base32-encode base32-decode | ||
``` | ||
### Initialise your Instance | ||
#### Using Classes | ||
```js | ||
import otplib from 'otplib'; | ||
import { HOTP, TOTP, Authenticator } from 'otplib'; | ||
const secret = 'KVKFKRCPNZQUYMLXOVYDSQKJKZDTSRLD'; | ||
// Alternatively: const secret = otplib.authenticator.generateSecret(); | ||
// Base32 Plugin | ||
// for thirty-two | ||
import { keyDecoder, keyEncoder } from 'otplib/plugin-thirty-two'; | ||
// for base32-encode and base32-decode | ||
import { keyDecoder, keyEncoder } from 'otplib/plugin-base32-enc-dec'; | ||
const token = otplib.authenticator.generate(secret); | ||
// Crypto Plugin | ||
// for node crypto | ||
import { createDigest, createRandomBytes } from 'otplib/plugin-crypto'; | ||
// for crypto-js | ||
import { createDigest, createRandomBytes } from 'otplib/plugin-crypto-js'; | ||
try { | ||
const isValid = otplib.authenticator.check(token, secret); | ||
// or | ||
const isValid = otplib.authenticator.verify({ token, secret }); | ||
} catch (err) { | ||
// Error possibly thrown by the thirty-two package | ||
// 'Invalid input - it is not base32 encoded string' | ||
console.error(err); | ||
} | ||
// Setup an OTP instance which you need | ||
const hotp = new HOTP({ createDigest }); | ||
const totp = new TOTP({ createDigest }); | ||
const authenticator = new Authenticator({ | ||
createDigest, | ||
createRandomBytes, | ||
keyDecoder, | ||
keyEncoder | ||
}); | ||
// Go forth and generate tokens | ||
const token = hotp.generate(YOUR_SECRET, 0); | ||
const token = totp.generate(YOUR_SECRET); | ||
const token = authenticator.generate(YOUR_SECRET); | ||
``` | ||
#### Using specific OTP implementations | ||
#### Using Functions | ||
If you want to include a specific OTP specification, you can import it directly: | ||
Alternatively, if you are using the functions directly instead of the classes, | ||
pass these as options into the functions. | ||
```js | ||
import hotp from 'otplib/hotp'; | ||
import totp from 'otplib/totp'; | ||
import authenticator from 'otplib/authenticator'; | ||
import { | ||
hotpOptions, | ||
hotpToken, | ||
totpOptions, | ||
totpToken, | ||
authenticatorOptions, | ||
authenticatorToken | ||
} from 'otplib/core'; | ||
// As with classes, import your desired Base32 Plugin and Crypto Plugin. | ||
// import ... | ||
// Go forth and generate tokens | ||
const token = hotpToken(YOUR_SECRET, 0, hotpOptions({ createDigest)); | ||
const token = totpToken(YOUR_SECRET, totpOptions({ createDigest)); | ||
const token = authenticatorToken(YOUR_SECRET, authenticatorOptions({ | ||
createDigest, | ||
createRandomBytes, | ||
keyDecoder, | ||
keyEncoder | ||
)); | ||
``` | ||
**Important**: If you import the libraries directly, you will have to provide a crypto | ||
solution (this is to allow custom crypto solutions), as long as they implement `createHmac` and `randomBytes`. | ||
Take a look at the [browser implementation](https://github.com/yeojz/otplib/blob/master/packages/otplib-browser) | ||
of this package as an example. | ||
## Available Options | ||
For **example**: | ||
All instantiated classes will have their options inherited from their respective options | ||
generator. i.e. HOTP from `hotpOptions`, TOTP from `totpOptions` | ||
and Authenticator from `authenticatorOptions`. | ||
All OTP classes have an object setter and getter method to override these default options. | ||
For example, | ||
```js | ||
import authenticator from 'otplib/authenticator'; | ||
import crypto from 'crypto'; | ||
import { authenticator } from 'otplib/preset-default'; | ||
authenticator.options = { crypto }; | ||
// setting | ||
authenticator.options = { | ||
step: 30, | ||
window: 1 | ||
}; | ||
// Or if you're using the other OTP methods | ||
// hotp.options = { crypto } | ||
// totp.options = { crypto } | ||
// getting | ||
const opts = authenticator.options; | ||
const secret = 'KVKFKRCPNZQUYMLXOVYDSQKJKZDTSRLD' | ||
const token = authenticator.generate(secret); // 556443 | ||
// reset to default | ||
authenticator.resetOptions(); | ||
// getting all options, with validation | ||
// and backfilled with library defaults | ||
const opts = authenticator.allOptions(); | ||
``` | ||
#### Using classes | ||
### HOTP Options | ||
For ease of use, the default exports are all instantiated instances of their respective classes. | ||
You can access the original classes via it's same name property of the instantiated class. | ||
| Option | Type | Description | | ||
| ------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------- | | ||
| algorithm | string | The algorithm used for calculating the HMAC. | | ||
| createDigest | function | Creates the digest which token is derived from. | | ||
| createHmacKey | function | Formats the secret into a HMAC key, applying transformations (like padding) where needed. | | ||
| digest | string | **USE WITH CAUTION**. Same digest = same token. <br />Used in cases where digest is generated externally. (eg: async use cases) | | ||
| digits | integer | The length of the token. | | ||
| encoding | string | The encoding that was used on the secret. | | ||
i.e | ||
```js | ||
// HOTP defaults | ||
{ | ||
algorithm: 'sha1' | ||
createDigest: undefined, // to be provided via a otplib-plugin | ||
createHmacKey: hotpCreateHmacKey, | ||
digits: 6, | ||
encoding: 'ascii', | ||
} | ||
``` | ||
### TOTP Options | ||
> Note: Includes all HOTP Options | ||
| Option | Type | Description | | ||
| ------ | -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ||
| epoch | integer | **USE WITH CAUTION**. Same epoch = same token. <br />Starting time since the UNIX epoch (seconds). <br /> Epoch is JavaScript formatted. i.e. `Date.now()` or `UNIX time * 1000` | | ||
| step | integer | Time step (seconds) | | ||
| window | integer, <br /> [number, number] | Tokens in the previous and future x-windows that should be considered valid. <br /> If integer, same value will be used for both. <br /> Alternatively, define array: `[past, future]` | | ||
```js | ||
import hotp from 'otplib/hotp'; | ||
const HOTP = hotp.HOTP; | ||
// const inst = new HOTP(); | ||
// TOTP defaults | ||
{ | ||
// ...includes all HOTP defaults | ||
createHmacKey: totpCreateHmacKey, | ||
epoch: Date.now(), | ||
step: 30, | ||
window: 0, | ||
} | ||
``` | ||
import totp from 'otplib/totp'; | ||
const TOTP = totp.TOTP; | ||
// const inst = new TOTP(); | ||
### Authenticator Options | ||
import authenticator from 'otplib/authenticator'; | ||
const Authenticator = authenticator.Authenticator; | ||
// const inst = new Authenticator(); | ||
> Note: Includes all HOTP + TOTP Options | ||
// Alternatively, you can get it from the default module as well | ||
import otplib from 'otplib'; | ||
const HOTP = otplib.hotp.HOTP | ||
const TOTP = otplib.totp.TOTP | ||
const Authenticator = otplib.authenticator.Authenticator | ||
| Option | Type | Description | | ||
| ----------------- | -------- | ----------------------------------------------------------------------------------------------------- | | ||
| createRandomBytes | function | Creates a random string containing the defined number of bytes to be used in generating a secret key. | | ||
| keyEncoder | function | Encodes a secret key into a Base32 string before it is sent to the user (in QR Code etc). | | ||
| keyDecoder | function | Decodes the Base32 string given by the user into a secret. | | ||
```js | ||
// Authenticator defaults | ||
{ | ||
// ...includes all HOTP + TOTP defaults | ||
encoding: 'hex', | ||
createRandomBytes: undefined, // to be provided via a otplib-plugin | ||
keyEncoder: undefined, // to be provided via a otplib-plugin | ||
keyDecoder: undefined, // to be provided via a otplib-plugin | ||
} | ||
``` | ||
#### Using with Expo.io | ||
### Async Options | ||
[Expo](https://expo.io) does not contain a `randomBytes` implementation | ||
within the platform-provided crypto. As such, you should avoid | ||
using `otplib.authenticator.generateSecret();` and generate your own secrets instead. | ||
The following options are modified for `functions` and `classes` which are postfixed with `Async`. | ||
### In browser | ||
eg: `AuthenticatorAsync`, `totpDigestAsync`, `hotpTokenAsync` etc. | ||
A browser-targeted version has been compiled. | ||
You'll need to add the following scripts to your code: | ||
| Option | Type | Output | | ||
| ----------------- | -------------- | --------------------------------------------------- | | ||
| createDigest | async function | function returns Promise<string\> instead of string | | ||
| createHmacKey | async function | function returns Promise<string\> instead of string | | ||
| createRandomBytes | async function | function returns Promise<string\> instead of string | | ||
| keyEncoder | async function | function returns Promise<string\> instead of string | | ||
| keyDecoder | async function | function returns Promise<string\> instead of string | | ||
```html | ||
<script src="otplib-browser.js"></script> | ||
## Available Packages | ||
<script type="text/javascript"> | ||
// window.otplib | ||
</script> | ||
This library has been split into 3 categories: `core`, `plugin` and `preset`. | ||
### Core | ||
Provides the core functionality of the library. Parts of the logic | ||
has been separated out in order to provide flexibility to the library via | ||
available plugins. | ||
| file | description | | ||
| -------------------- | ---------------------------------------------------- | | ||
| otplib/hotp | HOTP functions + class | | ||
| otplib/hotp | TOTP functions + class | | ||
| otplib/authenticator | Google Authenticator functions + class | | ||
| otplib/core | Aggregates hotp/totp/authenticator functions + class | | ||
#### Core (Async) | ||
| file | description | | ||
| -------------------------- | --------------------------------------- | | ||
| otplib/hotp-async | async version of `otplib/hotp` | | ||
| otplib/hotp-async | async version of `otplib/hotp` | | ||
| otplib/authenticator-async | async version of `otplib/authenticator` | | ||
| otplib/core-async | async version of `otplib/core` | | ||
### Plugins | ||
#### Crypto Plugins | ||
| plugin | type | depends on | | ||
| ---------------------------------- | ----- | ----------------------------------- | | ||
| otplib/plugin-crypto | sync | crypto (included in Node.js) | | ||
| otplib/plugin-crypto-js | sync | `npm install crypto-js` | | ||
| otplib/plugin-crypto-async-ronomon | async | `npm install @ronomon/crypto-async` | | ||
These crypto plugins provides: | ||
```js | ||
{ | ||
createDigest, // used for token derivation | ||
createRandomBytes, //used to generate random keys for Google Authenticator | ||
} | ||
``` | ||
You can find it in `node_modules/otplib` after you install. | ||
#### Base32 Plugins | ||
Alternatively you can | ||
| plugin | type | depends on | | ||
| ---------------------------- | ---- | ----------------------------------------- | | ||
| otplib/plugin-thirty-two | sync | `npm install thirty-two` | | ||
| otplib/plugin-base32-enc-dec | sync | `npm install base32-encode base32-decode` | | ||
- Download from [gh-pages][project-lib]. | ||
- Use unpkg.com | ||
These Base32 plugins provides: | ||
```html | ||
<script src="https://unpkg.com/otplib@^10.0.0/otplib-browser.js"></script> | ||
```js | ||
{ | ||
keyDecoder, //for decoding Google Authenticator secrets | ||
keyEncoder, // for encoding Google Authenticator secrets. | ||
} | ||
``` | ||
For a live example, the [project site][project-web] has been built using `otplib-browser.js`. | ||
The source code can be found [here](https://github.com/yeojz/otplib/tree/master/site). | ||
### Presets | ||
#### Browser Compatibility | ||
Presets are preconfigured HOTP, TOTP, Authenticator instances to | ||
allow you to get started with the library quickly. | ||
In order to reduce the size of the browser package, the `crypto` package has been replaced with | ||
an alternative implementation. The current implementation depends on [Uint8Array][mdn-uint8array] | ||
and the browser's native [crypto][mdn-crypto] methods, which may only be available in | ||
recent browser versions. | ||
Each presets would need the corresponding dependent npm modules to be installed. | ||
To find out more about the replacements, you can take a look at `packages/otplib-browser/crypto.js` | ||
| file | depends on | description | | ||
| --------------------------- | ------------------------------------------------------ | ---------------------------------------------------- | | ||
| otplib/preset-default | `npm install thirty-two` | | | ||
| otplib/preset-default-async | `npm install thirty-two @ronomon/crypto-async` | async version of `otplib/preset-default` | | ||
| otplib/preset-browser | [See Browser Compatibility][docs-browser-compatiblity] | Webpack bundle and is self contained. | | ||
| otplib/preset-v11 | `npm install thirty-two` | Wrapper to adapt the APIs to v11.x compatible format | | ||
The approximate **output sizes** are as follows: | ||
## Appendix | ||
- with node crypto: ~311Kb | ||
- with alternative crypto: ~106Kb | ||
### Type Definitions | ||
## Advanced Usage | ||
`TypeScript` support was introduced in `v10.0.0`, which added type definitions over `.js` files. | ||
Ihis library been split and classified into 6 core files with other specific | ||
environment based bundles provided. | ||
As of `v12.0.0`, the library has been re-written in Typescript from the ground up. | ||
### Core | ||
### Async Support | ||
| file | description | | ||
| ---------------------------------------------------------------------------------------- | --------------------------------------------------------------- | | ||
| [authenticator.js](https://yeojz.github.io/otplib/docs/module-otplib-authenticator.html) | Google Authenticator bindings | | ||
| [core.js](https://yeojz.github.io/otplib/docs/module-otplib-core.html) | Functions for various steps in the OTP generation process | | ||
| [hotp.js](https://yeojz.github.io/otplib/docs/module-otplib-hotp.html) | Wraps core functions into an instantiated HOTP class | | ||
| [otplib.js](https://yeojz.github.io/otplib/docs/module-otplib.html) | Library entry file, containing all instances with crypto set up | | ||
| [totp.js](https://yeojz.github.io/otplib/docs/module-otplib-totp.html) | Wraps core functions into an instantiated TOTP class | | ||
| [utils.js](https://yeojz.github.io/otplib/docs/module-otplib-utils.html) | Helper utilities | | ||
`async` support was introduced in `v12.0.0`. | ||
### Other Bundles | ||
This was added as some libraries like [expo.io][link-expo-crypto] or even | ||
the browser API ([window.Crypto.subtle][link-mdn-subtlecrypto]) started providing | ||
only async methods. | ||
| file | description | | ||
| ----------------------------------------------------------------------------------- | --------------------------------------------- | | ||
| [otplib-browser.js](https://yeojz.github.io/otplib/docs/module-otplib-browser.html) | Browser compatible package built with webpack | | ||
There are 2 was to use `async` - using async replacements, or handling digests separately. | ||
For more information about the functions, check out the [documentation][project-docs]. | ||
#### Using Async Replacements | ||
## Notes | ||
This is the simplest way to get started. Other than `allOptions()` and `resetOptions`, | ||
all other methods are converted to async and thus needs to be `Promise.resolve` or `await`. | ||
eg: `await .generate(...)`, `await .check(...)` | ||
### Setting Custom Options | ||
```js | ||
import { AuthenticatorAsync } from 'otplib/core-async'; | ||
All instantiated classes will have their options inherited from their respective options | ||
generator. i.e. HOTP from `hotpOptions` and TOTP/Authenticator from `totpOptions`. | ||
const authenticator = new AuthenticatorAsync({ | ||
// ...options | ||
// make sure you use async versions of | ||
// required functions like createDigest. | ||
}); | ||
All OTP classes have an object setter and getter method to override these default options. | ||
// Note: await needed as all methods are async | ||
const token = await authenticator.generate(secret); | ||
``` | ||
For example, | ||
#### Async over Sync Methods | ||
In this method, you would essentially take over the digest generation, leaving | ||
the library to handle the digest to token conversion. | ||
```js | ||
import otplib from 'otplib'; | ||
import { Authenticator } from 'otplib/core'; | ||
import { authenticatorDigestAsync } from 'otplib/authenticator-async'; | ||
// setting | ||
otplib.authenticator.options = { | ||
step: 30, | ||
window: 1 | ||
}; | ||
// This is a synchronous Authenticator class. | ||
const authenticator = new Authenticator({ | ||
// ...options | ||
}); | ||
// getting | ||
const opts = otplib.authenticator.options; | ||
// Override the digest generation. | ||
const digest = await authenticatorDigestAsync(secret, { | ||
...authenticator.allOptions(), | ||
createDigest: async (algorithm, hmacKey, counter) => 'string'; // put your async implementation | ||
}); | ||
// reset to default | ||
otplib.authenticator.resetOptions(); | ||
authenticator.options = { digest }; | ||
const token = authenticator.generate(secret); | ||
// recommended: reset to remove the digest. | ||
authenticator.resetOptions(); | ||
// reference test in: ./packages/tests-builds/example.test.js | ||
``` | ||
#### Available Options | ||
Check the [API Documentation][project-api] for the full list of async functions. | ||
| Option | Type | Defaults | Description | | ||
| ---------------------------- | ---------------- | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | ||
| algorithm | string | 'sha1' | Algorithm used for HMAC | | ||
| createHmacSecret | function | hotpSecret, totpSecret | Transforms the secret and applies any modifications like padding to it. | | ||
| crypto | object | node crypto | Crypto module to use. | | ||
| digits | integer | 6 | The length of the token | | ||
| encoding | string | 'ascii' ('hex' for authenticator) | The encoding of secret which is given to digest | | ||
| epoch (totp, authenticator) | integer | null | Starting time since the UNIX epoch (seconds). <br /> epoch format is non-javascript. i.e. `Date.now() / 1000` | | ||
| step (totp, authenticator) | integer | 30 | Time step (seconds) | | ||
| window (totp, authenticator) | integer or array | 0 | Tokens in the previous and future x-windows that should be considered valid. <br /> If integer, same value will be used for both. <br /> Alternatively, define array: `[previous, future]` | | ||
### Browser Compatiblity | ||
### Seed / secret length | ||
`otplib/preset-browser` is a `umd` bundle with some node modules replaced to reduce the browser size. | ||
In [RFC 6238][rfc-6238], the secret / seed length for different algorithms is predefined: | ||
The following defaults have been used: | ||
- **crypto**: `crypto-js` | ||
- **encoder**: `base32-encode` | ||
- **decoder**: `base32-decode` | ||
To see what is included, you can take a look at `packages/otplib-browser/index.ts`. | ||
#### Browser bundle size | ||
The approximate **bundle sizes** are as follows: | ||
| Bundle Type | Size | | ||
| --------------------------------- | ---------- | | ||
| original | 324KB | | ||
| original, minified + gzipped | 102KB | | ||
| optimised | 30.9KB | | ||
| **optimised, minified + gzipped** | **9.53KB** | | ||
Paired with the gzipped browser `buffer.js` module, it would be about `7.65KB + 9.53KB = 17.18KB`. | ||
### Length of Secrets | ||
In [RFC 6238][rfc-6238], the secret / seed length for different algorithms are predefined: | ||
```txt | ||
@@ -312,4 +587,4 @@ HMAC-SHA1 - 20 bytes | ||
As such, the length of the secret is padded and sliced according to the expected | ||
length for respective algorithms. | ||
As such, the length of the secret provided (after any decoding) will be padded and sliced | ||
according to the expected length for respective algorithms. | ||
@@ -322,4 +597,7 @@ ### Google Authenticator | ||
#### Base32 Keys and RFC3548 | ||
#### RFC3548 Base32 | ||
> Note: [RFC4648][rfc-4648] obseletes [RFC 3548][rfc-3548]. | ||
> Any encoders following the newer specifications will work. | ||
Google Authenticator requires keys to be base32 encoded. | ||
@@ -329,9 +607,7 @@ It also requires the base32 encoder to be [RFC 3548][rfc-3548] compliant. | ||
OTP calculation will still work should you want to use | ||
other base32 encoding methods (like Crockford's Base 32) | ||
other base32 encoding methods (like Crockford's Base32) | ||
but it will NOT be compatible with Google Authenticator. | ||
```js | ||
import authenticator from 'otplib/authenticator'; | ||
const secret = authenticator.generateSecret(); // base 32 encoded hex secret key | ||
const secret = authenticator.generateSecret(); // base32 encoded hex secret key | ||
const token = authenticator.generate(secret); | ||
@@ -345,12 +621,13 @@ ``` | ||
take in a QR code that holds a URL with the protocol `otpauth://`, | ||
which you get from `otplib.authenticator.keyuri`. | ||
which you get from `authenticator.keyuri`. | ||
Google Authenticator will ignore the `algorithm`, `digits`, and `step` options. | ||
See the [documentation](https://github.com/google/google-authenticator/wiki/Key-Uri-Format) | ||
See the [keyuri documentation](https://github.com/google/google-authenticator/wiki/Key-Uri-Format) | ||
for more information. | ||
If you are using a different authenticator app, check the documentation | ||
for that app if the token provided does not work. | ||
for that app to see if any options are ignored, which will result in invalid tokens. | ||
While this library provides the "otpauth" uri, you'll need a library to generate the QR Code image. | ||
While this library provides the "otpauth" uri, you'll need a library to | ||
generate the QR Code image. | ||
@@ -362,3 +639,3 @@ An example is shown below: | ||
import qrcode from 'qrcode'; | ||
import otplib from 'otplib'; | ||
import { authenticator } from 'otplib/preset-default'; | ||
@@ -369,7 +646,10 @@ const user = 'A user name, possibly an email'; | ||
// v11.x.x and above | ||
const otpauth = otplib.authenticator.keyuri(user, service, secret); | ||
const otpauth = authenticator.keyuri(user, service, secret); | ||
// v10.x.x and below | ||
const otpauth = otplib.authenticator.keyuri( | ||
encodeURIComponent(user), encodeURIComponent(service), secret); | ||
const otpauth = authenticator.keyuri( | ||
encodeURIComponent(user), | ||
encodeURIComponent(service), | ||
secret | ||
); | ||
@@ -386,5 +666,5 @@ qrcode.toDataURL(otpauth, (err, imageUrl) => { | ||
> **Note**: For versions `v10.x.x` and below, `keyuri` does not URI encode | ||
> `user` and `service` and it's the developer's job to do it as show above. | ||
> `user` and `service`. You'll need to do so before passing in the parameteres. | ||
### Getting Time Remaining / Time Used | ||
#### Getting Time Remaining / Time Used | ||
@@ -403,2 +683,13 @@ Helper methods for getting the remaining time and used time within a validity period | ||
### Using with Expo | ||
[Expo][link-expo-io] contains modified crypto implmentations targeted at the platform. | ||
While `otplib` does not provide an `expo` specified package, with the re-architecture | ||
of `otplib`, you can now provide an expo native `createDigest` to the library. | ||
Alternatively, you can make use of crypto provided by `otplib/plugin-crypto-js` or | ||
the bundled browser umd module `otplib/preset-browser`. | ||
Pull Requests are much welcomed for a native expo implementation as well. | ||
### Exploring with local-repl | ||
@@ -423,9 +714,26 @@ | ||
## Contributing | ||
## Contributors | ||
Check out: [CONTRIBUTING.md][pr-welcome-link] | ||
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): | ||
[![Support Project][coffee-badge]][coffee-link] | ||
[![PRs Welcome][pr-welcome-badge]][pr-welcome-link] | ||
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section --> | ||
<!-- prettier-ignore-start --> | ||
<!-- markdownlint-disable --> | ||
<table> | ||
<tr> | ||
<td align="center"><a href="https://github.com/yeojz"><img src="https://avatars2.githubusercontent.com/u/429598?v=4" width="80px;" alt="Gerald Yeo"/><br /><sub><b>Gerald Yeo</b></sub></a><br /><a href="https://github.com/yeojz/otplib/commits?author=yeojz" title="Code">💻</a> <a href="https://github.com/yeojz/otplib/commits?author=yeojz" title="Documentation">📖</a> <a href="#maintenance-yeojz" title="Maintenance">🚧</a> <a href="https://github.com/yeojz/otplib/commits?author=yeojz" title="Tests">⚠️</a></td> | ||
<td align="center"><a href="https://ols.io"><img src="https://avatars3.githubusercontent.com/u/6209178?v=4" width="80px;" alt="Oliver Schneider"/><br /><sub><b>Oliver Schneider</b></sub></a><br /><a href="https://github.com/yeojz/otplib/commits?author=olsio" title="Documentation">📖</a></td> | ||
<td align="center"><a href="https://developer.mozilla.org/profiles/madarche/"><img src="https://avatars0.githubusercontent.com/u/152407?v=4" width="80px;" alt="Marc-Aurèle DARCHE"/><br /><sub><b>Marc-Aurèle DARCHE</b></sub></a><br /><a href="https://github.com/yeojz/otplib/commits?author=madarche" title="Documentation">📖</a></td> | ||
<td align="center"><a href="http://shakram02.github.io/"><img src="https://avatars3.githubusercontent.com/u/10996982?v=4" width="80px;" alt="Ahmed Hamdy (@shakram02)"/><br /><sub><b>Ahmed Hamdy (@shakram02)</b></sub></a><br /><a href="https://github.com/yeojz/otplib/commits?author=shakram02" title="Documentation">📖</a></td> | ||
<td align="center"><a href="https://tony.brix.ninja"><img src="https://avatars3.githubusercontent.com/u/97994?v=4" width="80px;" alt="Tony Brix"/><br /><sub><b>Tony Brix</b></sub></a><br /><a href="https://github.com/yeojz/otplib/commits?author=UziTech" title="Code">💻</a> <a href="https://github.com/yeojz/otplib/commits?author=UziTech" title="Documentation">📖</a></td> | ||
</tr> | ||
</table> | ||
<!-- markdownlint-enable --> | ||
<!-- prettier-ignore-end --> | ||
<!-- ALL-CONTRIBUTORS-LIST:END --> | ||
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) | ||
specification. Contributions of any kind welcome! | ||
## License | ||
@@ -435,31 +743,36 @@ | ||
<img width="150" src="https://yeojz.github.io/otplib/otplib.png" /> | ||
<img width="150" src="https://otplib.yeojz.com/otplib.png" /> | ||
[npm-badge]: https://img.shields.io/npm/v/otplib.svg?style=flat-square | ||
[npm-link]: https://www.npmjs.com/package/otplib | ||
[npm-next-badge]: https://img.shields.io/npm/v/otplib/next.svg?style=flat-square | ||
[npm-downloads-badge]: https://img.shields.io/npm/dt/otplib.svg?style=flat-square | ||
[circle-badge]: https://img.shields.io/circleci/project/github/yeojz/otplib/master.svg?style=flat-square | ||
[circle-link]: https://circleci.com/gh/yeojz/otplib | ||
[coveralls-badge]: https://img.shields.io/coveralls/yeojz/otplib/master.svg?style=flat-square | ||
[coveralls-link]: https://coveralls.io/github/yeojz/otplib | ||
[pr-welcome-badge]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square&longCache=true | ||
[pr-welcome-link]: https://github.com/yeojz/otplib/blob/master/CONTRIBUTING.md | ||
[mdn-uint8array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array | ||
[mdn-crypto]: https://developer.mozilla.org/en-US/docs/Web/API/Window/crypto | ||
[project-web]: https://yeojz.github.io/otplib | ||
[project-docs]: https://yeojz.github.io/otplib/docs | ||
[project-lib]: https://github.com/yeojz/otplib/tree/gh-pages/lib | ||
[project-examples]: https://github.com/yeojz/otplib/tree/master/examples | ||
[rfc-4226]: http://tools.ietf.org/html/rfc4226 | ||
[rfc-6238]: http://tools.ietf.org/html/rfc6238 | ||
[badge-circle]: https://img.shields.io/circleci/project/github/yeojz/otplib/master.svg?style=flat-square | ||
[badge-coveralls]: https://img.shields.io/coveralls/yeojz/otplib/master.svg?style=flat-square | ||
[badge-npm-downloads]: https://img.shields.io/npm/dt/otplib.svg?style=flat-square | ||
[badge-npm]: https://img.shields.io/npm/v/otplib.svg?style=flat-square | ||
[badge-npm-next]: https://img.shields.io/npm/v/otplib/next.svg?style=flat-square | ||
[badge-type-ts]: https://img.shields.io/badge/typedef-.d.ts-blue.svg?style=flat-square&longCache=true | ||
[docs-browser-compatiblity]: #browser-compatiblity | ||
[docs-plugins-base32]: #base32-plugins | ||
[docs-plugins-crypto]: #crypto-plugins | ||
[docs-quick-start]: #quick-start | ||
[link-expo-crypto]: https://docs.expo.io/versions/v33.0.0/sdk/crypto/ | ||
[link-expo-io]: https://expo.io | ||
[link-mdn-async]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function | ||
[link-mdn-classes]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes | ||
[link-mdn-functions]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions | ||
[link-mdn-subtlecrypto]: https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto | ||
[link-npm-buffer]: https://www.npmjs.com/package/buffer | ||
[project-api]: https://otplib.yeojz.com/api | ||
[project-circle]: https://circleci.com/gh/yeojz/otplib | ||
[project-coveralls]: https://coveralls.io/github/yeojz/otplib | ||
[project-docs]: https://otplib.yeojz.com/api | ||
[project-npm]: https://www.npmjs.com/package/otplib | ||
[project-v11-api]: https://5d4d0cc4c85e00000788a456--otplib.netlify.com/docs | ||
[project-v11-readme]: https://github.com/yeojz/otplib/blob/d0aedccbca8ae7ec1983f40da4d7a14c9e815e9c/README.md | ||
[project-web]: https://otplib.yeojz.com | ||
[rfc-3548]: http://tools.ietf.org/html/rfc3548 | ||
[rfc-4648]: https://tools.ietf.org/html/rfc4648 | ||
[rfc-4226-dataset]: https://github.com/yeojz/otplib/blob/master/packages/tests-data/rfc4226.ts | ||
[rfc-4226-wiki]: http://en.wikipedia.org/wiki/HMAC-based_One-time_Password_Algorithm | ||
[rfc-4226]: http://tools.ietf.org/html/rfc4226 | ||
[rfc-6238-dataset]: https://github.com/yeojz/otplib/blob/master/packages/tests-data/rfc6238.ts | ||
[rfc-6238-wiki]: http://en.wikipedia.org/wiki/Time-based_One-time_Password_Algorithm | ||
[donate-badge]: https://img.shields.io/badge/donate-%3C3-red.svg?longCache=true&style=flat-square | ||
[donate-link]: https://www.paypal.me/yeojz | ||
[coffee-badge]: https://img.shields.io/badge/%E2%98%95%EF%B8%8F-buy%20me%20a%20coffee-orange.svg?longCache=true&style=flat-square | ||
[coffee-link]: https://paypal.me/yeojz | ||
[type-ts-badge]: https://img.shields.io/badge/typedef-.d.ts-blue.svg?style=flat-square&longCache=true | ||
[type-ts-link]: https://github.com/yeojz/otplib/tree/master/packages/types-ts | ||
[type-ts-file]: https://github.com/yeojz/otplib/blob/master/packages/types-ts/index.d.ts | ||
[rfc-6238]: http://tools.ietf.org/html/rfc6238 |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
204933
0
45
2888
763
31
2
2
1
- Removedthirty-two@1.0.2
- Removedthirty-two@1.0.2(transitive)