UNPKG

9.39 kBJavaScriptView Raw
1'use strict';
2/**
3 *
4 */
5
6var debug = require('debug')('plugin:extauth');
7var request = require('request');
8var rs = require('jsrsasign');
9var JWS = rs.jws.JWS;
10
11const authHeaderRegex = /Bearer (.+)/;
12const acceptAlg = ['RS256'];
13
14var acceptField = {};
15acceptField.alg = acceptAlg;
16
17module.exports.init = function(config, logger, stats) {
18
19 var publickeys = {};
20 var publickey_url = config["publickey_url"];
21 var client_id = config.hasOwnProperty("client_id") ? config.client_id : 'client_id';
22 var iss = config["iss"];
23 //set keyType to pem if the endpoint returns a single pem file
24 var keyType = config.hasOwnProperty("keyType") ? config.keyType : 'jwk';
25 //check for jwt expiry
26 var exp = config.hasOwnProperty('exp') ? config.exp : true;
27 //return error from plugin
28 var sendErr = config.hasOwnProperty("sendErr") ? config.sendErr : true;
29 //preserve or delete the auth header
30 var keepAuthHeader = config.hasOwnProperty('keep-authorization-header') ? config['keep-authorization-header'] : false;
31
32 if (iss) {
33 debug("Issuer " + iss);
34 acceptField.iss = [];
35 acceptField.iss[0] = iss;
36 }
37
38 request({
39 url: publickey_url,
40 method: 'GET'
41 }, function(err, response, body) {
42 if (err) {
43 debug('publickey gateway timeout');
44 console.log(err);
45 } else {
46 debug("loaded public keys");
47 if (keyType == 'jwk') {
48 debug("keyType is jwk");
49 publickeys = JSON.parse(body);
50 } else {
51 //the body should contain a single pem
52 publickeys = body;
53 }
54 }
55 });
56
57 function getJWK(kid) {
58 if (publickeys.keys && publickeys.keys.constructor == Array) {
59 for (var i = 0; i < publickeys.keys.length; i++) {
60 if (publickeys.keys[i].kid == kid) {
61 return publickeys.keys[i];
62 }
63 }
64 debug("no public key that matches kid found");
65 return null;
66 } else if (publickeys[kid]) { //handle cases like https://www.googleapis.com/oauth2/v1/certs
67 return publickeys[kid];
68 } else { //if the publickeys url does not return arrays, then use the only public key
69 debug("returning default public key");
70 return publickeys;
71 }
72 }
73
74 function validateJWT(pem, payload, exp) {
75 var isValid = false;
76 if (exp) {
77 debug("JWT Expiry enabled");
78 acceptField.verifyAt = rs.KJUR.jws.IntDate.getNow();
79 isValid = rs.jws.JWS.verifyJWT(payload, pem, acceptField);
80 } else {
81 debug("JWT Expiry disabled");
82 isValid = rs.jws.JWS.verify(payload, pem, acceptAlg);
83 }
84 return isValid;
85 }
86
87 return {
88 onrequest: function(req, res, next) {
89 debug('plugin onrequest');
90 var isValid = false;
91 try {
92 var jwtpayload = authHeaderRegex.exec(req.headers['authorization']);
93
94 if (!jwtpayload || jwtpayload.length < 2) {
95 debug("ERROR - JWT Token Missing in Auth header");
96 delete(req.headers['authorization']);
97 delete(req.headers['x-api-key']);
98 if (sendErr) {
99 return sendError(req, res, next, logger, stats, 'missing_authorization');
100 }
101 } else {
102 var jwtdecode = JWS.parse(jwtpayload[1]);
103 if (jwtdecode.headerObj) {
104 var kid = jwtdecode.headerObj.kid;
105 debug("Found jwt kid: " + kid);
106 if (keyType != 'jwk') {
107 debug("key type is PEM");
108 isValid = validateJWT(publickeys, jwtpayload[1], exp);
109 if (isValid) {
110 if (!keepAuthHeader) {
111 delete(req.headers['authorization']);
112 }
113 if (!sendErr) {
114 //if this plugin is not sending errors, assume MG is not in local mode
115 req.headers['x-api-key'] = jwtdecode.payloadObj[client_id];
116 }
117 } else {
118 debug("ERROR - JWT is invalid");
119 delete(req.headers['authorization']);
120 delete(req.headers['x-api-key']);
121 if (sendErr) {
122 return sendError(req, res, next, logger, stats, 'invalid_token');
123 }
124 }
125 } else if (!kid && keyType == 'jwk') {
126 debug("ERROR - JWT Missing kid in header");
127 delete(req.headers['authorization']);
128 delete(req.headers['x-api-key']);
129 if (sendErr) {
130 return sendError(req, res, next, logger, stats, 'invalid_token');
131 }
132 } else {
133 var jwk = getJWK(kid);
134 if (!jwk) {
135 debug("ERROR - Could not find public key to match kid");
136 delete(req.headers['authorization']);
137 delete(req.headers['x-api-key']);
138 if (sendErr) {
139 return sendError(req, res, next, logger, stats, 'invalid_authorization');
140 }
141 } else {
142 debug("Found JWK");
143 var publickey = rs.KEYUTIL.getKey(jwk);
144 var pem = rs.KEYUTIL.getPEM(publickey);
145 isValid = validateJWT(pem, jwtpayload[1], exp);
146 if (isValid) {
147 debug("JWT is valid");
148 if (!keepAuthHeader) {
149 delete(req.headers['authorization']);
150 }
151 if (!sendErr) {
152 //if this plugin is not sending errors, assume MG is not in local mode
153 req.headers['x-api-key'] = jwtdecode.payloadObj[client_id];
154 }
155 } else {
156 debug("ERROR - JWT is invalid");
157 delete(req.headers['authorization']);
158 delete(req.headers['x-api-key']);
159 if (sendErr) {
160 return sendError(req, res, next, logger, stats, 'access_denied');
161 }
162 }
163 }
164 }
165 } else {
166 debug("ERROR - Missing header in JWT");
167 delete(req.headers['authorization']);
168 delete(req.headers['x-api-key']);
169 if (sendErr) {
170 return sendError(req, res, next, logger, stats, 'missing_authorization');
171 }
172 }
173 }
174 } catch (err) {
175 debug("ERROR - " + err);
176 delete(req.headers['authorization']);
177 delete(req.headers['x-api-key']);
178 if (sendErr) {
179 return sendError(req, res, next, logger, stats, 'invalid_authorization');
180 }
181 }
182 next();
183 }
184 };
185}
186
187function sendError(req, res, next, logger, stats, code, message) {
188
189 switch (code) {
190 case 'invalid_request':
191 res.statusCode = 400;
192 break;
193 case 'access_denied':
194 res.statusCode = 403;
195 break;
196 case 'invalid_token':
197 case 'missing_authorization':
198 case 'invalid_authorization':
199 res.statusCode = 401;
200 break;
201 case 'gateway_timeout':
202 res.statusCode = 504;
203 break;
204 default:
205 res.statusCode = 500;
206 }
207
208 var response = {
209 error: code,
210 error_description: message
211 };
212
213 debug('auth failure', res.statusCode, code, message ? message : '', req.headers, req.method, req.url);
214 logger.error({
215 req: req,
216 res: res
217 }, 'extauth');
218
219 //opentracing
220 if (process.env.EDGEMICRO_OPENTRACE) {
221 const traceHelper = require('../microgateway-core/lib/trace-helper');
222 traceHelper.setChildErrorSpan('extauth', req.headers);
223 }
224 //
225
226 if (!res.finished) res.setHeader('content-type', 'application/json');
227 res.end(JSON.stringify(response));
228 stats.incrementStatusCount(res.statusCode);
229 next(code, message);
230 return code;
231}
\No newline at end of file