Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding better typescript definitions #134

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,16 @@ Follow the instructions below to use the library :
1. Install the NPM package:

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

or
```sh
yarn install intuit-oauth-ts
```
2. Require the Library:

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

const oauthClient = new OAuthClient({
clientId: '<Enter your clientId>',
Expand Down
31 changes: 13 additions & 18 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
{
"name": "intuit-oauth",
"version": "4.0.0",
"name": "intuit-oauth-ts",
"version": "0.0.4",
"description": "Intuit Node.js client for OAuth2.0 and OpenIDConnect",
"main": "./src/OAuthClient.js",
"typings": "./src/OAuthClient.d.ts",
"author": "bigbizze",
"license": "MIT",
"scripts": {
"start": "node index.js",
"karma": "karma start karma.conf.js",
Expand All @@ -14,9 +17,7 @@
"test-watch": "mocha --watch --reporter=spec",
"test-debug": "mocha --inspect-brk --watch test",
"show-coverage": "npm test; open -a 'Google Chrome' coverage/index.html",
"clean-install": "rm -rf node_modules && npm install",
"snyk-protect": "snyk protect",
"prepublish": "npm run snyk-protect"
"clean-install": "rm -rf node_modules && npm install"
},
"keywords": [
"intuit-oauth",
Expand Down Expand Up @@ -55,27 +56,23 @@
},
"repository": {
"type": "git",
"url": "https://github.com/intuit/oauth-jsclient.git"
"url": "https://github.com/bigbizze/oauth-jsclient.git"
},
"author": {
"name": "Anil Kumar",
"email": "[email protected]"
},
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/intuit/oauth-jsclient/issues"
"url": "https://github.com/bigbizze/oauth-jsclient/issues"
},
"homepage": "https://github.com/intuit/oauth-jsclient",
"homepage": "https://github.com/bigbizze/oauth-jsclient",
"dependencies": {
"atob": "2.1.2",
"csrf": "^3.0.4",
"jsonwebtoken": "^8.3.0",
"jsonwebtoken": "^9.0.0",
"popsicle": "10.0.1",
"query-string": "^6.12.1",
"rsa-pem-from-mod-exp": "^0.8.4",
"winston": "^3.1.0"
},
"devDependencies": {
"@types/jsonwebtoken": "^9.0.1",
"btoa": "^1.2.1",
"chai": "^4.1.2",
"chai-as-promised": "^7.1.1",
Expand All @@ -87,8 +84,6 @@
"nock": "^9.2.3",
"nyc": "^15.0.1",
"prettier": "^2.0.5",
"sinon": "^9.0.2",
"snyk": "^1.316.1"
},
"snyk": true
"sinon": "^9.0.2"
}
}
95 changes: 95 additions & 0 deletions src/OAuthClient.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import * as winston from "winston";
import * as popsicle from 'popsicle';
import * as jwt from 'jsonwebtoken';
import { AuthResponse } from "./response/AuthResponse";
import { Token } from "./access-token/Token";
import * as Csrf from 'csrf';

interface Scopes {
Accounting: 'com.intuit.quickbooks.accounting',
Payment: 'com.intuit.quickbooks.payment',
Payroll: 'com.intuit.quickbooks.payroll',
TimeTracking: 'com.intuit.quickbooks.payroll.timetracking',
Benefits: 'com.intuit.quickbooks.payroll.benefits',
Profile: 'profile',
Email: 'email',
Phone: 'phone',
Address: 'address',
OpenId: 'openid',
Intuit_name: 'intuit_name',
}
interface Environment {
sandbox: 'https://sandbox-quickbooks.api.intuit.com/',
production: 'https://quickbooks.api.intuit.com/',
}

export default class OAuthClient {
static cacheId: 'cacheID';
static authorizeEndpoint: 'https://appcenter.intuit.com/connect/oauth2';
static tokenEndpoint: 'https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer';
static revokeEndpoint: 'https://developer.api.intuit.com/v2/oauth2/tokens/revoke';
static userinfo_endpoint_production:
'https://accounts.platform.intuit.com/v1/openid_connect/userinfo';
static userinfo_endpoint_sandbox:
'https://sandbox-accounts.platform.intuit.com/v1/openid_connect/userinfo';
static migrate_sandbox: 'https://developer-sandbox.api.intuit.com/v2/oauth2/tokens/migrate';
static migrate_production: 'https://developer.api.intuit.com/v2/oauth2/tokens/migrate';
static environment: Environment;
static jwks_uri: 'https://oauth.platform.intuit.com/op/v1/jwks';
static user_agent: string;
static scopes: Scopes;
environment: keyof Environment;
clientId: string;
clientSecret: string;
redirectUri: string;
token: Token;
logging: boolean;
logger: winston.Logger | null;
state: Csrf;

constructor(params: {
clientId: string,
clientSecret: string,
environment: keyof Environment,
redirectUri: string
});
getJson(): Record<string, any>;
setToken(opts: {
access_token: string;
refresh_token: string;
token_type: string;
expires_in: number;
x_refresh_token_expires_in: number;
id_token?: string;
createdAt?: number
}): Token;
setAuthorizeURLs(params: {
authorizeEndpoint: string;
tokenEndpoint: string;
revokeEndpoint: string;
userInfoEndpoint: string;
}): this;
authorizeUri(opts: {
scope: Scopes[keyof Scopes][],
state: string
}): string;
createToken(uri: string): Promise<AuthResponse>;
refresh(): Promise<AuthResponse>;
refreshUsingToken(refreshToken: string): Promise<AuthResponse>;
revoke(params?: { access_token: string, refresh_token: string }): Promise<AuthResponse>;
getUserInfo(): Promise<AuthResponse>;
makeApiCall(params: { transport: popsicle.TransportOptions, url: string, method: string, headers: Record<string, string>, body: Record<string, any> }): Promise<AuthResponse>;
validateIdToken(params?: { id_token: string }): Promise<boolean>;
getValidatedIdToken(params?: { id_token: string }): Promise<jwt.JwtPayload>;
getKeyFromJWKsURI(id_token: string, kid: string, request: popsicle.Request): Promise<jwt.JwtPayload>;
getPublicKey(): string;
getTokenRequest(request: popsicle.Request): Promise<AuthResponse>;
validateToken(): boolean;
loadResponse(request: popsicle.Request): Promise<popsicle.Response>;
loadResponseFromJWKsURI(request: popsicle.Request): Promise<popsicle.Response>;
createError(e: Error, authResponse: AuthResponse): Error;
isAccessTokenValid(): boolean;
getToken(): Token;
authHeader(): string;
log(level: keyof winston.config.NpmConfigSetLevels, message: string, messageData: string): void;
}
71 changes: 71 additions & 0 deletions src/OAuthClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,77 @@ OAuthClient.prototype.validateIdToken = function validateIdToken(params = {}) {
});
};

/**
* Validate id_token
* *
* @param {Object} params(optional)
* @returns {Promise<AuthResponse>}
*/
OAuthClient.prototype.getValidatedIdToken = function getValidatedIdToken(params = {}) {
return new Promise((resolve) => {
if (!this.getToken().id_token) throw new Error('The bearer token does not have id_token');

const id_token = this.getToken().id_token || params.id_token;

// Decode ID Token
const token_parts = id_token.split('.');
const id_token_header = JSON.parse(atob(token_parts[0]));
const id_token_payload = JSON.parse(atob(token_parts[1]));

// Step 1 : First check if the issuer is as mentioned in "issuer"
if (id_token_payload.iss !== 'https://oauth.platform.intuit.com/op/v1') return false;

// Step 2 : check if the aud field in idToken contains application's clientId
if (!id_token_payload.aud.find((audience) => audience === this.clientId)) return false;

// Step 3 : ensure the timestamp has not elapsed
if (id_token_payload.exp < Date.now() / 1000) return false;

const request = {
url: OAuthClient.jwks_uri,
method: 'GET',
headers: {
Accept: AuthResponse._jsonContentType,
'User-Agent': OAuthClient.user_agent,
},
};

return resolve(this.getKeyFromJWKsURI(id_token, id_token_header.kid, request));
})
.then((res) => {
this.log('info', 'The validateIdToken () response is : ', JSON.stringify(res, null, 2));
return res;
})
.catch((e) => {
this.log('error', 'The validateIdToken () threw an exception : ', JSON.stringify(e, null, 2));
throw e;
});
};

OAuthClient.prototype.getIdTokenComponents = async function(params = {}) {
if (!this.getToken().id_token) {
throw new Error('The bearer token does not have id_token');
}
const id_token = this.getToken().id_token || params.id_token;
const token_parts = id_token.split('.');
const id_token_header = JSON.parse(atob(token_parts[0]));
const id_token_payload = JSON.parse(atob(token_parts[1]));
if (id_token_payload.iss !== 'https://oauth.platform.intuit.com/op/v1') {
throw new Error('The issuer is not as mentioned in "issuer"');
}

// Step 2 : check if the aud field in idToken contains application's clientId
if (!id_token_payload.aud.find((audience) => audience === this.clientId)) {
throw new Error('The aud field in idToken does not contain application\'s clientId')
}

// Step 3 : ensure the timestamp has not elapsed
if (id_token_payload.exp < Date.now() / 1000) {
throw new Error('The timestamp has elapsed');
}
return { id_token_header, id_token_payload };
}

/**
* Get Key from JWKURI
* *
Expand Down
39 changes: 39 additions & 0 deletions src/access-token/Token.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@

export class Token {
realmId: string;
token_type: string;
access_token: string;
refresh_token: string;
expires_in: number;
x_refresh_token_expires_in: number;
id_token: string;
latency: number;
createdAt: number;
constructor(opts?: {
realmId: string,
token_type: string,
access_token: string,
refresh_token: string,
expires_in: number,
x_refresh_token_expires_in: number,
id_token: string,
latency: number,
createdAt: number
});
isAccessTokenValid(): boolean;
isRefreshTokenValid(): boolean;
accessToken(): string;
refreshToken(): string;
tokenType(): string;
getToken(): Token;
setToken(opts: {
access_token: string;
refresh_token: string;
token_type: string;
expires_in: number;
x_refresh_token_expires_in: number;
id_token?: string;
createdAt?: number
}): this;
clearToken(): this;
}
27 changes: 27 additions & 0 deletions src/response/AuthResponse.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import * as popsicle from "popsicle";
import { Token } from "../access-token/Token";

export class AuthResponse {
response: popsicle.Response;
body: string;
json: Record<string, any>;
intuit_tid: string;
token: Token;
constructor(params: {
token?: Token,
response?: popsicle.Response,
body?: string,
intuit_id?: string
});
processResponse(response: popsicle.Response): void;
getToken(): Token;
text(): string;
status(): number;
headers(): Record<string, any>;
valid(): boolean;
getJson(): Record<string, any>;
get_intuit_tid(): string;
isContentType(): boolean;
getContentType(): string;
isJson(): boolean;
}