oniyi-http-plugin-credentials
Advanced tools
Comparing version 1.0.0 to 1.1.0
@@ -14,2 +14,3 @@ 'use strict'; | ||
const PLUGIN_NAME = 'attach-credentials'; | ||
const REQUEST_PHASE_NAME = 'credentials'; | ||
@@ -46,59 +47,67 @@ const defaults = { | ||
const extractRequestparams = makeRequestParamsExtractor(removeUserProp, userPropName); | ||
const extractRequestParams = makeRequestParamsExtractor(removeUserProp, userPropName); | ||
return { | ||
name: PLUGIN_NAME, | ||
load: (req, origParams, callback) => { | ||
// create a copy of provided request parameters (remove user prop if requested in `options`) | ||
const reqParams = extractRequestparams(origParams); | ||
const { [userPropName]: user } = origParams; | ||
onRequest: [{ | ||
phaseName: REQUEST_PHASE_NAME, | ||
handler: (ctx, next) => { | ||
const { options } = ctx; | ||
// create a copy of provided request parameters (remove user prop if requested in `options`) | ||
const reqParams = extractRequestParams(options); | ||
const { [userPropName]: user } = options; | ||
if (!user) { | ||
debug('No "%s" prop found in request params, skipping plugin operations', userPropName, origParams); | ||
// [bk] @TODO: should we use `reqParams` here instead? | ||
// That would mean we potentially pass back params without | ||
// the user prop and without credentials | ||
callback(null, origParams); | ||
return; | ||
} | ||
if (!user) { | ||
debug('Reason: No "%s" prop found in request options', userPropName, options); | ||
if (!_.isFunction(user[credentialsMethodName])) { | ||
const msg = `${userPropName}.${credentialsMethodName} must be a function`; | ||
debug(msg, { user }); | ||
callback(new TypeError(msg)); | ||
return; | ||
} | ||
user[credentialsMethodName](providerName, reqParams, (getCredentialsError, credentials) => { | ||
if (getCredentialsError) { | ||
debug( | ||
'Failed to load credentials for %s %s and provider %s', | ||
userPropName, | ||
getUserId(user), | ||
providerName, | ||
getCredentialsError | ||
); | ||
callback(getCredentialsError); | ||
// This would mean we potentially pass back params without | ||
// the user prop and without credentials | ||
next(); | ||
return; | ||
} | ||
if (!credentials) { | ||
// [bk] @TODO: add switch to plugin options to either abort or ignore this error. | ||
// currently we abort | ||
const err = new Error(`No credentials found for user "${getUserId(user)}" and provider "${providerName}"`); | ||
debug(err.message); | ||
callback(err); | ||
if (!_.isFunction(user[credentialsMethodName])) { | ||
const msg = `${userPropName}.${credentialsMethodName} must be a function`; | ||
debug(msg, { user }); | ||
next(new TypeError(msg)); | ||
return; | ||
} | ||
applyCredentials(reqParams, credentials, (applyCredentialsError, reqParamsWithCredentials) => { | ||
if (applyCredentialsError) { | ||
callback(applyCredentialsError); | ||
user[credentialsMethodName](providerName, reqParams, (getCredentialsError, credentials) => { | ||
if (getCredentialsError) { | ||
debug( | ||
'Failed to load credentials for %s %s and provider %s', | ||
userPropName, | ||
getUserId(user), | ||
providerName, | ||
getCredentialsError | ||
); | ||
next(getCredentialsError); | ||
return; | ||
} | ||
callback(null, reqParamsWithCredentials); | ||
if (!credentials) { | ||
// [bk] @TODO: add switch to plugin options to either abort or ignore this error. | ||
// currently we abort | ||
const err = new Error(`No credentials found for user "${getUserId(user)}" and provider "${providerName}"`); | ||
debug(err.message); | ||
next(err); | ||
return; | ||
} | ||
applyCredentials(reqParams, credentials, (applyCredentialsError, reqParamsWithCredentials) => { | ||
if (applyCredentialsError) { | ||
next(applyCredentialsError); | ||
return; | ||
} | ||
// once we acquired request params with credentials, need to update current ctx.options | ||
// so that next phase hook handler can use them as well | ||
_.assign(ctx, { options: reqParamsWithCredentials }); | ||
next(); | ||
}); | ||
}); | ||
}); | ||
} | ||
}, | ||
], | ||
}; | ||
}; |
{ | ||
"name": "oniyi-http-plugin-credentials", | ||
"version": "1.0.0", | ||
"version": "1.1.0", | ||
"description": "A plugin for oniyi-http-client for automatic attachment of user credentials", | ||
@@ -26,3 +26,3 @@ "homepage": "https://github.com/benkroeger/oniyi-http-plugin-credentials#readme", | ||
"devDependencies": { | ||
"ava": " ^0.23.0", | ||
"ava": " ^0.25.0", | ||
"eslint": "^4.10.0", | ||
@@ -29,0 +29,0 @@ "eslint-config-oniyi": "^5.0.2", |
# oniyi-http-plugin-credentials [![NPM version][npm-image]][npm-url] [![Dependency Status][daviddm-image]][daviddm-url] | ||
> An async plugin for oniyi-http-client to resolve and attach credentials to request params | ||
This plugin is designed to work with the [third-party login component](https://docs.strongloop.com/pages/releaseview.action?pageId=3836277) of [loopback](https://docs.strongloop.com/display/public/LB/LoopBack). | ||
The [third-party login component](https://docs.strongloop.com/pages/releaseview.action?pageId=3836277) is heavily based on [passportjs](http://passportjs.org/). | ||
To make things work more broadly, I also wrote a [loopback](https://docs.strongloop.com/display/public/LB/LoopBack) extension to allow the usage of custom auth schemes [oniyi-loopback-passport-custom-schemes](https://github.com/benkroeger/oniyi-loopback-passport-custom-schemes). | ||
resolve endpoint specific credentials asynchronusly and inject them into request options before making the actual request to endpoint | ||
@@ -20,4 +18,5 @@ ## Installation | ||
const clientOptions = {}; | ||
const httpClient = new OniyiHttpClient(clientOptions); | ||
const httpClientParams = { | ||
requestPhases: ['initial','credentials', 'final'], | ||
}; | ||
@@ -31,4 +30,14 @@ const pluginOptions = { | ||
const plugin = oniyiHttpPluginCredentials(pluginOptions); | ||
const phaseMapOptions = { | ||
requestPhaseMap: { | ||
credentials: 'newCredentialsPhase', | ||
}, | ||
responsePhaseMap: { | ||
final: 'end', | ||
}, | ||
}; | ||
httpClient.use(plugin); | ||
const httpClient = OniyiHttpClient | ||
.create(httpClientParams) // create custom http client with defined phase lists | ||
.use(plugin, phaseMapOptions); // mount a plugin | ||
``` | ||
@@ -47,15 +56,18 @@ | ||
`plugin.load()` retrieves an object with parameters (origParams) that will later be used to make an http(s) request. From there, the following flow is applied: | ||
This plugin relies on logic implemented in [oniyi-http-client](https://npmjs.org/package/oniyi-http-client), which has extensive documentation on how phase lists work and what conventions must be followed when implementing a plugin. | ||
Basically, we have implemented a phase list hook handler which gets invoked in `request phase list` of http client. | ||
Once `credentials` hook handler gets invoked, it receives a `ctx` and `next` params. Once we pull `options` from the context object, the following flow is applied: | ||
copy `origParams` into `reqParams`. Depending on `options.removeUserProp`, the original prop named `options.userPropName` will be omitted or included. | ||
read prop named `options.userPropName` from `origParams` into `user`. | ||
If `user` can not be found, abort flow and invoke callback with `origParams`. | ||
If `user[options.credentialsMethodName]` is not a function, invoke callback with `Error`. | ||
copy `options` into `reqParams`. Depending on `pluginOptions.removeUserProp`, the original prop named `pluginOptions.userPropName` will be omitted or included. | ||
read prop named `pluginOptions.userPropName` from `options` into `user`. | ||
If `user` can not be found, abort flow and invoke `next` function so that next plugin in this phase list can do its operations. | ||
If `user[pluginOptions.credentialsMethodName]` is not a function, invoke `next` with `Error`. | ||
Invoke `user[options.credentialsMethodName]` with `options.providerName` and `reqParams` as well as a callback function. | ||
Now `user[options.credentialsMethodName]` is supposed to resolve credentials for `user` and the authentication provider. This resolution should happen async and results be passed to our local callback (which takes `err` and `credentials` arguments). | ||
Invoke `user[pluginOptions.credentialsMethodName]` with `pluginOptions.providerName` and `reqParams` as well as a callback function. | ||
Now `user[pluginOptions.credentialsMethodName]` is supposed to resolve credentials for `user` and the authentication provider. This resolution should happen async and results be passed to our local callback (which takes `err` and `credentials` arguments). | ||
If an error occurs, plugin flow is aborted and `err` passed to callback. | ||
If `credentials` is falsy, plugin flow is also aborted and callback invoked with an according error. | ||
If `credentials` is falsy, plugin flow is also aborted and `next` gets invoked. | ||
At this point, we let `user[options.credentialsMethodName]` resolve credentials for the auth provider that this plugin instance is configured for – and no errors occurred. | ||
At this point, we let `user[pluginOptions.credentialsMethodName]` resolve credentials for the auth provider that this plugin instance is configured for – and no errors occurred. | ||
@@ -65,2 +77,3 @@ Now the plugin applies `credentials` to `reqParams`. For that, `credentials.type` is mapped against a list of supported credential types. If `credentials.type` is supported, that type specific implementation is invoked with `reqParams` and `credentials.payload`. | ||
Finally, we update the `ctx.options` object with latest `reqParamsWithCredentials` changes, and invoke `next` function. | ||
@@ -67,0 +80,0 @@ ## Credentials types |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
15188
156
109
0