This is an automated email from the ASF dual-hosted git repository. jamesthomas pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/incubator-openwhisk-client-js.git
The following commit(s) were added to refs/heads/master by this push: new 5e2b541 add authHandler option to deal with authorization header (#132) 5e2b541 is described below commit 5e2b541b413a9df2a3d9a01596ee1bf2129e2634 Author: Carlos Santana <csantan...@apache.org> AuthorDate: Fri Aug 17 04:28:05 2018 -0400 add authHandler option to deal with authorization header (#132) * add authHandler option Closes #131 --- README.md | 18 ++++++++++++++++++ lib/client.js | 42 ++++++++++++++++++++++++------------------ test/unit/client.test.js | 28 +++++++++++++++++++--------- 3 files changed, 61 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 74df04e..dd4a346 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,24 @@ var ow = openwhisk(options); ow.actions.invoke('sample').then(result => console.log(result)) ``` +#### using 3rd party authentication handler +You can specify an authentication handler in `options.auth_handler` this is an object that provides a function `getAuthHeader` that returns a Promise or String to be used in the `Authorization` http header for every http request. +```javascript +const authHandler = { + getAuthHeader: ()=>{ + return Promise.resolve('Basic user:password') + } +} +var openwhisk = require('openwhisk'); +var options = { + apihost: 'openwhisk.ng.bluemix.net', + auth_handler: authHandler +} +var ow = openwhisk(options) +ow.actions.invoke('sample').then(result => console.log(result)) +``` + + ### constructor options _Client constructor supports the following mandatory parameters:_ diff --git a/lib/client.js b/lib/client.js index 48723e3..7dd0982 100644 --- a/lib/client.js +++ b/lib/client.js @@ -66,6 +66,7 @@ class Client { * @param {boolean} [options.ignore_certs] * @param {string} [options.apigw_token] * @param {string} [options.apigw_space_guid] + * @param {Function} [options.auth_handler] */ constructor (options) { this.options = this.parseOptions(options || {}) @@ -91,13 +92,13 @@ class Client { apigwSpaceGuid = apiKey.split(':')[0] } - if (!apiKey) { - throw new Error(`${messages.INVALID_OPTIONS_ERROR} Missing api_key parameter.`) + if (!apiKey && !options.auth_handler) { + throw new Error(`${messages.INVALID_OPTIONS_ERROR} Missing api_key parameter or token plugin.`) } else if (!api) { throw new Error(`${messages.INVALID_OPTIONS_ERROR} Missing either api or apihost parameters.`) } - return {apiKey: apiKey, api, ignoreCerts: ignoreCerts, namespace: options.namespace, apigwToken: apigwToken, apigwSpaceGuid: apigwSpaceGuid} + return {apiKey: apiKey, api, ignoreCerts: ignoreCerts, namespace: options.namespace, apigwToken: apigwToken, apigwSpaceGuid: apigwSpaceGuid, authHandler: options.auth_handler} } urlFromApihost (apihost) { @@ -113,21 +114,23 @@ class Client { } request (method, path, options) { - const req = this.params(method, path, options) - return rp(req).catch(err => this.handleErrors(err)) + const params = this.params(method, path, options) + return params.then(req => rp(req)).catch(err => this.handleErrors(err)) } params (method, path, options) { - return Object.assign({ - json: true, - method: method, - url: this.pathUrl(path), - rejectUnauthorized: !this.options.ignoreCerts, - headers: { - 'User-Agent': (options && options['User-Agent']) || 'openwhisk-client-js', - Authorization: this.authHeader() - } - }, options) + return this.authHeader().then(header => { + return Object.assign({ + json: true, + method: method, + url: this.pathUrl(path), + rejectUnauthorized: !this.options.ignoreCerts, + headers: { + 'User-Agent': (options && options['User-Agent']) || 'openwhisk-client-js', + Authorization: header + } + }, options) + }) } pathUrl (urlPath) { @@ -143,10 +146,13 @@ class Client { } authHeader () { - const apiKeyBase64 = Buffer.from(this.options.apiKey).toString('base64') - return `Basic ${apiKeyBase64}` + if (this.options.authHandler) { + return this.options.authHandler.getAuthHeader() + } else { + const apiKeyBase64 = Buffer.from(this.options.apiKey).toString('base64') + return Promise.resolve(`Basic ${apiKeyBase64}`) + } } - handleErrors (reason) { let message = `Unknown Error From API: ${reason.message}` if (reason.hasOwnProperty('statusCode')) { diff --git a/test/unit/client.test.js b/test/unit/client.test.js index dc97643..6d8fad3 100644 --- a/test/unit/client.test.js +++ b/test/unit/client.test.js @@ -108,12 +108,12 @@ test('should handle multiple api parameter formats', t => { t.is(client.urlFromApihost('http://my_host:80'), 'http://my_host:80/api/v1/') }) -test('should return default request parameters without options', t => { +test('should return default request parameters without options', async t => { const client = new Client({api_key: 'username:password', apihost: 'blah'}) const METHOD = 'get' const PATH = 'some/path/to/resource' - const params = client.params(METHOD, PATH) + const params = await client.params(METHOD, PATH) t.is(params.url, 'https://blah/api/v1/some/path/to/resource') t.is(params.method, METHOD) t.true(params.json) @@ -121,13 +121,13 @@ test('should return default request parameters without options', t => { t.true(params.headers.hasOwnProperty('Authorization')) }) -test('should return request parameters with merged options', t => { +test('should return request parameters with merged options', async t => { const client = new Client({api_key: 'username:password', apihost: 'blah'}) const METHOD = 'get' const PATH = 'some/path/to/resource' const OPTIONS = {b: {bar: 'foo'}, a: {foo: 'bar'}} - const params = client.params(METHOD, PATH, OPTIONS) + const params = await client.params(METHOD, PATH, OPTIONS) t.is(params.url, 'https://blah/api/v1/some/path/to/resource') t.is(params.method, METHOD) t.true(params.json) @@ -137,20 +137,30 @@ test('should return request parameters with merged options', t => { t.deepEqual(params.b, {bar: 'foo'}) }) -test('should return request parameters with explicit api option', t => { +test('should return request parameters with explicit api option', async t => { const client = new Client({api_key: 'username:password', api: 'https://api.com/api/v1'}) const METHOD = 'get' const PATH = 'some/path/to/resource' - t.is(client.params(METHOD, PATH).url, 'https://api.com/api/v1/some/path/to/resource') + t.is((await client.params(METHOD, PATH)).url, 'https://api.com/api/v1/some/path/to/resource') client.options.api += '/' - t.is(client.params(METHOD, PATH).url, 'https://api.com/api/v1/some/path/to/resource') + t.is((await client.params(METHOD, PATH)).url, 'https://api.com/api/v1/some/path/to/resource') }) -test('should generate auth header from API key', t => { +test('should generate auth header from API key', async t => { const apiKey = 'some sample api key' const client = new Client({api: true, api_key: apiKey}) - t.is(client.authHeader(), `Basic ${Buffer.from(apiKey).toString('base64')}`) + t.is(await client.authHeader(), `Basic ${Buffer.from(apiKey).toString('base64')}`) +}) + +test('should generate auth header from 3rd party authHandler plugin', async t => { + const authHandler = { + getAuthHeader: () => { + return Promise.resolve('Basic user:password') + } + } + const client = new Client({api: true, auth_handler: authHandler}) + t.is(await client.authHeader(), `Basic user:password`) }) test('should return path and status code in error message', t => {