[![SDK Banner](views/SDK.png)][ss1]

[![Build Status](https://travis-ci.org/intuit/oauth-jsclient.svg?branch=master)](https://travis-ci.org/intuit/oauth-jsclient?branch=master)
[![NPM Package Version](https://img.shields.io/npm/v/intuit-oauth.svg?style=flat-square)](https://www.npmjs.com/package/intuit-oauth)
[![Coverage Status](https://coveralls.io/repos/github/intuit/oauth-jsclient/badge.svg?branch=master)](https://coveralls.io/github/intuit/oauth-jsclient?branch=master)
[![GitHub contributors](https://img.shields.io/github/contributors/intuit/oauth-jsclient?style=flat-square)](https://github.com/intuit/oauth-jsclient/graphs/contributors)
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/intuit/oauth-jsclient/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/intuit/oauth-jsclient/?branch=master)
![npm](https://img.shields.io/npm/dm/intuit-oauth?style=flat-square)
[![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=shield)](https://github.com/prettier/prettier)
[![Known Vulnerabilities](https://snyk.io/test/github/intuit/oauth-jsclient/badge.svg)](https://snyk.io/test/github/intuit/oauth-jsclient)

# Intuit OAuth2.0 NodeJS Library

The OAuth2 Nodejs Client library is meant to work with Intuit's
[OAuth2.0](https://developer.intuit.com/app/developer/qbo/docs/develop/authentication-and-authorization/oauth-2.0)
and
[OpenID Connect](https://developer.intuit.com/app/developer/qbo/docs/develop/authentication-and-authorization/openid-connect)
implementations which conforms to the specifications.

## Table of Contents

- [Intuit OAuth2.0 NodeJS Library](#intuit-oauth20-nodejs-library)
  - [Table of Contents](#table-of-contents)
- [Requirements](#requirements)
- [Installation](#installation)
  - [Using NodeJS](#using-nodejs)
    - [Options :](#options)
- [Usage](#usage)
  - [Authorization Code Flow](#authorization-code-flow)
    - [Step 1](#step-1)
    - [Scopes :](#scopes)
    - [Step 2](#step-2)
- [Sample](#sample)
  - [Helpers](#helpers)
    - [Is AccessToken Valid](#is-accesstoken-valid)
    - [Refresh access_token](#refresh-access_token)
    - [Refresh access_token by passing the refresh_token explicitly](#refresh-access_token-by-passing-the-refresh_token-explicitly)
    - [Revoke access_token](#retrieve-the-token-)
    - [Getter / Setter for Token](#getter--setter-for-token)
      - [Retrieve the Token :](#retrieve-the-token)
      - [Set the Token :](#set-the-token-)
    - [Migrate OAuth1.0 Tokens to OAuth2.0](#migrate-oauth10-tokens-to-oauth20)
    - [Validate ID Token](#validate-id-token)
    - [Make API call](#make-api-call)
    - [Auth-Response](#auth-response)
    - [Error Logging](#error-logging)
  - [FAQ](#faq)
  - [Contributing](#contributing)
    - [Steps](#steps)
  - [Changelog](#changelog)
  - [License](#license)

# Requirements

The Node.js client library is tested against the `Node 10` and newer versions.

| Version                                                                          | Node support                      |
|----------------------------------------------------------------------------------|-----------------------------------|
| [intuit-oauth@1.x.x](https://github.com/intuit/oauth-jsclient/tree/1.5.0)        | Node 6.x or higher                |
| [intuit-oauth@2.x.x](https://github.com/intuit/oauth-jsclient/tree/2.0.0)        | Node 7.x or higher                |
| [intuit-oauth@3.x.x](https://github.com/intuit/oauth-jsclient/tree/3.0.2)        | Node 8.x or Node 9.x and higher   |

**Note**: Older node versions are not supported.

# Installation

Follow the instructions below to use the library :

## Using NodeJS

1. Install the NPM package:

   ```sh
   npm install intuit-oauth --save
   ```

2. Require the Library:

   ```js
   const OAuthClient = require('intuit-oauth');

   const oauthClient = new OAuthClient({
     clientId: '<Enter your clientId>',
     clientSecret: '<Enter your clientSecret>',
     environment: 'sandbox' || 'production',
     redirectUri: '<Enter your callback URL>',
   });
   ```

### Options

- `clientId` - clientID for your app. Required
- `clientSecret` - clientSecret fpor your app. Required
- `environment` - environment for the client. Required
  - `sandbox` - for authorizing in sandbox.
  - `production` - for authorizing in production.
- `redirectUri` - redirectUri on your app to get the `authorizationCode` from Intuit Servers. Make sure this redirect URI is also added on your app in the [developer portal](https://developer.intuit.com) on the Keys & OAuth tab. Required
- `logging` - by default, logging is disabled i.e `false`. To enable provide`true`.

# Usage

We assume that you have a basic understanding about OAuth2.0. If not please read
[API Documentation](https://developer.intuit.com/app/developer/qbo/docs/develop/authentication-and-authorization/oauth-2.0)
for clear understanding

## Authorization Code Flow

The Authorization Code flow is made up of two parts :

**Step 1.** Redirect user to `oauthClient.authorizeUri(options)`.  
**Step 2.** Parse response uri and get access-token using the function
`oauthClient.createToken(req.url)` which returns a
[Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise).

### Step 1

```javascript
// Instance of client
const oauthClient = new OAuthClient({
  clientId: '<Enter your clientId>',
  clientSecret: '<Enter your clientSecret>',
  environment: 'sandbox',
  redirectUri: '<http://localhost:8000/callback>',
});

// AuthorizationUri
const authUri = oauthClient.authorizeUri({
  scope: [OAuthClient.scopes.Accounting, OAuthClient.scopes.OpenId],
  state: 'testState',
}); // can be an array of multiple scopes ex : {scope:[OAuthClient.scopes.Accounting,OAuthClient.scopes.OpenId]}

// Redirect the authUri
res.redirect(authUri);
```

### Scopes

The available scopes include :

- `com.intuit.quickbooks.accounting` - for accounting scope include `OAuthClient.scopes.Accounting`
- `com.intuit.quickbooks.payment` - for payment scope include `OAuthClient.scopes.Payment`
- `com.intuit.quickbooks.payroll` - for QuickBooks Payroll API (whitelisted beta apps only)
- `com.intuit.quickbooks.payroll.timetracking` - for QuickBooks Payroll API for for access to
  compensation (whitelisted beta apps only)
- `com.intuit.quickbooks.payroll.benefits` - for QuickBooks Payroll API for access to
  benefits/pension/deduction (whitelisted beta apps only)

OpenID Scopes :

- `openid` - for openID assertion include `OAuthClient.scopes.OpenId`
- `profile` - for profile assertion include `OAuthClient.scopes.Profile`
- `email` - for email assertion include `OAuthClient.scopes.Email`
- `phone` - for phone assertion include `OAuthClient.scopes.Phone`
- `address` - for address assertion include `OAuthClient.scopes.Address`

### Step 2

```javascript
// Parse the redirect URL for authCode and exchange them for tokens
const parseRedirect = req.url;

// Exchange the auth code retrieved from the **req.url** on the redirectUri
oauthClient
  .createToken(parseRedirect)
  .then(function (authResponse) {
    console.log('The Token is  ' + JSON.stringify(authResponse.getToken()));
  })
  .catch(function (e) {
    console.error('The error message is :' + e.originalMessage);
    console.error(e.intuit_tid);
  });
```

# Sample

For more clarity, we suggest you take a look at the sample application below :  
[sample](https://github.com/intuit/oauth-jsclient/tree/master/sample)

## Helpers

### Is AccessToken Valid

You can check if the `access_token` associated with the `oauthClient` is valid ( not expired ) or
not using the helper method.

```javascript
if (oauthClient.isAccessTokenValid()) {
  console.log('The access_token is valid');
}

if (!oauthClient.isAccessTokenValid()) {
  oauthClient
    .refresh()
    .then(function (authResponse) {
      console.log('Tokens refreshed : ' + JSON.stringify(authResponse.getToken()));
    })
    .catch(function (e) {
      console.error('The error message is :' + e.originalMessage);
      console.error(e.intuit_tid);
    });
}
```

\*\* Note: If the access_token is not valid, you can call the client's `refresh()` method to refresh
the tokens for you as shown below

### Refresh access_token

Access tokens are valid for 3600 seconds (one hour), after which time you need to get a fresh one
using the latest refresh_token returned to you from the previous request. When you request a fresh
access_token, always use the refresh token returned in the most recent token_endpoint response. Your
previous refresh tokens expire 24 hours after you receive a new one.

```javascript
oauthClient
  .refresh()
  .then(function (authResponse) {
    console.log('Tokens refreshed : ' + JSON.stringify(authResponse.getToken()));
  })
  .catch(function (e) {
    console.error('The error message is :' + e.originalMessage);
    console.error(e.intuit_tid);
  });
```

### Refresh access_token by passing the refresh_token explicitly

You can call the below helper method to refresh tokens by explictly passing the refresh_token.  
\*\*Note : `refresh_token` should be of the type `string`

```javascript
oauthClient
  .refreshUsingToken('<Enter the refresh token>')
  .then(function (authResponse) {
    console.log('Tokens refreshed : ' + JSON.stringify(authResponse.getToken()));
  })
  .catch(function (e) {
    console.error('The error message is :' + e.originalMessage);
    console.error(e.intuit_tid);
  });
```

### Revoke access_token

When you no longer need the access_token, you could use the below helper method to revoke the
tokens.

```javascript
oauthClient
  .revoke()
  .then(function (authResponse) {
    console.log('Tokens revoked : ' + JSON.stringify(authResponse.json));
  })
  .catch(function (e) {
    console.error('The error message is :' + e.originalMessage);
    console.error(e.intuit_tid);
  });
```

Alternatively you can also pass `access_token` or `refresh_token` to this helper method using the
`params` object: refer to - [Getter / Setter for Token](#getter--setter-for-token) section to know
how to retrieve the `token` object

```javascript
oauthClient
  .revoke(params)
  .then(function (authResponse) {
    console.log('Tokens revoked : ' + JSON.stringify(authResponse.json));
  })
  .catch(function (e) {
    console.error('The error message is :' + e.originalMessage);
    console.error(e.intuit_tid);
  });
```

** Note ** : `params` is the Token JSON object as shown below : ( _If you do not pass the `params`
then the token object of the client would be considered._)

```
{
    "token_type": "bearer",
    "expires_in": 3600,
    "refresh_token":"<refresh_token>",
    "x_refresh_token_expires_in":15552000,
    "access_token":"<access_token>",
    "createdAt": "(Optional Default = Date.now()) <Milliseconds> from the unix epoch"

}
```

** Note ** :

### Getter / Setter for Token

You can call the below methods to set and get the tokens using the `oauthClient` instance:

#### Retrieve the Token :

```javascript
// To get the tokens
let authToken = oauthClient.getToken().getToken();

`OR`;

let authToken = oauthClient.token.getToken();
```

#### Set the Token :

```javascript
// To Set the retrieved tokens explicitly using Token Object but the same instance
oauthClient.setToken(authToken);

OR;

// To set the retrieved tokens using a new client instance
const oauthClient = new OAuthClient({
  clientId: '<Enter your clientId>',
  clientSecret: '<Enter your clientSecret>',
  environment: 'sandbox',
  redirectUri: '<http://localhost:8000/callback>',
  token: authToken,
});
```

The authToken parameters are as follows:

```
{
    token_type: '<String>',
    access_token: '<String>',
    expires_in: '<Int> Seconds',
    refresh_token: '<String>',
    x_refresh_token_expires_in: '<Int>  Seconds',
    id_token: "(Optional Default = '') <String>",
    createdAt: '(Optional Default = Date.now()) <Milliseconds> from the unix epoch'
}
```

**Note** :  
The OAuth Client library converts the accessToken and refreshToken expiry time to `TimeStamp`. If
you are setting a stored token, please pass in the `createdAt` for accurate experiations.

```javascript
oauthClient.setToken(authToken);
```

### Migrate OAuth1.0 Tokens to OAuth2.0

You can call the below method to migrate the bearer / refresh tokens from OAuth1.0 to OAuth2.0. You

```javascript
// Fill in the params object ( argument to the migrate function )

let params = {
  oauth_consumer_key: '<Enter oauth1ConsumerKey>',
  oauth_consumer_secret: '<Enter oauth1ConsumerSecret>',
  oauth_signature_method: 'HMAC-SHA1',
  oauth_timestamp: Math.round(new Date().getTime() / 1000),
  oauth_nonce: 'nonce',
  oauth_version: '1.0',
  access_token: '<Enter OAuth1.0 access_token>',
  access_secret: '<Enter OAuth1.0 access_secret>',
  scope: [OAuthClient.scopes.Accounting],
};

oauthClient
  .migrate(params)
  .then(function (response) {
    console.log('The response is ' + JSON.stringify(response));
  })
  .catch(function (e) {
    console.log('The error is ' + e.message);
  });
```

### Validate ID Token

You can validate the ID token obtained from `Intuit Authorization Server` as shown below :

```javascript
oauthClient
  .validateIdToken()
  .then(function (response) {
    console.log('Is my ID token validated  : ' + response);
  })
  .catch(function (e) {
    console.log('The error is ' + JSON.stringify(e));
  });

// Is my ID token validated : true
```

The client validates the ID Token and returns boolean `true` if validates successfully else it would
throw an exception.

### Make API Call

You can make API call using the token generated from the client as shown below :

```javascript
// Body sample from API explorer examples
const body = {
  TrackQtyOnHand: true,
  Name: 'Garden Supplies',
  QtyOnHand: 10,
  InvStartDate: '2015-01-01',
  Type: 'Inventory',
  IncomeAccountRef: {
    name: 'Sales of Product Income',
    value: '79',
  },
  AssetAccountRef: {
    name: 'Inventory Asset',
    value: '81',
  },
  ExpenseAccountRef: {
    name: 'Cost of Goods Sold',
    value: '80',
  },
};

oauthClient
  .makeApiCall({
    url: 'https://sandbox-quickbooks.api.intuit.com/v3/company/1234/item',
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(body),
  })
  .then(function (response) {
    console.log('The API response is  : ' + response);
  })
  .catch(function (e) {
    console.log('The error is ' + JSON.stringify(e));
  });
```

The client validates the ID Token and returns boolean `true` if validates successfully else it would
throw an exception.

#### Support for PDF format
In order to save the PDF generated from the APIs properly, the correct transport type should be passed into the `makeAPI()`.Below is an example of the same:
```
.makeApiCall({ url: `${url}v3/company/${companyID}/invoice/${invoiceNumber}/pdf?minorversion=59` , headers:{'Content-Type': 'application/pdf','Accept':'application/pdf'}, transport: popsicle.createTransport({type: 'buffer'})})
```
The response is an actual buffer( binary BLOB) which could then be saved to the file. 

### Auth-Response

The response provided by the client is a wrapped response of the below items which is what we call
authResponse, lets see how it looks like:

```text

    1. response             // response from `HTTP Client` used by library
    2. token                // instance of `Token` Object
    3. body                 // res.body in `text`
    4. json                 // res.body in `JSON`
    5. intuit_tid           // `intuit-tid` from response headers

```

A sample `AuthResponse` object would look similar to :

```json
{
  "token": {
    "realmId": "<realmId>",
    "token_type": "bearer",
    "access_token": "<access_token>",
    "refresh_token": "<refresh_token>",
    "expires_in": 3600,
    "x_refresh_token_expires_in": 8726400,
    "id_token": "<id_token>",
    "latency": 60000
  },
  "response": {
    "url": "https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer",
    "headers": {
      "content-type": "application/json;charset=UTF-8",
      "content-length": "61",
      "connection": "close",
      "server": "nginx",
      "strict-transport-security": "max-age=15552000",
      "intuit_tid": "1234-1234-1234-123",
      "cache-control": "no-cache, no-store",
      "pragma": "no-cache"
    },
    "body": "{\"id_token\":\"<id_token>\",\"expires_in\":3600,\"token_type\":\"bearer\",\"x_refresh_token_expires_in\":8726400,\"refresh_token\":\"<refresh_token>\",\"access_token\":\"<access_token>\"}",
    "status": 200,
    "statusText": "OK"
  },
  "body": "{\"id_token\":\"<id_token>\",\"expires_in\":3600,\"token_type\":\"bearer\",\"x_refresh_token_expires_in\":8726400,\"refresh_token\":\"<refresh_token>\",\"access_token\":\"<access_token>\"}",
  "json": {
    "access_token": "<access_token>",
    "refresh_token": "<refresh_token>",
    "token_type": "bearer",
    "expires_in": "3600",
    "x_refresh_token_expires_in": "8726400",
    "id_token": "<id_token>"
  },
  "intuit_tid": "4245c696-3710-1548-d1e0-d85918e22ebe"
}
```

You can use the below helper methods to make full use of the Auth Response Object :

```javascript
oauthClient.createToken(parseRedirect).then(function (authResponse) {
  console.log('The Token in JSON is  ' + JSON.stringify(authResponse.json));
  let status = authResponse.status();
  let body = authResponse.text();
  let jsonResponse = authResponse.json;
  let intuit_tid = authResponse.get_intuit_tid();
});
```

### Error Logging

By default the logging is `disabled` i.e set to `false`. However, to enable logging, pass
`logging=true` when you create the `oauthClient` instance :

```javascript
const oauthClient = new OAuthClient({
  clientId: '<Enter your clientId>',
  clientSecret: '<Enter your clientSecret>',
  environment: 'sandbox',
  redirectUri: '<http://localhost:8000/callback>',
  logging: true,
});
```

The logs would be captured under the directory `/logs/oAuthClient-log.log`

Whenever there is an error, the library throws an exception and you can use the below helper methods
to retrieve more information :

```javascript
oauthClient.createToken(parseRedirect).catch(function (error) {
  console.log(error);
});

/**
* This is how the Error Object Looks : 
{  
   "originalMessage":"Response has an Error",
   "error":"invalid_grant",
   "error_description":"Token invalid",
   "intuit_tid":"4245c696-3710-1548-d1e0-d85918e22ebe"
}
*/
```

## FAQ

You can refer to our [FAQ](https://github.com/intuit/oauth-jsclient/wiki/FAQ) if you have any
questions.

## Contributing

- You are welcome to send a PR to `develop` branch.
- The `master` branch will always point to the latest published version.
- The `develop` branch will contain the latest development/testing changes.

### Steps

- Fork and clone the repository (`develop` branch).
- Run `npm install` for dependencies.
- Run `npm test` to execute all specs.

## Changelog

See the changelog [here](https://github.com/intuit/oauth-jsclient/blob/master/CHANGELOG.md)

## License

Intuit `oauth-jsclient` is licensed under the
[Apache License, Version 2.0](https://github.com/intuit/oauth-jsclient/blob/master/LICENSE)

[ss1]: https://help.developer.intuit.com/s/SDKFeedback?cid=1120
