1 | 'use strict';
|
2 |
|
3 |
|
4 |
|
5 |
|
6 | var debug = require('debug')('plugin:extauth');
|
7 | var request = require('request');
|
8 | var rs = require('jsrsasign');
|
9 | var JWS = rs.jws.JWS;
|
10 |
|
11 | const authHeaderRegex = /Bearer (.+)/;
|
12 | const acceptAlg = ['RS256'];
|
13 |
|
14 | var acceptField = {};
|
15 | acceptField.alg = acceptAlg;
|
16 |
|
17 | module.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 |
|
24 | var keyType = config.hasOwnProperty("keyType") ? config.keyType : 'jwk';
|
25 |
|
26 | var exp = config.hasOwnProperty('exp') ? config.exp : true;
|
27 |
|
28 | var sendErr = config.hasOwnProperty("sendErr") ? config.sendErr : true;
|
29 |
|
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 |
|
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]) {
|
67 | return publickeys[kid];
|
68 | } else {
|
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 |
|
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 |
|
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 |
|
187 | function 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 |
|
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 |