Skip to content

Commit

Permalink
feat: add new token manager for ICP4D
Browse files Browse the repository at this point in the history
Introduce a constructor parameter, `authentication_type`, for specifying the authentication pattern. Required for using ICP4D
  • Loading branch information
dpopp07 committed May 28, 2019
1 parent 257dc92 commit ee1ddad
Show file tree
Hide file tree
Showing 9 changed files with 841 additions and 1,029 deletions.
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
export interface IcpTokenData {
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 {
private username: string;
private password: string;

/**
* ICP Token Manager Service
*
* Retreives, stores, and refreshes ICP tokens.
*
* @param {Object} options
* @param {String} options.icpApikey
* @param {String} options.icpAccessToken
* @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';
} 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.
* @returns {void}
*/
protected requestToken(cb: Function): void {
const parameters = {
options: {
url: this.url,
method: 'GET',
headers: {
Authorization:
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.
*
* 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');
}

/**
* 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 {
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;
}

/**
* 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

0 comments on commit ee1ddad

Please sign in to comment.