Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

simple-oauth2

Package Overview
Dependencies
Maintainers
2
Versions
53
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

simple-oauth2 - npm Package Compare versions

Comparing version 3.4.0 to 4.0.0

lib/config.js

20

CHANGELOG.md
# Changelog
## Next
## 4.0.0
### Breaking changes
* [#292](https://github.com/lelylan/simple-oauth2/pull/292) [#313](https://github.com/lelylan/simple-oauth2/pull/313) Require at least Node 12
* [#292](https://github.com/lelylan/simple-oauth2/pull/292) Use private class fields for non-public API's
* [#292](https://github.com/lelylan/simple-oauth2/pull/292) Access token `.token` property is now frozen
* [#318](https://github.com/lelylan/simple-oauth2/pull/318) New public API separating each grant type into it's own submodule
* [#321](https://github.com/lelylan/simple-oauth2/pull/321) Rename resource owner credentials module to be accurate
### Maintainance
* [#292](https://github.com/lelylan/simple-oauth2/pull/292) Upgrade @hapi/hoek to v9 (requires Node 12)
* [#292](https://github.com/lelylan/simple-oauth2/pull/292) Upgrade @hapi/joi to v17 (requires Node 12)
* [#292](https://github.com/lelylan/simple-oauth2/pull/292) Upgrade @hapi/wreck to v17 (requires Node 12)
* [#311](https://github.com/lelylan/simple-oauth2/pull/311) Upgrade nock dev library to v12
* [#319](https://github.com/lelylan/simple-oauth2/pull/319) Add Node 14 to test matrix
### Documentation
* [#314](https://github.com/lelylan/simple-oauth2/pull/314) Add client credentials token refresh disclaimer
* [#317](https://github.com/lelylan/simple-oauth2/pull/317) Fix output documentation for boom errors
* [#320](https://github.com/lelylan/simple-oauth2/pull/320) Add complete reference documentation
## 3.4.0

@@ -6,0 +24,0 @@ ### Improvements

90

index.js
'use strict';
const Joi = require('@hapi/joi');
const Client = require('./lib/client');
const AuthorizationCode = require('./lib/grants/authorization-code');
const PasswordOwner = require('./lib/grants/password-owner');
const ClientCredentials = require('./lib/grants/client-credentials');
const AccessToken = require('./lib/access-token');
const { authorizationMethodEnum, bodyFormatEnum, encodingModeEnum } = require('./lib/request-options');
const Config = require('./lib/config');
const AuthorizationCodeGrant = require('./lib/grants/authorization-code');
const ResourceOwnerPasswordGrant = require('./lib/grants/resource-owner-password');
const ClientCredentialsGrant = require('./lib/grants/client-credentials');
// https://tools.ietf.org/html/draft-ietf-oauth-v2-31#appendix-A.1
const vsCharRegEx = /^[\x20-\x7E]*$/;
class AuthorizationCode extends AuthorizationCodeGrant {
constructor(options) {
const config = Config.apply(options);
const client = new Client(config);
const clientSchema = Joi.object().keys({
id: Joi.string().pattern(vsCharRegEx).allow(''),
secret: Joi.string().pattern(vsCharRegEx).allow(''),
secretParamName: Joi.string().default('client_secret'),
idParamName: Joi.string().default('client_id'),
}).required();
super(config, client);
}
}
const authSchema = Joi.object().keys({
tokenHost: Joi.string().required().uri({ scheme: ['http', 'https'] }),
tokenPath: Joi.string().default('/oauth/token'),
revokePath: Joi.string().default('/oauth/revoke'),
authorizeHost: Joi.string().uri({ scheme: ['http', 'https'] }).default(Joi.ref('tokenHost')),
authorizePath: Joi.string().default('/oauth/authorize'),
}).required();
class ClientCredentials extends ClientCredentialsGrant {
constructor(options) {
const config = Config.apply(options);
const client = new Client(config);
const optionsSchema = Joi.object().keys({
scopeSeparator: Joi.string().default(' '),
credentialsEncodingMode: Joi
.string()
.valid(...Object.values(encodingModeEnum))
.default(encodingModeEnum.STRICT),
bodyFormat: Joi
.string()
.valid(...Object.values(bodyFormatEnum))
.default(bodyFormatEnum.FORM),
authorizationMethod: Joi
.string()
.valid(...Object.values(authorizationMethodEnum))
.default(authorizationMethodEnum.HEADER),
}).default();
super(config, client);
}
}
const moduleOptionsSchema = Joi.object().keys({
client: clientSchema,
auth: authSchema,
http: Joi.object().unknown(true),
options: optionsSchema,
});
class ResourceOwnerPassword extends ResourceOwnerPasswordGrant {
constructor(options) {
const config = Config.apply(options);
const client = new Client(config);
super(config, client);
}
}
module.exports = {
/**
* Creates a new simple-oauth2 client with the provided configuration
* @param {Object} opts Module options as defined in schema
* @returns {Object} The simple-oauth2 client
*/
create(opts = {}) {
const options = Joi.attempt(opts, moduleOptionsSchema, 'Invalid options provided to simple-oauth2');
const client = new Client(options);
return {
accessToken: {
create: AccessToken.factory(options, client),
},
ownerPassword: new PasswordOwner(options, client),
authorizationCode: new AuthorizationCode(options, client),
clientCredentials: new ClientCredentials(options, client),
};
},
ResourceOwnerPassword,
ClientCredentials,
AuthorizationCode,
};

@@ -11,6 +11,5 @@ 'use strict';

class AccessToken {
static factory(config, client) {
return (token) => new AccessToken(config, client, token);
}
module.exports = class AccessToken {
#config = null;
#client = null;

@@ -22,5 +21,5 @@ constructor(config, client, token) {

this.config = config;
this.client = client;
this.token = parseToken(token);
this.#config = config;
this.#client = client;
this.token = Object.freeze(parseToken(token));
}

@@ -46,10 +45,11 @@

async refresh(params = {}) {
const refreshParams = Object.assign({}, params, {
const refreshParams = {
...params,
refresh_token: this.token.refresh_token,
});
};
const parameters = GrantParams.forGrant(REFRESH_TOKEN_PROPERTY_NAME, this.config.options, refreshParams);
const response = await this.client.request(this.config.auth.tokenPath, parameters.toObject());
const parameters = GrantParams.forGrant(REFRESH_TOKEN_PROPERTY_NAME, this.#config.options, refreshParams);
const response = await this.#client.request(this.#config.auth.tokenPath, parameters.toObject());
return new AccessToken(this.config, this.client, response);
return new AccessToken(this.#config, this.#client, response);
}

@@ -66,3 +66,3 @@

tokenType === ACCESS_TOKEN_PROPERTY_NAME || tokenType === REFRESH_TOKEN_PROPERTY_NAME,
`Invalid token type. Only ${ACCESS_TOKEN_PROPERTY_NAME} or ${REFRESH_TOKEN_PROPERTY_NAME} are valid values`
`Invalid token type. Only ${ACCESS_TOKEN_PROPERTY_NAME} or ${REFRESH_TOKEN_PROPERTY_NAME} are valid values`,
);

@@ -75,3 +75,3 @@

return this.client.request(this.config.auth.revokePath, options);
return this.#client.request(this.#config.auth.revokePath, options);
}

@@ -81,2 +81,3 @@

* Revokes both the current access and refresh tokens
*
* @returns {Promise}

@@ -88,4 +89,2 @@ */

}
}
module.exports = AccessToken;
};

@@ -38,5 +38,8 @@ 'use strict';

return Object.assign({}, token, tokenProperties);
return {
...token,
...tokenProperties,
};
}
module.exports = { parseToken };

@@ -19,2 +19,5 @@ 'use strict';

module.exports = class Client {
#config = null;
#client = null;
constructor(config) {

@@ -27,8 +30,8 @@ const configHttpOptions = Hoek.applyToDefaults(config.http || {}, {

this.config = config;
this.client = Wreck.defaults(httpOptions);
this.#config = config;
this.#client = Wreck.defaults(httpOptions);
}
async request(url, params, opts) {
const requestOptions = new RequestOptions(this.config, params);
const requestOptions = new RequestOptions(this.#config, params);
const options = requestOptions.toObject(opts);

@@ -39,3 +42,3 @@

const response = await this.client.post(url, options);
const response = await this.#client.post(url, options);

@@ -42,0 +45,0 @@ return response.payload;

@@ -20,2 +20,6 @@ 'use strict';

module.exports = class GrantParams {
#params = null;
#baseParams = null;
#options = null;
static forGrant(grantType, options, params) {

@@ -30,12 +34,12 @@ const baseParams = {

constructor(options, baseParams, params) {
this.options = options;
this.params = Object.assign({}, params);
this.baseParams = Object.assign({}, baseParams);
this.#options = { ...options };
this.#params = { ...params };
this.#baseParams = { ...baseParams };
}
toObject() {
const scopeParams = getScopeParam(this.params.scope, this.options.scopeSeparator);
const scopeParams = getScopeParam(this.#params.scope, this.#options.scopeSeparator);
return Object.assign(this.baseParams, this.params, scopeParams);
return Object.assign(this.#baseParams, this.#params, scopeParams);
}
};

@@ -5,8 +5,12 @@ 'use strict';

const querystring = require('querystring');
const AccessToken = require('../access-token');
const GrantParams = require('../grant-params');
module.exports = class AuthorizationCode {
#config = null;
#client = null;
constructor(config, client) {
this.config = config;
this.client = client;
this.#config = config;
this.#client = client;
}

@@ -27,7 +31,7 @@

response_type: 'code',
[this.config.client.idParamName]: this.config.client.id,
[this.#config.client.idParamName]: this.#config.client.id,
};
const url = new URL(this.config.auth.authorizePath, this.config.auth.authorizeHost);
const parameters = new GrantParams(this.config.options, baseParams, params);
const url = new URL(this.#config.auth.authorizePath, this.#config.auth.authorizeHost);
const parameters = new GrantParams(this.#config.options, baseParams, params);

@@ -41,12 +45,13 @@ return `${url}?${querystring.stringify(parameters.toObject())}`;

* @param {String} params.code Authorization code (from previous step)
* @param {String} params.redirecURI String representing the registered application URI where the user is redirected after authentication
* @param {String} params.redirectURI String representing the registered application URI where the user is redirected after authentication
* @param {String|Array<String>} [params.scope] String or array of strings representing the application privileges
* @param {Object} [httpOptions] Optional http options passed through the underlying http library
* @return {Promise}
* @return {Promise<AccessToken>}
*/
async getToken(params, httpOptions) {
const parameters = GrantParams.forGrant('authorization_code', this.config.options, params);
const parameters = GrantParams.forGrant('authorization_code', this.#config.options, params);
const response = await this.#client.request(this.#config.auth.tokenPath, parameters.toObject(), httpOptions);
return this.client.request(this.config.auth.tokenPath, parameters.toObject(), httpOptions);
return new AccessToken(this.#config, this.#client, response);
}
};
'use strict';
const GrantParams = require('../grant-params');
const AccessToken = require('../access-token');
module.exports = class ClientCredentials {
#config = null;
#client = null;
constructor(config, client) {
this.config = config;
this.client = client;
this.#config = config;
this.#client = client;
}

@@ -17,9 +21,10 @@

* @param {Object} [httpOptions] Optional http options passed through the underlying http library
* @return {Promise}
* @return {Promise<AccessToken>}
*/
async getToken(params, httpOptions) {
const parameters = GrantParams.forGrant('client_credentials', this.config.options, params);
const parameters = GrantParams.forGrant('client_credentials', this.#config.options, params);
const response = await this.#client.request(this.#config.auth.tokenPath, parameters.toObject(), httpOptions);
return this.client.request(this.config.auth.tokenPath, parameters.toObject(), httpOptions);
return new AccessToken(this.#config, this.#client, response);
}
};

@@ -28,14 +28,17 @@ 'use strict';

class RequestOptions {
#config = null;
#requestOptions = null;
constructor(config, params) {
this.config = config;
this.requestOptions = this.createOptions(params);
this.#config = config;
this.#requestOptions = this.createOptions(params);
}
createOptions(params) {
const parameters = Object.assign({}, params);
const parameters = { ...params };
const requestOptions = getDefaultRequestOptions();
if (this.config.options.authorizationMethod === authorizationMethodEnum.HEADER) {
const encoding = new Encoding(this.config.options.credentialsEncodingMode);
const credentials = encoding.getAuthorizationHeaderToken(this.config.client.id, this.config.client.secret);
if (this.#config.options.authorizationMethod === authorizationMethodEnum.HEADER) {
const encoding = new Encoding(this.#config.options.credentialsEncodingMode);
const credentials = encoding.getAuthorizationHeaderToken(this.#config.client.id, this.#config.client.secret);

@@ -48,7 +51,7 @@ debug('Using header authentication. Authorization header set to %s', credentials);

parameters[this.config.client.idParamName] = this.config.client.id;
parameters[this.config.client.secretParamName] = this.config.client.secret;
parameters[this.#config.client.idParamName] = this.#config.client.id;
parameters[this.#config.client.secretParamName] = this.#config.client.secret;
}
if (this.config.options.bodyFormat === bodyFormatEnum.FORM) {
if (this.#config.options.bodyFormat === bodyFormatEnum.FORM) {
debug('Using form request format');

@@ -69,3 +72,3 @@

toObject(requestOptions = {}) {
return Hoek.applyToDefaults(requestOptions, this.requestOptions);
return Hoek.applyToDefaults(requestOptions, this.#requestOptions);
}

@@ -72,0 +75,0 @@ }

{
"name": "simple-oauth2",
"version": "3.4.0",
"version": "4.0.0",
"description": "Node.js client for OAuth2",

@@ -20,3 +20,3 @@ "author": "Andrea Reginato <andrea.reginato@gmail.com>",

"engine": {
"node": ">=8"
"node": ">=12"
},

@@ -49,20 +49,21 @@ "scripts": {

"dependencies": {
"@hapi/hoek": "^8.5.0",
"@hapi/joi": "^16.1.8",
"@hapi/wreck": "^15.1.0",
"date-fns": "^2.9.0",
"@hapi/hoek": "^9.0.4",
"@hapi/joi": "^17.1.1",
"@hapi/wreck": "^17.0.0",
"date-fns": "^2.14.0",
"debug": "^4.1.1"
},
"devDependencies": {
"@hapi/boom": "^8.0.1",
"ava": "^3.1.0",
"chance": "^1.1.4",
"@hapi/boom": "^9.1.0",
"ava": "^3.8.2",
"babel-eslint": "^10.1.0",
"chance": "^1.1.6",
"chance-access-token": "^2.0.0",
"doctoc": "^1.4.0",
"eslint": "^6.8.0",
"eslint-config-airbnb-base": "^14.0.0",
"eslint-plugin-import": "^2.20.0",
"nock": "^11.7.2",
"nyc": "^15.0.0"
"eslint-config-airbnb-base": "^14.1.0",
"eslint-plugin-import": "^2.20.2",
"nock": "^12.0.3",
"nyc": "^15.0.1"
}
}
# Simple OAuth2
[![NPM Package Version](https://img.shields.io/npm/v/simple-oauth2.svg?style=flat-square)](https://www.npmjs.com/package/simple-oauth2)
[![Build Status](https://img.shields.io/travis/lelylan/simple-oauth2.svg?style=flat-square)](https://travis-ci.org/lelylan/simple-oauth2)
[![Build Status](https://github.com/lelylan/simple-oauth2/workflows/Node.js%20CI/badge.svg)](https://github.com/lelylan/simple-oauth2/actions)
[![Dependency Status](https://img.shields.io/david/lelylan/simple-oauth2.svg?style=flat-square)](https://david-dm.org/lelylan/simple-oauth2)
Node.js client library for [OAuth2](http://oauth.net/2/). OAuth2 allows users to grant access to restricted resources by third party applications.
[Simple OAuth2](#simple-oauth2) is a Node.js client library for the [OAuth 2.0](http://oauth.net/2/) authorization framework. [OAuth 2.0](http://oauth.net/2/) is the industry-standard protocol for authorization, enabling third-party applications to obtain limited access to an HTTP service, either on behalf of a resource owner or by allowing the third-party application to obtain access on it's own behalf.

@@ -20,11 +19,11 @@ ## Table of Contents

- [Usage](#usage)
- [OAuth2 Supported grants](#oauth2-supported-grants)
- [Authorization Code](#authorization-code)
- [Password Credentials Flow](#password-credentials-flow)
- [Client Credentials Flow](#client-credentials-flow)
- [Access Token object](#access-token-object)
- [Supported Grant Types](#supported-grant-types)
- [Authorization Code Grant](#authorization-code-grant)
- [Resource Owner Password Credentials Grant](#resource-owner-password-credentials-grant)
- [Client Credentials Grant](#client-credentials-grant)
- [Access Token](#access-token)
- [Refresh an access token](#refresh-an-access-token)
- [Revoke an access or refresh token](#revoke-an-access-or-refresh-token)
- [Errors](#errors)
- [Debugging the module](#debugging-the-module)
- [API](#api)
- [Usage examples](#usage-examples)
- [Contributing](#contributing)

@@ -41,3 +40,3 @@ - [Authors](#authors)

The node client library is tested against Node 8 LTS and newer versions. Older node versions are unsupported.
The node client library is tested against Node 12 LTS and newer versions. Older node versions are unsupported.

@@ -52,6 +51,6 @@ ## Usage

Create a new instance by specifying the minimal configuration
With a minimal configuration, create an client instace of any supported [grant type](#supported-grant-types).
```javascript
const credentials = {
const config = {
client: {

@@ -66,19 +65,20 @@ id: '<client-id>',

const oauth2 = require('simple-oauth2').create(credentials);
const { ClientCredentials, ResourceOwnerPassword, AuthorizationCode } = require('simple-oauth2');
```
For more detailed configuration information see [API Documentation](./API.md)
### OAuth2 Supported grants
For a complete reference of configuration options, see the [API Options](./API.md#options)
Depending on your use case, any of the following supported grant types may be useful:
### Supported Grant Types
#### Authorization Code
Depending on your use-case, any of the following supported grant types may be useful:
The [Authorization Code](http://tools.ietf.org/html/draft-ietf-oauth-v2-31#section-4.1) grant type is made up from two parts. At first your application asks to the user the permission to access their data. If the user approves the OAuth2 server sends to the client an authorization code. In the second part, the client POST the authorization code along with its client secret to the oauth server in order to get the access token.
#### Authorization Code Grant
The [Authorization Code](https://oauth.net/2/grant-types/authorization-code/) grant type is used by confidential and public clients to exchange an authorization code for an access token. After the user returns to the client via the redirect URL, the application will get the authorization code from the URL and use it to request an access token.
```javascript
async function run() {
const oauth2 = require('simple-oauth2').create(credentials);
const client = new AuthorizationCode(config);
const authorizationUri = oauth2.authorizationCode.authorizeURL({
const authorizationUri = client.authorizeURL({
redirect_uri: 'http://localhost:3000/callback',

@@ -92,3 +92,3 @@ scope: '<scope>',

const tokenConfig = {
const tokenParams = {
code: '<code>',

@@ -100,4 +100,3 @@ redirect_uri: 'http://localhost:3000/callback',

try {
const result = await oauth2.authorizationCode.getToken(tokenConfig);
const accessToken = oauth2.accessToken.create(result);
const accessToken = await client.getToken(tokenParams);
} catch (error) {

@@ -111,11 +110,13 @@ console.log('Access Token Error', error.message);

#### Password Credentials Flow
See the [API reference](./API.md#new-authorizationcodeoptions) for a complete reference of available options or any of our available examples at the [example folder](./example).
The [Password Owner](http://tools.ietf.org/html/draft-ietf-oauth-v2-31#section-4.3) grant type is suitable when the resource owner has a trust relationship with the client, such as its computer operating system or a highly privileged application. Use this flow only when other flows are not viable or when you need a fast way to test your application.
#### Resource Owner Password Credentials Grant
The [Resource Owner Password Credentials](https://oauth.net/2/grant-types/password/) grant type is a way to exchange a user's credentials for an access token. Because the client application has to collect the user's password and send it to the authorization server, it is not recommended that this grant be used at all anymore.
```javascript
async function run() {
const oauth2 = require('simple-oauth2').create(credentials);
const client = new ResourceOwnerPassword(config);
const tokenConfig = {
const tokenParams = {
username: 'username',

@@ -127,4 +128,3 @@ password: 'password',

try {
const result = await oauth2.ownerPassword.getToken(tokenConfig);
const accessToken = oauth2.accessToken.create(result);
const accessToken = await client.getToken(tokenParams);
} catch (error) {

@@ -138,11 +138,13 @@ console.log('Access Token Error', error.message);

#### Client Credentials Flow
See the [API reference](./API.md#new-resourceownerpasswordoptions) for a complete reference of available options.
The [Client Credentials](http://tools.ietf.org/html/draft-ietf-oauth-v2-31#section-4.4) grant type is suitable when client is requesting access to the protected resources under its control.
#### Client Credentials Grant
The [Client Credentials](https://oauth.net/2/grant-types/client-credentials/) grant type is used by clients to obtain an access token outside of the context of a user. This is typically used by clients to access resources about themselves rather than to access a user's resources.
```javascript
async function run() {
const oauth2 = require('simple-oauth2').create(credentials);
const client = new ClientCredentials(config);
const tokenConfig = {
const tokenParams = {
scope: '<scope>',

@@ -152,4 +154,3 @@ };

try {
const result = await oauth2.clientCredentials.getToken(tokenConfig);
const accessToken = oauth2.accessToken.create(result);
const accessToken = await client.getToken(tokenParams);
} catch (error) {

@@ -163,23 +164,21 @@ console.log('Access Token error', error.message);

### Access Token object
See the [API reference](./API.md#new-clientcredentialsoptions) for a complete reference of available options.
When a token expires we need to refresh it. Simple OAuth2 offers the AccessToken class that add a couple of useful methods to refresh the access token when it is expired.
### Access Token
On completion of any [supported grant type](#supported-grant-types) an access token will be obtained. A list of supported operations can be found below.
#### Refresh an access token
When a token expires we need a mechanism to obtain a new access token. The [AccessToken](./API.md#accesstoken) methods can be used to perform the token refresh process.
```javascript
async function run() {
const tokenObject = {
'access_token': '<access-token>',
'refresh_token': '<refresh-token>',
'expires_in': '7200'
};
let accessToken = oauth2.accessToken.create(tokenObject);
if (accessToken.expired()) {
try {
const params = {
const refreshParams = {
scope: '<scope>',
};
accessToken = await accessToken.refresh(params);
accessToken = await accessToken.refresh(refreshParams);
} catch (error) {

@@ -202,3 +201,3 @@ console.log('Error refreshing access token: ', error.message);

if (token.expired(EXPIRATION_WINDOW_IN_SECONDS)) {
if (accessToken.expired(EXPIRATION_WINDOW_IN_SECONDS)) {
try {

@@ -215,4 +214,10 @@ accessToken = await accessToken.refresh();

When you've done with the token or you want to log out, you can revoke the access and refresh tokens.
**Warning:** Tokens obtained with the Client Credentials grant may not be refreshed. Fetch a new token when it's expired.
See the [API reference](./API.md#accesstoken) for a complete reference of available options.
#### Revoke an access or refresh token
When you've done with the token or you want to log out, you can revoke both access and refresh tokens.
```javascript

@@ -235,3 +240,2 @@ async function run() {

async function run() {
// Revoke both access and refresh tokens
try {

@@ -248,16 +252,16 @@ // Revokes both tokens, refresh token is only revoked if the access_token is properly revoked

See the [API reference](./API.md#accesstoken) for a complete reference of available options.
### Errors
Errors are returned when a 4xx or 5xx status code is received.
Whenever a client or server error is produced, a [boom](https://github.com/hapijs/boom) error is thrown by the library. As such any [boom error property](https://hapi.dev/module/boom/api) is available, but the exact information may vary according to the type of error.
BoomError
As a standard [boom](https://github.com/hapijs/boom) error you can access any of the boom error properties. The total amount of information varies according to the generated status code.
```javascript
async function run() {
const client = new ClientCredentials(config);
try {
await oauth2.authorizationCode.getToken();
await client.getToken();
} catch(error) {
console.log(error);
console.log(error.output);
}

@@ -267,7 +271,9 @@ }

run();
// => {
// "statusCode": 401,
// "error": "Unauthorized",
// "message": "invalid password"
// }
// { statusCode: 401,
// payload:
// { statusCode: 401,
// error: 'Unauthorized',
// message: 'Response Error: 401 Unauthorized' },
// headers: {} }
```

@@ -282,9 +288,2 @@

## API
For a complete reference, see the module [API](./API.md).
## Usage examples
For complete reference examples, see the [example folder](./example).
## Contributing

@@ -291,0 +290,0 @@

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc