1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 |
|
30 |
|
31 | var jwt = require('jwt-simple');
|
32 | var crypto = require('crypto');
|
33 |
|
34 | var algorithm = 'aes-256-cbc';
|
35 | var key;
|
36 | var iv;
|
37 |
|
38 | function sizes(cipher) {
|
39 | for (let nkey = 1, niv = 0;;) {
|
40 | try {
|
41 | crypto.createCipheriv(cipher, '.'.repeat(nkey), '.'.repeat(niv));
|
42 | return [nkey, niv];
|
43 | } catch (e) {
|
44 | if (/invalid iv length/i.test(e.message)) niv += 1;
|
45 | else if (/invalid key length/i.test(e.message)) nkey += 1;
|
46 | else throw e;
|
47 | }
|
48 | }
|
49 | }
|
50 |
|
51 | function compute(cipher, passphrase) {
|
52 | let [nkey, niv] = sizes(cipher);
|
53 | for (let key = '', iv = '', p = '';;) {
|
54 | const h = crypto.createHash('md5');
|
55 | h.update(p, 'hex');
|
56 | h.update(passphrase);
|
57 | p = h.digest('hex');
|
58 | let n, i = 0;
|
59 | n = Math.min(p.length-i, 2*nkey);
|
60 | nkey -= n/2, key += p.slice(i, i+n), i += n;
|
61 | n = Math.min(p.length-i, 2*niv);
|
62 | niv -= n/2, iv += p.slice(i, i+n), i += n;
|
63 | if (nkey+niv === 0) return [key, iv];
|
64 | }
|
65 | }
|
66 |
|
67 | function decrypt(text, secret) {
|
68 | if (!key) {
|
69 | var results = compute(algorithm, secret);
|
70 | console.log('**** jwtHandler decrypt: key = ' + results[0]);
|
71 | console.log('**** jwtHandler decrypt: iv = ' + results[1]);
|
72 | key = Buffer.from(results[0], 'hex');
|
73 | iv = Buffer.from(results[1], 'hex');
|
74 | }
|
75 | var dec;
|
76 | try {
|
77 | var decipher = crypto.createDecipheriv(algorithm, key, iv)
|
78 | dec = decipher.update(text,'hex','utf8')
|
79 | dec += decipher.final('utf8');
|
80 | }
|
81 | catch(err) {
|
82 | dec = 'Error: ' + err;
|
83 | }
|
84 | return dec;
|
85 | }
|
86 |
|
87 | function encrypt(text, secret) {
|
88 | if (!key) {
|
89 | var results = compute(algorithm, secret);
|
90 | console.log('**** jwtHandler encrypt: key = ' + results[0]);
|
91 | console.log('**** jwtHandler encrypt: iv = ' + results[1]);
|
92 | key = Buffer.from(results[0], 'hex');
|
93 | iv = Buffer.from(results[1], 'hex');
|
94 | }
|
95 | var cipher = crypto.createCipheriv(algorithm, key, iv);
|
96 | var crypted = cipher.update(text,'utf8','hex');
|
97 | crypted += cipher.final('hex');
|
98 | return crypted;
|
99 | }
|
100 |
|
101 | function decodeJWT(token) {
|
102 | try {
|
103 | return {payload: jwt.decode(token, this.jwt.secret)};
|
104 | }
|
105 | catch(err) {
|
106 | return {error: 'Invalid JWT: ' + err};
|
107 | }
|
108 | }
|
109 |
|
110 | function encodeJWT(payload) {
|
111 | return jwt.encode(payload, this.jwt.secret);
|
112 | }
|
113 |
|
114 | function decodeJWTInWorker(jwt, callback) {
|
115 |
|
116 | var msg = {
|
117 | type: 'ewd-jwt-decode',
|
118 | params: {
|
119 | jwt: jwt
|
120 | }
|
121 | };
|
122 | this.handleMessage(msg, callback);
|
123 | }
|
124 |
|
125 | function encodeJWTInWorker(payload, callback) {
|
126 |
|
127 | var msg = {
|
128 | type: 'ewd-jwt-encode',
|
129 | params: {
|
130 | payload: payload
|
131 | }
|
132 | };
|
133 | this.handleMessage(msg, callback);
|
134 | }
|
135 |
|
136 | function sendToMicroService(socketClient, data, application, handleResponse) {
|
137 | var q = this;
|
138 |
|
139 | socketClient.client.send(data, function(responseObj) {
|
140 | if (!responseObj.message.error) {
|
141 | if (responseObj.message.token) {
|
142 |
|
143 | var payload = jwt.decode(responseObj.message.token, null, true);
|
144 | if (payload.application !== application) {
|
145 | payload.application = application;
|
146 | encodeJWTInWorker.call(q, payload, function(jwtObj) {
|
147 | responseObj.message.token = jwtObj.message.jwt;
|
148 | handleResponse(responseObj);
|
149 | });
|
150 | return;
|
151 | }
|
152 | }
|
153 | }
|
154 | handleResponse(responseObj);
|
155 | });
|
156 | }
|
157 |
|
158 | function masterRequest(data, socket, handleResponse) {
|
159 |
|
160 |
|
161 |
|
162 | var q = this;
|
163 | if (this.jwt && this.jwt.secret) {
|
164 |
|
165 |
|
166 |
|
167 | decodeJWTInWorker.call(this, data.token, function(responseObj) {
|
168 | if (responseObj.message.error) {
|
169 | socket.emit('ewdjs', {
|
170 | type: data.type,
|
171 | message: {
|
172 | error: responseObj.message.error,
|
173 | disconnect: true
|
174 | }
|
175 | });
|
176 | return;
|
177 | }
|
178 |
|
179 |
|
180 |
|
181 | data.jwt = true;
|
182 |
|
183 |
|
184 | console.log('decodeJWTInWorker - responseObj = ' + JSON.stringify(responseObj));
|
185 | var payload = responseObj.message.payload;
|
186 | var application = payload.application;
|
187 | if (q.u_services && q.u_services.byApplication[application] && q.u_services.byApplication[application][data.type]) {
|
188 |
|
189 | if (q.log) console.log('incoming request for ' + application + ': ' + data.type + ' will be handled by microservice');
|
190 |
|
191 |
|
192 |
|
193 |
|
194 |
|
195 |
|
196 |
|
197 |
|
198 | var socketClient = q.u_services.byApplication[application][data.type];
|
199 |
|
200 | if (payload.application !== socketClient.application) {
|
201 | payload.application = socketClient.application;
|
202 |
|
203 |
|
204 |
|
205 | encodeJWTInWorker.call(q, payload, function(responseObj) {
|
206 | data.token = responseObj.message.jwt;
|
207 | sendToMicroService.call(q, socketClient, data, application, handleResponse)
|
208 | });
|
209 | }
|
210 | else {
|
211 | sendToMicroService.call(q, socketClient, data, application, handleResponse)
|
212 | }
|
213 | }
|
214 | else {
|
215 |
|
216 |
|
217 |
|
218 |
|
219 | q.handleMessage(data, handleResponse);
|
220 | }
|
221 | });
|
222 | }
|
223 | else {
|
224 |
|
225 |
|
226 | socket.emit('ewdjs', {
|
227 | type: data.type,
|
228 | message: {
|
229 | error: 'QEWD has not been configured to support JWTs',
|
230 | disconnect: true
|
231 | }
|
232 | });
|
233 | return;
|
234 | }
|
235 | }
|
236 |
|
237 | function register(messageObj) {
|
238 |
|
239 | if (!this.jwt) {
|
240 |
|
241 | delete messageObj.jwt;
|
242 | return {
|
243 | error: 'Application expects to use JWTs, but QEWD is not running with JWT support turned on',
|
244 | disconnect: true
|
245 | };
|
246 | }
|
247 |
|
248 | return {token: createJWT.call(this, messageObj)};
|
249 | }
|
250 |
|
251 | function reregister(payload, messageObj) {
|
252 | payloadsocketId = messageObj.socketId;
|
253 | var token = updateJWT.call(this, payload);
|
254 | return {
|
255 | ok: true,
|
256 | token: token
|
257 | };
|
258 | }
|
259 |
|
260 | function createUServiceSession(messageObj) {
|
261 | return createRestSession.call(this, {
|
262 | req: {
|
263 | application: messageObj.application,
|
264 | ip: messageObj.ip
|
265 | }
|
266 | });
|
267 | }
|
268 |
|
269 | function createRestSession(args, timeout) {
|
270 | var now = Math.floor(Date.now()/1000);
|
271 | var timeout = timeout || this.userDefined.config.initialSessionTimeout;
|
272 |
|
273 | var payload = {
|
274 | exp: now + timeout,
|
275 | iat: now,
|
276 | iss: 'qewd.jwt',
|
277 | application: args.req.application,
|
278 | ipAddress: args.req.ip,
|
279 | timeout: timeout,
|
280 | authenticated: false,
|
281 | qewd: {
|
282 | },
|
283 | qewd_list: {
|
284 | ipAddress: true,
|
285 | authenticated: true
|
286 | }
|
287 | };
|
288 |
|
289 |
|
290 |
|
291 | payload.makeSecret = function(name) {
|
292 | payload.qewd_list[name] = true;
|
293 | }
|
294 | payload.isSecret = function(name) {
|
295 | if (payload.qewd_list[name] === true) return true;
|
296 | return false;
|
297 | }
|
298 | payload.makePublic = function(name) {
|
299 | delete payload.qewd_list[name];
|
300 | }
|
301 | return payload;
|
302 |
|
303 | }
|
304 |
|
305 | function createJWT(messageObj) {
|
306 |
|
307 | var now = Math.floor(Date.now()/1000);
|
308 | var timeout = this.userDefined.config.initialSessionTimeout;
|
309 |
|
310 | var payload = {
|
311 | exp: now + timeout,
|
312 | iat: now,
|
313 | iss: 'qewd.jwt',
|
314 | application: messageObj.application,
|
315 | timeout: timeout
|
316 | };
|
317 |
|
318 | var secretData = {
|
319 | socketId: messageObj.socketId,
|
320 | ipAddress: messageObj.ipAddress,
|
321 | authenticated: false
|
322 | };
|
323 |
|
324 | payload.qewd = encrypt(JSON.stringify(secretData), this.jwt.secret);
|
325 |
|
326 | return jwt.encode(payload, this.jwt.secret);
|
327 | }
|
328 |
|
329 | function validate(messageObj, noVerify) {
|
330 |
|
331 |
|
332 | if (noVerify === true) {
|
333 | return {
|
334 | session: jwt.decode(messageObj.token, 'dummy', true)
|
335 | }
|
336 | }
|
337 |
|
338 | try {
|
339 | var payload = jwt.decode(messageObj.token, this.jwt.secret);
|
340 | }
|
341 | catch(err) {
|
342 | return {
|
343 | error: 'Invalid JWT: ' + err,
|
344 | status: {
|
345 | code: 403,
|
346 | text: 'Forbidden'
|
347 | }
|
348 | };
|
349 | }
|
350 |
|
351 |
|
352 | var dec = decrypt(payload.qewd, this.jwt.secret);
|
353 | if (typeof dec === 'string' && dec.startsWith('Error: ')) {
|
354 | console.log("\n*** Unable to decrypt the JWT's secret payload, possibly due to ");
|
355 | console.log('an invalid or outdated cookie in the browser, eg before a change');
|
356 | console.log('of JWT secret. Delete the browser cookie');
|
357 | console.log(dec);
|
358 | console.log('-----');
|
359 | dec = {};
|
360 | }
|
361 | try {
|
362 | payload.qewd = JSON.parse(dec);
|
363 | payload.qewd_list = {};
|
364 | for (var name in payload.qewd) {
|
365 |
|
366 |
|
367 |
|
368 | if (!payload[name]) {
|
369 | payload[name] = payload.qewd[name];
|
370 | payload.qewd_list[name] = true;
|
371 | }
|
372 | }
|
373 | }
|
374 | catch(err) {
|
375 |
|
376 | }
|
377 |
|
378 |
|
379 |
|
380 | payload.makeSecret = function(name) {
|
381 | payload.qewd_list[name] = true;
|
382 | }
|
383 | payload.isSecret = function(name) {
|
384 | if (payload.qewd_list[name] === true) return true;
|
385 | return false;
|
386 | }
|
387 | payload.makePublic = function(name) {
|
388 | delete payload.qewd_list[name];
|
389 | }
|
390 |
|
391 | return {
|
392 | session: payload
|
393 | };
|
394 | }
|
395 |
|
396 | function getProperty(propertyName, token) {
|
397 | var payload;
|
398 | try {
|
399 | payload = jwt.decode(token, null, true);
|
400 | }
|
401 | catch(err) {
|
402 | return false;
|
403 | }
|
404 | if (!payload[propertyName]) return false;
|
405 | return payload[propertyName];
|
406 | }
|
407 |
|
408 | function updateJWTExpiry(token, application) {
|
409 | try {
|
410 | var payload = jwt.decode(token, null, true);
|
411 | }
|
412 | catch(err) {
|
413 | return false;
|
414 | }
|
415 |
|
416 |
|
417 |
|
418 |
|
419 |
|
420 | var now = Math.floor(Date.now()/1000);
|
421 | payload.iat = now;
|
422 | payload.exp = now + payload.timeout || 300;
|
423 | if (application && application !== '') payload.application = application;
|
424 |
|
425 | token = jwt.encode(payload, this.jwt.secret);
|
426 | return token;
|
427 | }
|
428 |
|
429 | function updateJWT(payload) {
|
430 |
|
431 | var timeout = payload.timeout;
|
432 |
|
433 |
|
434 |
|
435 | if (payload.qewd) {
|
436 | for (var name in payload.qewd_list) {
|
437 |
|
438 |
|
439 | if (typeof payload[name] !== 'undefined') {
|
440 |
|
441 | payload.qewd[name] = payload[name];
|
442 | delete payload[name];
|
443 | }
|
444 | }
|
445 |
|
446 | payload.qewd = encrypt(JSON.stringify(payload.qewd), this.jwt.secret);
|
447 |
|
448 |
|
449 |
|
450 | delete payload.qewd_list;
|
451 | delete payload.makeSecret;
|
452 | delete payload.makePublic;
|
453 | delete payload.isSecret;
|
454 | }
|
455 |
|
456 |
|
457 |
|
458 | var now = Math.floor(Date.now()/1000);
|
459 | payload.iat = now;
|
460 | payload.exp = now + timeout;
|
461 | var token = jwt.encode(payload, this.jwt.secret);
|
462 | return token;
|
463 | };
|
464 |
|
465 |
|
466 | function validateRestRequest(messageObj, finished, bearer, checkIfAuthenticated) {
|
467 | if (checkIfAuthenticated !== false) checkIfAuthenticated = true;
|
468 | var jwt = getRestJWT(messageObj, bearer);
|
469 | if (jwt === '') {
|
470 | finished({error: 'Authorization Header missing or JWT not found in header (expected format: Bearer {{JWT}}'});
|
471 | return false;
|
472 | }
|
473 |
|
474 | var status = validate.call(this, {token: jwt});
|
475 | if (status.error) {
|
476 | finished(status);
|
477 | return false;
|
478 | }
|
479 | if (checkIfAuthenticated && !status.session.authenticated) {
|
480 | finished({error: 'User is not authenticated'});
|
481 | return false;
|
482 | }
|
483 | messageObj.session = status.session;
|
484 | return true;
|
485 | }
|
486 |
|
487 | function getRestJWT(messageObj, bearer) {
|
488 | var jwt = '';
|
489 | if (messageObj.headers && messageObj.headers.authorization) {
|
490 | jwt = messageObj.headers.authorization;
|
491 | if (bearer !== false) {
|
492 | jwt = jwt.split('Bearer ')[1];
|
493 | if (typeof jwt === 'undefined') jwt = '';
|
494 | }
|
495 | }
|
496 | return jwt;
|
497 | }
|
498 |
|
499 | function isTokenAPossibleJWT(token) {
|
500 | var pieces = token.split('.');
|
501 | if (pieces.length !== 3) return false;
|
502 | if (pieces[0].length < 2) return false;
|
503 | if (pieces[1].length < 2) return false;
|
504 | if (pieces[2].length < 2) return false;
|
505 | return true;
|
506 | }
|
507 |
|
508 | function isJWTValid(token, noVerify) {
|
509 | try {
|
510 | var payload = jwt.decode(token, this.jwt.secret, noVerify);
|
511 | if (noVerify) {
|
512 | if (payload.exp && payload.exp < (Date.now()/1000)) {
|
513 | return {
|
514 | ok: false,
|
515 | error: 'Invalid JWT: Token expired'
|
516 | };
|
517 | }
|
518 | }
|
519 | return {ok: true};
|
520 | }
|
521 | catch(err) {
|
522 | return {
|
523 | ok: false,
|
524 | error: 'Invalid JWT: ' + err
|
525 | };
|
526 | }
|
527 | }
|
528 |
|
529 | module.exports = {
|
530 | masterRequest: masterRequest,
|
531 | register: register,
|
532 | reregister: reregister,
|
533 | createJWT: createJWT,
|
534 | createRestSession: createRestSession,
|
535 | updateJWT: updateJWT,
|
536 | setJWT: updateJWT,
|
537 | validate: validate,
|
538 | getRestJWT: getRestJWT,
|
539 | validateRestRequest: validateRestRequest,
|
540 | updateJWTExpiry: updateJWTExpiry,
|
541 | isJWTValid: isJWTValid,
|
542 | createUServiceSession: createUServiceSession,
|
543 | decodeJWT: decodeJWT,
|
544 | encodeJWT: encodeJWT,
|
545 | getProperty: getProperty
|
546 | };
|