hapi-auth-keycloak
Version:
JSON Web Token based Authentication powered by Keycloak
130 lines (116 loc) • 3.45 kB
JavaScript
const _ = require('lodash')
const got = require('got')
const pupa = require('pupa')
const { raiseUnauthorized, errorMessages } = require('./utils')
/**
* @function
* @public
*
* Get api key endpoint its url with replaced placeholders.
*
* @param {Object} pluginOptions The plugin related options
* @returns {string|false} The rendered url if available
*/
function parseUrl (pluginOptions) {
const { apiKey, clientId, realmUrl } = pluginOptions
return !!apiKey && pupa(apiKey.url, {
...(realmUrl ? { realm: realmUrl.split('/').slice(-1) } : {}),
...(clientId ? { clientId } : {})
})
}
/**
* @function
* @public
*
* Extract the api key out of the original
* incoming request if possible. Otherwise
* return `false`.
*
* @param {Hapi.request} request The incoming request object
* @param {Object} options The api key related options
* @returns {string|false} The extracted api key if premises matched
*/
function getApiKey (request, options) {
const key = request[options.in][options.name]
const hasApiKey = !!key && key.startsWith(options.prefix)
return hasApiKey && key
}
/**
* @function
* @public
*
* Copy the authorization data of the original incoming request
* to the request of the api key service. Extend headers or query
* related to the settings. If there is no related key set or the
* set key is not prefixed with the defined prefix, get `false`.
* Otherwise the options for the proxied request.
*
* @param {Hapi.request} request The incoming request object
* @param {Object} options The api key related options
* @returns {Object|false} The request options if premises matched
*/
function getRequestOptions (request, options) {
const key = getApiKey(request, options)
const path = `${options.in}.${options.name}`
const requestOptions = Object.assign({ [options.in]: {} }, options.request)
return key && _.set(requestOptions, path, key)
}
/**
* @function
* @public
*
* Extend the hapi request life cycle with an
* additional api key interceptor.
*
* @param {Hapi.server} server The related hapi server object
* @param {Object} options The api key related options
* @param {string} url The url to be requested
*
* @throws {Boom.unauthorized} If requesting the access token failed
*/
function extendLifeCycle (server, options, url) {
server.ext('onRequest', async (request, h) => {
const requestOptions = getRequestOptions(request, options)
if (requestOptions) {
try {
const res = await got(url, requestOptions)
const body = JSON.parse(res.body)
const token = _.get(body, options.tokenPath)
request.headers.authorization = `Bearer ${token}`
} catch (err) {
throw raiseUnauthorized(
errorMessages.apiKey,
err.message,
null,
options.prefix.trim()
)
}
}
return h.continue
})
}
/**
* @function
* @public
*
* Initialize the api key strategy if enabled by
* user: parse the url based on the settings and
* extend request life cycle.
*
* @param {Hapi.server} server The related hapi server object
* @param {Object} pluginOptions The plugin related options
*/
function init (server, pluginOptions) {
const options = pluginOptions.apiKey
const url = parseUrl(pluginOptions)
if (options) {
extendLifeCycle(server, options, url)
}
}
module.exports = {
parseUrl,
getApiKey,
getRequestOptions,
extendLifeCycle,
init
}