UNPKG

9.23 kBJavaScriptView Raw
1'use strict';
2
3var debug = require('debug')('plugin:oauth');
4
5var basicAuth = require('../lib/basicAuth');
6//
7var fs = require('fs');
8var path = require('path');
9var requestLib = require('request');
10
11
12class apiKeyAuthorization extends basicAuth.BasicAuthorizerPlugin {
13
14 constructor(config, logger, stats, authType) {
15 //
16 super(config, logger, stats, authType);
17
18 this.request = config.request ? requestLib.defaults(config.request) : requestLib;
19
20 this.cacheKey = false;
21
22 this.enableCache();
23
24 this.updateConfig2(config);
25 }
26
27 updateConfig2(config) {
28 super.updateConfig();
29 //
30 this.apiKeyHeaderName = config.hasOwnProperty('api-key-header') ? config['api-key-header'] : 'x-api-key';
31 this.cacheKey = config.hasOwnProperty('cacheKey') ? config.cacheKey : false;
32
33 //support for enabling oauth or api key only
34 this.oauth_only = config.hasOwnProperty('allowOAuthOnly') ? config.allowOAuthOnly : false;
35 this.apikey_only = config.hasOwnProperty('allowAPIKeyOnly') ? config.allowAPIKeyOnly : false;
36 }
37
38 // -------- -------- -------- -------- -------- --------
39
40 missingApiKey(req,res) {
41 debug('missing api key');
42 return this.sendError(req, res, this.next, this.logger, this.stats, 'invalid_authorization', 'Missing API Key header');
43 }
44
45 apikeyGatewayTimeout(req, res, message) {
46 debug('verify apikey gateway timeout');
47 return this.sendError(req, res, this.next, this.logger, this.stats, 'gateway_timeout', message);
48 }
49
50 // -------- -------- -------- -------- -------- --------
51
52 //
53 exchangeApiKeyForToken(req, res, apiKey) {
54
55 this.setApiKey(apiKey)
56
57 var cacheControl = req.headers['cache-control'] || 'no-cache';
58 var cacheAllowed = ( !cacheControl || (cacheControl && cacheControl.indexOf('no-cache') < 0) );
59
60 if ( this.cacheEnabled || cacheAllowed ) { // caching is allowed
61 //
62 this.cache.read(apiKey, function(err, value) {
63 if ( err ) {
64 //
65 }
66 if ( value ) {
67 if ( (Date.now() / 1000) < value.exp ) { // not expired yet (token expiration is in seconds)
68 debug('api key cache hit', apiKey);
69 return this.authorize(req, res, value);
70 } else {
71 this.cache.remove(apiKey);
72 debug('api key cache expired', apiKey);
73 this.requestApiKeyJWT(req, res);
74 }
75 } else {
76 debug('api key cache miss', apiKey);
77 this.requestApiKeyJWT(req, res);
78 }
79 });
80 } else {
81 this.requestApiKeyJWT(req, res);
82 }
83
84 }
85
86
87 ifCachingCache(req,decodedToken) {
88 if ( (this.apiKey !== undefined) && this.apiKey ) {
89 var cacheControl = req.headers['cache-control'] || 'no-cache';
90 if ( this.cacheEnabled || this.cacheKey || (!cacheControl || (cacheControl && cacheControl.indexOf('no-cache') < 0)) ) { // caching is allowed
91 // default to now (in seconds) + 30m if not set
92 decodedToken.exp = decodedToken.exp || +(((Date.now() / 1000) + 1800).toFixed(0));
93 this.cache.store(this.apiKey, decodedToken,decodedToken.exp);
94 debug('api key cache store', this.apiKey);
95 } else {
96 debug('api key cache skip', this.apiKey);
97 }
98 }
99 }
100
101
102 setApiKeyOptions() {
103
104 var api_key_options = {
105 url: this.config.verify_api_key_url,
106 method: 'POST',
107 json: {
108 'apiKey': this.apiKey
109 },
110 headers: {
111 'x-dna-api-key': this.apiKey
112 }
113 };
114
115 var agentOptions = this.config.agentOptions;
116
117 if ( agentOptions ) {
118 if ( agentOptions.requestCert ) {
119 api_key_options.requestCert = true;
120 if ( agentOptions.cert && agentOptions.key ) {
121 var keyPath = path.resolve(agentOptions.key)
122 api_key_options.key = fs.readFileSync(keyPath, 'utf8');
123 var certPath = path.resolve(agentOptions.cert)
124 api_key_options.cert = fs.readFileSync(certPath,'utf8');
125 if ( agentOptions.ca ) {
126 var caPath = path.resolve(agentOptions.ca)
127 api_key_options.ca = fs.readFileSync(caPath, 'utf8');
128 }
129 } else if ( agentOptions.pfx ) {
130 var pfxPath = path.resolve(agentOptions.pfx)
131 api_key_options.pfx = fs.readFileSync(pfxPath);
132 }
133 if ( agentOptions.rejectUnauthorized ) {
134 api_key_options.rejectUnauthorized = true;
135 }
136 if ( agentOptions.secureProtocol ) {
137 api_key_options.secureProtocol = true;
138 }
139 if ( agentOptions.ciphers ) {
140 api_key_options.ciphers = agentOptions.ciphers;
141 }
142 if ( agentOptions.passphrase ) api_key_options.passphrase = agentOptions.passphrase;
143 }
144 }
145
146 return(api_key_options)
147 }
148
149 requestApiKeyJWT(req, res) {
150
151 if ( !(this.config.verify_api_key_url) ) return this.sendError(req, res, 'invalid_request', 'API Key Verification URL not configured');
152
153 var api_key_options = this.setApiKeyOptions();
154
155 var request = this.request;
156 var self = this;
157
158 request(api_key_options, function(err, keyRes, body) {
159 if ( err ) {
160 return this.apikeyGatewayTimeout(req, res, err.message)
161 }
162 if ( keyRes.statusCode !== 200 ) {
163 return this.accessDenied(req, res, "api key:: " + keyRes.message)
164 }
165 self.verify(body, req, res);
166 });
167
168
169 }
170}
171
172
173
174module.exports.init = function(config, logger, stats) {
175
176 if ( config === undefined || !config ) return(undefined);
177 //
178 var authObj = new apiKeyAuthorization(config, logger, stats,'oauth');
179
180 var middleware = function(req, res, next) {
181
182 if ( !req || !res ) return(-1); // need to check bad args
183 if ( !req.headers ) return(-1); // or throw -- means callers are bad
184
185 authObj.updateConfig2(config)
186 //
187 authObj.setNext(next);
188
189 // PARAMETERS FROM REQUEST OBJECT
190 // prefer an authorization header if available
191 var authHeader = req.headers[this.authHeaderName]
192 var haveAuthHeader = (authHeader !== undefined) && authHeader;
193
194 // attempt to get a valid api key
195 var apiKey = req.headers[this.apiKeyHeaderName]
196 var haveApiKey = (apiKey !== undefined) && apiKey;
197 if ( !(haveApiKey) ) { // try alternative
198 if ( req.reqUrl && req.reqUrl.query ) {
199 apiKey = req.reqUrl.query[this.apiKeyHeaderName];
200 haveApiKey = (apiKey !== undefined) && apiKey;
201 }
202 }
203
204 //
205 //support for enabling oauth or api key only
206 if ( authObj.oauth_only ) {
207 if ( haveAuthHeader ) {
208 var [matches,code] = authObj.matchHeader(authHeader)
209 if ( !(matches) ) return(code)
210 } else {
211 // no header give it a chance to bipass
212 if ( authObj.allowNoAuthorization ) {
213 return next();
214 } else {
215 return( authObj.missingAuthorization(req, res) );
216 }
217 }
218 } else if ( authObj.apikey_only ) {
219 if ( !(haveApiKey) ) {
220 return( authObj.missingApiKey(req,res) );
221 }
222 }
223
224 //leaving rest of the code same to ensure backward compatibility
225 if ( !(haveAuthHeader) || authObj.allowAPIKeyOnly ) {
226 if ( haveApiKey ) {
227 // API KEY ALTERNATIVE
228 authObj.exchangeApiKeyForToken(req, res, apiKey);
229 //
230 } else if ( authObj.allowNoAuthorization ) {
231 return next();
232 } else {
233 return( authObj.missingAuthorization(req, res) );
234 }
235 } else {
236 return( authObj.authFromToken(req,res,authHeader) )
237 }
238 } // end of middleware
239
240 return {
241
242 onrequest: function(req, res, next) {
243 if ( process.env.EDGEMICRO_LOCAL === "1" ) {
244 debug ("MG running in local mode. Skipping OAuth");
245 next();
246 } else {
247 middleware(req, res, next);
248 }
249 },
250
251 shutdown() {
252 // tests are needing shutdowns to remove services that keep programs running, etc.
253 }
254
255 };
256
257} // end of init
258
259// from the product name(s) on the token, find the corresponding proxy
260// then check if that proxy is one of the authorized proxies in bootstrap
261//
262module.exports.checkIfAuthorized = basicAuth.checkIfAuthorized;
263module.exports.tests = basicAuth.tests