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

feat: add new token manager for ICP4D #26

Merged
merged 19 commits into from
Jun 5, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
ee1ddad
feat: add new token manager for ICP4D
dpopp07 May 28, 2019
cfa3e1b
feat: add `IcpTokenManagerV1` as a top-level export of the package
dpopp07 May 28, 2019
de711e7
docs: add some documentation on `authentication_type` in the README
dpopp07 May 28, 2019
bd902eb
chore: address pr comments
dpopp07 May 28, 2019
4f2f789
feat: carry `disable_ssl_verification` through to token managers
dpopp07 May 28, 2019
70cfc23
refactor: re-organize auth folder in sdk
dpopp07 May 29, 2019
d91cd06
refactor: remove disable_ssl_verification support in iam
dpopp07 May 29, 2019
4ba77ca
refactor: refactor the token expiration time calculation
dpopp07 May 30, 2019
81a5da2
test: add unit tests for JwtTokenManagerV1
dpopp07 May 30, 2019
f06beb5
Merge branch 'master' into icp4d-authentication
dpopp07 May 30, 2019
6d718fa
test: add unit tests for icp token manager
dpopp07 May 31, 2019
3adbe10
test: add unit tests for base service changes
dpopp07 May 31, 2019
12028fc
refactor: move computeBasicAuthHeader to its own module
dpopp07 Jun 3, 2019
799a876
refactor: rename icp_access_token to icp4d_access_token
dpopp07 Jun 3, 2019
0f4ece7
docs: update copyright date in iam-token-manager-v1.js
dpopp07 Jun 3, 2019
1fc9fb1
build: ignore linting .js files under auth directory
dpopp07 Jun 3, 2019
91cbaf0
test: update base service test to use icp4d_access_token
dpopp07 Jun 3, 2019
9ac1cee
docs: remove references to "refreshing" tokens
dpopp07 Jun 4, 2019
dc3165d
refactor: add parameter `icp4d_url`
dpopp07 Jun 5, 2019
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ doc/
.env
.eslintcache
lib/*.js
auth/*.js
iam-token-manager/*.js
index.js
.nyc_output
Expand Down
97 changes: 97 additions & 0 deletions auth/icp-token-manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/**
* Copyright 2019 IBM Corp. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import extend = require('extend');
import { sendRequest } from '../lib/requestwrapper';
import { JwtTokenManager } from './jwt-token-manager';

export type Options = {
url: string;
accessToken?: string;
username?: string;
password?: string;
}

// this interface is a representation of the response
// object from the ICP service
dpopp07 marked this conversation as resolved.
Show resolved Hide resolved
export interface IcpTokenData {
dpopp07 marked this conversation as resolved.
Show resolved Hide resolved
username: string;
role: string;
permissions: string[];
sub: string;
iss: string;
aud: string;
uid: string;
_messageCode_: string;
message: string;
accessToken: string;
}

export class IcpTokenManagerV1 extends JwtTokenManager {
dpopp07 marked this conversation as resolved.
Show resolved Hide resolved
private username: string;
private password: string;

/**
* ICP Token Manager Service
*
* Retreives, stores, and refreshes ICP tokens.
*
* @param {Object} options
* @param {String} options.icpApikey
dpopp07 marked this conversation as resolved.
Show resolved Hide resolved
* @param {String} options.icpAccessToken
dpopp07 marked this conversation as resolved.
Show resolved Hide resolved
* @param {String} options.url - url of the icp api to retrieve tokens from
* @constructor
*/
constructor(options: Options) {
super(options);

this.tokenName = 'accessToken';

if (this.url) {
this.url = this.url + '/v1/preauth/validateAuth';
dpopp07 marked this conversation as resolved.
Show resolved Hide resolved
} else {
// this is required
console.error('`url` is a required parameter for the ICP token manager.');
}
if (options.username) {
this.username = options.username;
}
if (options.password) {
this.password = options.password;
}
// username and password are required too, unless there's access token
}

/**
* Request an ICP token using a basic auth header.
*
* @param {Function} cb - The callback that handles the response.
dpopp07 marked this conversation as resolved.
Show resolved Hide resolved
* @returns {void}
*/
protected requestToken(cb: Function): void {
const parameters = {
options: {
url: this.url,
method: 'GET',
headers: {
Authorization:
dpopp07 marked this conversation as resolved.
Show resolved Hide resolved
this.computeBasicAuthHeader(this.username, this.password),
},
}
};
sendRequest(parameters, cb);
}
}
169 changes: 169 additions & 0 deletions auth/jwt-token-manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
/**
* Copyright 2015 IBM Corp. All Rights Reserved.
dpopp07 marked this conversation as resolved.
Show resolved Hide resolved
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import extend = require('extend');
import jwt = require('jsonwebtoken');
import { sendRequest } from '../lib/requestwrapper';

function getCurrentTime(): number {
return Math.floor(Date.now() / 1000);
}

export type Options = {
accessToken?: string;
url?: string;
}

export class JwtTokenManager {
protected url: string;
protected tokenName: string;
protected userAccessToken: string;
private tokenInfo: any;
private timeToLive: number;
private expireTime: number;

/**
* Token Manager Service
*
* Retreives, stores, and refreshes JSON web tokens.
*
* @param {Object} options
* @param {String} options.url - url of the api to retrieve tokens from
* @param {String} options.accessToken
* @constructor
*/
constructor(options: Options) {
this.tokenInfo = {};

this.tokenName = 'access_token';

if (options.url) {
this.url = options.url;
}
if (options.accessToken) {
this.userAccessToken = options.accessToken;
}
}

/**
* This function sends an access token back through a callback. The source of the token
* is determined by the following logic:
* 1. If user provides their own managed access token, assume it is valid and send it
* 2. a) If this class is managing tokens and does not yet have one, make a request for one
* b) If this class is managing tokens and the token has expired, request a new one
* 3. If this class is managing tokens and has a valid token stored, send it
*
* @param {Function} cb - callback function that the token will be passed to
*/
public getToken(cb: Function) {
if (this.userAccessToken) {
// 1. use user-managed token
return cb(null, this.userAccessToken);
} else if (!this.tokenInfo[this.tokenName] || this.isTokenExpired()) {
// 2. request a new token
this.requestToken((err, tokenResponse) => {
this.saveTokenInfo(tokenResponse);
return cb(err, this.tokenInfo[this.tokenName]);
});
} else {
// 3. use valid, sdk-managed token
return cb(null, this.tokenInfo[this.tokenName]);
}
}

/**
* Set a self-managed access token.
* The access token should be valid and not yet expired.
*
* By using this method, you accept responsibility for managing the
* access token yourself. You must set a new access token before this
* one expires. Failing to do so will result in authentication errors
* after this token expires.
*
* @param {string} accessToken - A valid, non-expired access token
* @returns {void}
*/
public setAccessToken(accessToken: string): void {
this.userAccessToken = accessToken;
}

/**
* Request a JWT using an API key.
*
* @param {Function} cb - The callback that handles the response.
* @returns {void}
*/
protected requestToken(cb: Function): void {
cb(null, 'token');
dpopp07 marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* Compute and return a Basic Authorization header from a username and password.
*
* @param {string} username - The username or client id
* @param {string} password - The password or client secret
* @returns {string}
*/
protected computeBasicAuthHeader(username, password): string {
dpopp07 marked this conversation as resolved.
Show resolved Hide resolved
const encodedCreds = Buffer.from(`${username}:${password}`).toString('base64');
return `Basic ${encodedCreds}`;
}

/**
* Check if currently stored token is expired.
*
* Using a buffer to prevent the edge case of the
* token expiring before the request could be made.
*
* The buffer will be a fraction of the total TTL. Using 80%.
*
* @private
* @returns {boolean}
*/
private isTokenExpired(): boolean {
const { timeToLive, expireTime } = this;

if (!timeToLive || !expireTime) {
return true;
}

const fractionOfTtl = 0.8;
const currentTime = getCurrentTime();
const refreshTime = expireTime - (timeToLive * (1.0 - fractionOfTtl));
return refreshTime < currentTime;
dpopp07 marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* Decode the access token and save the response from the JWT service to the object's state.
*
* @param tokenResponse - Response object from JWT service request
* @private
* @returns {void}
*/
private saveTokenInfo(tokenResponse): void {
const accessToken = tokenResponse[this.tokenName];

// the time of expiration is found by decoding the JWT access token
const decodedResponse = jwt.decode(accessToken);
const { exp, iat } = decodedResponse;

// exp is the time of expire and iat is the time of token retrieval
this.timeToLive = exp - iat;
this.expireTime = exp;

this.tokenInfo = extend({}, tokenResponse);
}
}
Loading