Skip to content

Commit

Permalink
Merge pull request #156 from IBM-Cloud/iam-token
Browse files Browse the repository at this point in the history
IAM Support: phase II: token management
  • Loading branch information
srl295 authored Apr 22, 2019
2 parents 9e25239 + c0f6940 commit 0e649da
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 39 deletions.
17 changes: 16 additions & 1 deletion lib/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,14 +85,28 @@ class Client {

// instantiate the promised swagger client
Object.defineProperty(this,
'swaggerClient', {
'_swaggerClient', {
configurable: true,
enumerable: false,
value: this.createSwaggerClient(),
writable: false
});
}

/**
* Return the swagger client, after updating token.
* All access to the client must go through this.
* @private
*/
get swaggerClient() {
if(this.updateToken) {
return this._swaggerClient
.then(this.updateToken);
} else {
return this._swaggerClient;
}
}

/**
* Version number of the REST service used. Currently ‘V2’.
*/
Expand Down Expand Up @@ -125,6 +139,7 @@ class Client {
url: schemaUrl,
requestInterceptor: (req) => gpiam.apply(req)
});
this.updateToken = gpiam.updateToken; // Call this before using the client.
return clientPromise;
} else {
// assume GP credentials
Expand Down
74 changes: 70 additions & 4 deletions lib/gp-iam.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,19 @@
*/

/* eslint no-console: "off" */
const { URL } = require('url');
const bent = require('bent');
const querystring = require('querystring');
const grant_type = 'urn:ibm:params:oauth:grant-type:apikey';
const tokenExpiryThreshold= process.env.IAM_TOKEN_EXPIRY_THRESHOLD_PROP_KEY || 0.85;
const fetchToken = bent('json', // JSON response
'POST', // POST method
{
// headers
'Content-Type': 'application/x-www-form-urlencoded',
Accept: 'application/json'
},
200); // expect 200

/**
* Manage use of IAM API Keys and Tokens
Expand All @@ -29,12 +42,59 @@ const GpIAM = function GpIAM(credentials) {
if(!this.credentials || !this.credentials.apikey || !this.credentials.iam_endpoint) {
throw new Error('GpIAM: params need to be "apikey, iam_endpoint"');
}
this.tokenUrl = new URL('/identity/token', this.credentials.iam_endpoint);

if(!this.GP_USE_APIKEY) {
const now = new Date();
this.ourCacheKey = `${this.credentials.iam_endpoint}#${this.credentials.apikey}`;

this.updateToken = (async function updateToken(x) {
const apikey = this.credentials.apikey;
const cacheEntry = GpIAM.prototype.tokenCache[this.ourCacheKey] =
GpIAM.prototype.tokenCache[this.ourCacheKey] || {/* initialized to empty */};
// if(this.VERBOSE) {
// const tokenn = token.n = (token.n || 0) + 1;
// console.log(`Token count: ${tokenn} ${this.ourCacheKey}`);
// }

if (!cacheEntry.validUntil || // no token
(now > cacheEntry.validUntil)) { // or expired

delete this.access_token; // in case we run into a failure.
const tokenResponse = await fetchToken(this.tokenUrl, Buffer.from(querystring.stringify({
grant_type,
apikey
})));

// save the entire response
cacheEntry.tokenResponse = tokenResponse;

// get the response time
const { expires_in } = tokenResponse;

cacheEntry.validUntil = new Date(now.getTime() + (1000 * expires_in * tokenExpiryThreshold));
if(this.VERBOSE) {
console.log('Fetched access token ');
}
}
if(this.VERBOSE) {
console.log(`Token valid until ${cacheEntry.validUntil}`);
}

// save off the access token
this.access_token = cacheEntry.tokenResponse.access_token;

return x;
}).bind(this);
}
};

GpIAM.prototype.tokenCache = {};

GpIAM.prototype.API_KEY = "API-KEY"; // GP SPECIFIC header

GpIAM.prototype.VERBOSE = process.env.GP_VERBOSE || false;
GpIAM.prototype.GP_USE_APIKEY = process.env.GP_USE_APIKEY || true; // if false: use token manager
GpIAM.prototype.GP_USE_APIKEY = process.env.GP_USE_APIKEY || false; // if false: use token manager

/**
* Generate HTTP Authorization header.
Expand All @@ -43,9 +103,15 @@ GpIAM.prototype.apply = function(obj) {
if(this.VERBOSE) console.dir(obj, {color: true, depth: null});
if(obj.url.indexOf("/swagger.json") !== -1) return obj; // skip for swagger.json

const authHeader = this.API_KEY + ' ' + this.credentials.apikey;
if(this.VERBOSE) console.log('Authorization: ' + authHeader.replace(this.credentials.apikey, '****'));
obj.headers.Authorization = authHeader;
if(this.access_token) {
const authHeader = 'Bearer ' + this.access_token;
if(this.VERBOSE) console.log('Authorization: ' + authHeader.replace(this.access_token, '****'));
obj.headers.Authorization = authHeader;
} else {
const authHeader = this.API_KEY + ' ' + this.credentials.apikey;
if(this.VERBOSE) console.log('Authorization: ' + authHeader.replace(this.credentials.apikey, '****'));
obj.headers.Authorization = authHeader;
}
return obj;
};

Expand Down
84 changes: 50 additions & 34 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"request": "^2.87.0"
},
"dependencies": {
"bent": "^1.4.0",
"g11n-pipeline-flatten": "^2.0.0",
"minimist": "^1.2.0",
"swagger-client": "^3.8.25"
Expand Down

0 comments on commit 0e649da

Please sign in to comment.