1 | const _ = require('lodash')
|
2 | const got = require('got')
|
3 | const pupa = require('pupa')
|
4 | const { raiseUnauthorized, errorMessages } = require('./utils')
|
5 |
|
6 | /**
|
7 | * @function
|
8 | * @public
|
9 | *
|
10 | * Get api key endpoint its url with replaced placeholders.
|
11 | *
|
12 | * @param {Object} pluginOptions The plugin related options
|
13 | * @returns {string|false} The rendered url if available
|
14 | */
|
15 | function parseUrl (pluginOptions) {
|
16 | const { apiKey, clientId, realmUrl } = pluginOptions
|
17 |
|
18 | return !!apiKey && pupa(apiKey.url, {
|
19 | realm: realmUrl.split('/').slice(-1),
|
20 | clientId
|
21 | })
|
22 | }
|
23 |
|
24 | /**
|
25 | * @function
|
26 | * @public
|
27 | *
|
28 | * Extract the api key out of the original
|
29 | * incoming request if possible. Otherwise
|
30 | * return `false`.
|
31 | *
|
32 | * @param {Hapi.request} request The incoming request object
|
33 | * @param {Object} options The api key related options
|
34 | * @returns {string|false} The extracted api key if premises matched
|
35 | */
|
36 | function getApiKey (request, options) {
|
37 | const key = request[options.in][options.name]
|
38 | const hasApiKey = !!key && key.startsWith(options.prefix)
|
39 |
|
40 | return hasApiKey && key
|
41 | }
|
42 |
|
43 | /**
|
44 | * @function
|
45 | * @public
|
46 | *
|
47 | * Copy the authorization data of the original incoming request
|
48 | * to the request of the api key service. Extend headers or query
|
49 | * related to the settings. If there is no related key set or the
|
50 | * set key is not prefixed with the defined prefix, get `false`.
|
51 | * Otherwise the options for the proxied request.
|
52 | *
|
53 | * @param {Hapi.request} request The incoming request object
|
54 | * @param {Object} options The api key related options
|
55 | * @returns {Object|false} The request options if premises matched
|
56 | */
|
57 | function getRequestOptions (request, options) {
|
58 | const key = getApiKey(request, options)
|
59 | const path = `${options.in}.${options.name}`
|
60 | const requestOptions = Object.assign({ [options.in]: {} }, options.request)
|
61 |
|
62 | return key && _.set(requestOptions, path, key)
|
63 | }
|
64 |
|
65 | /**
|
66 | * @function
|
67 | * @public
|
68 | *
|
69 | * Extend the hapi request life cycle with an
|
70 | * additional api key interceptor.
|
71 | *
|
72 | * @param {Hapi.server} server The related hapi server object
|
73 | * @param {Object} options The api key related options
|
74 | * @param {string} url The url to be requested
|
75 | *
|
76 | * @throws {Boom.unauthorized} If requesting the access token failed
|
77 | */
|
78 | function extendLifeCycle (server, options, url) {
|
79 | server.ext('onRequest', async (request, h) => {
|
80 | const requestOptions = getRequestOptions(request, options)
|
81 |
|
82 | if (requestOptions) {
|
83 | try {
|
84 | const res = await got(url, requestOptions)
|
85 | const body = JSON.parse(res.body)
|
86 | const token = _.get(body, options.tokenPath)
|
87 |
|
88 | request.headers.authorization = `Bearer ${token}`
|
89 | } catch (err) {
|
90 | throw raiseUnauthorized(errorMessages.apiKey, err.message, options.prefix.trim())
|
91 | }
|
92 | }
|
93 |
|
94 | return h.continue
|
95 | })
|
96 | }
|
97 |
|
98 | /**
|
99 | * @function
|
100 | * @public
|
101 | *
|
102 | * Initialize the api key strategy if enabled by
|
103 | * user: parse the url based on the settings and
|
104 | * extend request life cycle.
|
105 | *
|
106 | * @param {Hapi.server} server The related hapi server object
|
107 | * @param {Object} pluginOptions The plugin related options
|
108 | */
|
109 | function init (server, pluginOptions) {
|
110 | const options = pluginOptions.apiKey
|
111 | const url = parseUrl(pluginOptions)
|
112 |
|
113 | if (options) {
|
114 | extendLifeCycle(server, options, url)
|
115 | }
|
116 | }
|
117 |
|
118 | module.exports = {
|
119 | parseUrl,
|
120 | getApiKey,
|
121 | getRequestOptions,
|
122 | extendLifeCycle,
|
123 | init
|
124 | }
|