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

IAM Support: phase II: token management #156

Merged
merged 1 commit into from
Apr 22, 2019
Merged
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
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) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since you are allowing both api key and token for iam auth, you might want to keep check for iam endpoint conditional.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sid41 the API states that the iam_endpoint is required, I wanted to keep the credential requirement consistent. Using the API-KEY header is not intended to be a high runner use case.

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