UNPKG

20.8 kBJavaScriptView Raw
1/*!
2
3 ----------------------------------------------------------------------------
4 | qewd: Quick and Easy Web Development |
5 | |
6 | Copyright (c) 2017-19 M/Gateway Developments Ltd, |
7 | Redhill, Surrey UK. |
8 | All rights reserved. |
9 | |
10 | http://www.mgateway.com |
11 | Email: rtweed@mgateway.com |
12 | |
13 | |
14 | Licensed under the Apache License, Version 2.0 (the "License"); |
15 | you may not use this file except in compliance with the License. |
16 | You may obtain a copy of the License at |
17 | |
18 | http://www.apache.org/licenses/LICENSE-2.0 |
19 | |
20 | Unless required by applicable law or agreed to in writing, software |
21 | distributed under the License is distributed on an "AS IS" BASIS, |
22 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
23 | See the License for the specific language governing permissions and |
24 | limitations under the License. |
25 ----------------------------------------------------------------------------
26
27 4 June 2019
28
29*/
30
31var getFragment = require('./getFragment');
32var resilientMode = require('./resilientMode');
33var handleJWT = require('./jwtHandler');
34var dumpDocuments = require('./dumpDocuments');
35var saveDocuments = require('./saveDocuments');
36
37function loadModule(application, finished) {
38 try {
39 var moduleName = application;
40 if (this.userDefined.config.moduleMap && this.userDefined.config.moduleMap[application]) {
41 moduleName = this.userDefined.config.moduleMap[application];
42 }
43 var appModule = require(moduleName);
44 if (!appModule.handlers) appModule.handlers = {};
45 if (appModule.handlers) this.handlers[application] = appModule.handlers;
46 if (appModule.beforeHandler) {
47 this.beforeHandlers[application] = appModule.beforeHandler;
48 }
49 if (appModule.beforeMicroServiceHandler) {
50 this.beforeHandlers[application] = function(messageObj, session, send, finished) {
51 return appModule.beforeMicroServiceHandler.call(this, messageObj, finished);
52 };
53 }
54 if (appModule.afterHandler) {
55 this.afterHandlers[application] = appModule.afterHandler;
56 }
57 if (appModule.servicesAllowed) {
58 this.servicesAllowed[application] = appModule.servicesAllowed;
59 }
60 // provide an initialisation point to load stuff like documentStore event handlers for indexing
61 if (appModule.init && typeof appModule.init === 'function') {
62 appModule.init.call(this, application);
63 }
64 console.log('loadModule: handler module loaded for application ' + application + '; module: ' + moduleName);
65 return true;
66 }
67 catch(err) {
68 var error = 'Unable to load handler module for: ' + application;
69 if (this.errorMessages && this.errorMessages[application] && this.errorMessages[application].moduleLoadError) error = this.errorMessages[application].moduleLoadError;
70 console.log(error + ': ' + err);
71 error = {
72 error: error,
73 reason: err
74 };
75 if (finished) {
76 finished(error);
77 return false;
78 }
79 return error;
80 }
81}
82
83function customError(errorObj) {
84 var error;
85 if (typeof errorObj === 'string') {
86 error = {error: errorObj};
87 }
88 else {
89 error = {
90 error: errorObj.text,
91 status: {
92 code: errorObj.statusCode
93 }
94 };
95 }
96 return error;
97}
98
99module.exports = function() {
100
101 this.on('message', function(messageObj, sendFn, finishedFn) {
102
103 //console.log('** appHandler - messageObj: ' + JSON.stringify(messageObj, null, 2));
104
105 var finished = finishedFn;
106 var q = this;
107
108 if (this.userDefined.config.resilientMode && messageObj.dbIndex) {
109 // create special version of finished function to log worker processing progress
110 finished = function(responseObj) {
111 if (responseObj.error) {
112 resilientMode.storeWorkerStatusUpdate.call(q, messageObj, 'error');
113 }
114 else {
115 resilientMode.storeWorkerStatusUpdate.call(q, messageObj, 'finished');
116 }
117 finishedFn(responseObj);
118 };
119 resilientMode.storeWorkerStatusUpdate.call(q, messageObj, 'started');
120 }
121
122 var error;
123 var errorObj;
124 if (!this.documentStore) {
125 error = {error: 'No Document Store has been created - you must use ewd-document-store!'};
126 if (this.errorMessages && this.errorMessages[application] && this.errorMessages[application].noDocumentStore)
127 errorObj = customError(this.errorMessages[application].noDocumentStore);
128 if (errorObj) error = errorObj;
129 finished(error);
130 return;
131 }
132
133 var session;
134 var application;
135 var status;
136 var ok;
137 var fin;
138 var type = messageObj.type;
139 var send = sendFn;
140
141 if (type === 'ewd-dump-documents') {
142 // primarily for use in Dockerised instances, to dump out documents
143 if (messageObj.params && messageObj.params.password && messageObj.params.password === this.userDefined.config.managementPassword) {
144 dumpDocuments.call(this, messageObj);
145 return finished({ok: true});
146 }
147 else {
148 return finished({error: 'Unauthorised attempt to use ' + type + ' message'});
149 }
150 }
151
152 if (type === 'ewd-save-documents') {
153 // primarily for use in Dockerised instances, to create / initialise documents
154 if (messageObj.params && messageObj.params.documents && messageObj.params.password && messageObj.params.password === this.userDefined.config.managementPassword) {
155 saveDocuments.call(this, messageObj.params.documents);
156 return finished({ok: true});
157 }
158 else {
159 return finished({error: 'Unauthorised attempt to use ' + type + ' message'});
160 }
161 }
162
163 if (type === 'ewd-jwt-decode') {
164 // used by QEWD sockets.js module to decode JWT
165 var payload = handleJWT.decodeJWT.call(this, messageObj.params.jwt);
166 return finished(payload);
167 }
168
169 if (type === 'ewd-jwt-encode') {
170 // used by QEWD sockets.js module to encode JWT
171 var jwt = handleJWT.encodeJWT.call(this, messageObj.params.payload);
172 return finished({jwt: jwt});
173 }
174
175 if (type === 'ewd-jwt-updateExpiry') {
176 // used by ewd-qoper8-express MicroService Router to update JWT
177
178 var token = handleJWT.updateJWTExpiry.call(this, messageObj.params.jwt, messageObj.params.application);
179 return finished({jwt: token});
180 }
181
182 if (type === 'ewd-jwt-isValid') {
183 // used by ewd-qoper8-express MicroService Router to validate JWT
184
185 status = handleJWT.isJWTValid.call(this, messageObj.params.jwt);
186 return finished(status);
187 }
188
189 if (type === 'ewd-qoper8-express') {
190 //if (messageObj.body && messageObj.body.type) {
191 if (messageObj.headers && messageObj.headers.qewd && messageObj.headers.qewd === 'ajax') {
192 // this must be an ewd-xpress message sent over Ajax
193 var ip = messageObj.ip;
194 var ips = messageObj.ips;
195 messageObj = messageObj.body;
196 type = messageObj.type;
197 if (type === 'ewd-register') {
198 messageObj.ipAddress = ip;
199 }
200 messageObj.ip = ip;
201 messageObj.ips = ips;
202 // can't use the send() function over Ajax so disable it to prevent a server-side crash
203 send = function(msg) {
204 console.log('** Unable to use send() function over Ajax for intermediate message ' + JSON.stringify(msg));
205 };
206 }
207 else if (messageObj.expressType) {
208 type = messageObj.expressType;
209 }
210 if (messageObj.application) {
211 application = messageObj.application;
212 if (!this.restModule) this.restModule = {};
213 if (!this.handlers[application]) {
214 try {
215 var moduleName = application;
216 if (this.userDefined.config.moduleMap && this.userDefined.config.moduleMap[application]) {
217 moduleName = this.userDefined.config.moduleMap[application];
218 }
219 var appModule = require(moduleName);
220 if (!appModule.handlers) appModule.handlers = {};
221 if (appModule.handlers) this.handlers[application] = appModule.handlers;
222
223 if (appModule.beforeHandler) {
224 this.beforeHandlers[application] = appModule.beforeHandler;
225 }
226 if (appModule.afterHandler) {
227 this.afterHandlers[application] = appModule.afterHandler;
228 }
229 if (appModule.servicesAllowed) this.servicesAllowed[application] = appModule.servicesAllowed;
230 if (appModule.restModule === true) this.restModule[application] = true;
231 if (appModule.init && typeof appModule.init === 'function') appModule.init.call(this, application);
232 console.log('handler module loaded for ' + application);
233 }
234 catch(err) {
235 error = 'Unable to load handler module for: ' + application;
236 if (this.errorMessages && this.errorMessages[application] && this.errorMessages[application].moduleLoadError) error = this.errorMessages[application].moduleLoadError;
237 console.log(error + ': ' + err);
238 finished({
239 error: error,
240 reason: err
241 });
242 return;
243 }
244 }
245 // If this is defined as a rest application, invoke its type handler now
246 if (this.restModule[application]) {
247 if (this.handlers[application][type]) {
248 // invoke the handler for this message
249 fin = function(results) {
250 results.restMessage = true;
251 //results.type = type;
252 results.ewd_application = application;
253 finished(results);
254 };
255
256 if (this.jwt && typeof this.jwt.handlers !== 'function') {
257 this.jwt.handlers = handleJWT;
258 }
259
260 if (this.beforeHandlers[application]) {
261 status = this.beforeHandlers[application].call(this, messageObj, fin);
262 if (status === false) return;
263 }
264 this.handlers[application][type].call(this, messageObj, fin);
265 if (this.afterHandlers[application]) {
266 this.afterHandlers[application].call(this, messageObj, fin);
267 }
268 }
269 else {
270 error = {error: 'No handler defined for ' + application + ' messages of type ' + type};
271 if (this.errorMessages && this.errorMessages[application] && this.errorMessages[application].noTypeHandler) {
272 errorObj = customError(this.errorMessages[application].noTypeHandler);
273 if (errorObj) error = errorObj;
274 }
275 finished(error);
276 }
277 return;
278 }
279 }
280 console.log('ewd-qoper8-express message remapped to: type ' + type + ': ' + JSON.stringify(messageObj));
281 }
282
283 if (type === 'ewd-register') {
284 // register a new application user
285 if (messageObj.jwt) {
286 return finished(handleJWT.register.call(this, messageObj));
287 }
288
289 var params = {
290 application: messageObj.application,
291 timeout: this.userDefined.config.initialSessionTimeout
292 };
293 session = this.sessions.create(params);
294 if (messageObj.socketId) session.socketId = messageObj.socketId;
295 if (messageObj.ipAddress) session.ipAddress = messageObj.ipAddress;
296 finished({token: session.token});
297 return;
298 }
299
300 var result;
301 if (messageObj.jwt) {
302 if (messageObj.path && messageObj.path.startsWith('/qewd/importRoutes/')) {
303 // ignore secret to allow transfer of secret from orchestrator
304 result = handleJWT.validate.call(this, messageObj, true);
305 }
306 else {
307 result = handleJWT.validate.call(this, messageObj);
308 }
309 }
310 else {
311 result = this.sessions.authenticate(messageObj.token, 'noCheck');
312 }
313 if (result.error) {
314 error = result.error;
315 if (this.errorMessages && this.errorMessages[application] && this.errorMessages[application].sessionNotAuthenticated) error = this.errorMessages[application].sessionNotAuthenticated;
316 var errorResponse = {
317 error: error,
318 disconnect: true
319 };
320 if (messageObj.ms_requestId) {
321 errorResponse.ms_requestId = messageObj.ms_requestId;
322 delete errorResponse.disconnect;
323 }
324 finished(errorResponse);
325 return;
326 }
327 session = result.session;
328 if (messageObj.jwt && !session) {
329 session = {};
330 }
331
332 if (type === 'ewd-reregister') {
333 // update the socketId in the session to the new one
334 console.log('re-register token ' + messageObj.token);
335 if (messageObj.jwt) {
336 result = handleJWT.reregister.call(this, session, messageObj);
337 }
338 else {
339 session.socketId = messageObj.socketId;
340 result = {ok: true};
341 }
342 finished(result);
343 return;
344 }
345
346 if (!messageObj.jwt) session.updateExpiry();
347
348 // use application from JWT unless this is a MicroService mapped request
349
350 application = session.application;
351
352 if (messageObj.qewd_destination && messageObj.qewd_application) {
353 //if (this.permit_application_switch && this.permit_application_switch[messageObj.qewd_application]) {
354 if (process.env.mode === 'microservice') {
355 application = messageObj.qewd_application;
356 }
357 }
358
359 if (type === 'ewd-fragment') {
360 if (messageObj.service && !this.servicesAllowed[application]) {
361 ok = loadModule.call(this, application, finished);
362 if (!ok) return;
363 }
364 //var responseObj = getFragment.call(this, messageObj, application, finished);
365 getFragment.call(this, messageObj, application, finished);
366 return;
367 }
368
369 // if no handlers have yet been loaded for the incoming application request
370 // load them now...
371
372 if (!this.handlers[application]) {
373 ok = loadModule.call(this, application);
374 if (ok.error) {
375 if (messageObj.ms_requestId) ok.ms_requestId = messageObj.ms_requestId;
376 finished(ok);
377 return;
378 }
379 if (!ok) return;
380 }
381
382 // session locking - has to be specifically switched on!
383 if (this.userDefined.config.lockSession) {
384 var timeout = this.userDefined.config.lockSession.timeout || 30;
385 this.sessionLocked = {
386 global: this.userDefined.config.sessionDocumentName,
387 subscripts: ["session", session.id]
388 };
389 console.log('*** session locked: ' + JSON.stringify(this.sessionLocked));
390 ok = this.db.lock(this.sessionLocked, timeout);
391 if (ok.result.toString() === '0') {
392 finished({error: 'Timed out waiting for EWD session to be released'});
393 return;
394 }
395 }
396
397 // is this a service request, and if so, is it allowed for this application?
398
399 //var appHandlers = this.handlers[application];
400 var servicesAllowed = this.servicesAllowed[application];
401 var service = messageObj.service;
402
403 if (service) {
404 // this is a request for a service handler
405 // first, check if the service is permitted for the user's application, and if so,
406 // make sure the service handlers are loaded
407
408 var allowService = false;
409 var sessionAllowService;
410 if (servicesAllowed && servicesAllowed[service]) allowService = true;
411 // can be over-ridden by session-specific service allowance
412 if (session && session.allowedServices && typeof session.allowedServices[service] !== 'undefined') {
413 allowService = session.allowedServices[service];
414 sessionAllowService = allowService;
415 }
416
417 if (allowService) {
418 if (!this.handlers[service]) {
419 try {
420 //this.handlers[service] = require(service).handlers;
421 ok = loadModule.call(this, service, finished);
422 if (!ok) return;
423 //console.log('service module loaded for ' + service);
424 }
425 catch(err) {
426 error = 'Unable to load handler module: ' + service;
427 if (this.errorMessages && this.errorMessages[application] && this.errorMessages[application].serviceModuleLoad) error = this.errorMessages[application].serviceModuleLoad;
428 console.log(error + ': ' + err);
429 finished({
430 error: error,
431 reason: err,
432 service: service
433 });
434 return;
435 }
436 }
437 if (this.handlers[service][type]) {
438 // invoke the handler for this message
439 if (this.beforeHandlers[service]) {
440 status = this.beforeHandlers[service].call(this, messageObj, session, send, finished);
441 if (status === false) return;
442 }
443 fin = function(responseObj) {
444 responseObj.ewd_application = service;
445 finished(responseObj);
446 };
447 this.handlers[service][type].call(this, messageObj, session, send, fin);
448 if (this.afterHandlers[service]) {
449 this.afterHandlers[service].call(this, messageObj, session, send, finished);
450 }
451 return;
452 }
453 else {
454 error = 'No handler defined for ' + service + ' service messages of type ' + type;
455 if (this.errorMessages && this.errorMessages[application] && this.errorMessages[application].noServiceModuleType) error = this.errorMessages[application].noServiceModuleType;
456 finished({
457 error: error,
458 service: service
459 });
460 return;
461 }
462 }
463 else {
464 error = service + ' service is not permitted for the ' + application + ' application';
465 if (sessionAllowService === false) error = 'You are not allowed access to the ' + service + ' service';
466 if (this.errorMessages && this.errorMessages[application]) {
467 if (this.errorMessages[application].serviceNotAllowed) error = this.errorMessages[application].serviceNotAllowed;
468 if (sessionAllowService === false && this.errorMessages[application].serviceNotAllowedForUser) error = this.errorMessages[application].serviceNotAllowedForUser;
469 }
470 finished({
471 error: error,
472 service: service
473 });
474 return;
475 }
476 }
477
478 // handlers are available for this user's application
479 // try to invoked the appropriate one for the incoming request
480
481 if (this.jwt && typeof this.jwt.handlers !== 'function') {
482 this.jwt.handlers = handleJWT;
483 }
484
485 //console.log('application = ' + application + '; type = ' + type);
486
487 // invoke the handler for this message
488 fin = function(results) {
489 //console.log('*** fin: results = ' + JSON.stringify(results));
490 //console.log('*** fin: session = ' + JSON.stringify(session));
491 results = results || {};
492 // MicroService array responses must be re-case in an object response
493 if (messageObj.ms_requestId && Array.isArray(results)) results = {results: results};
494 results.ewd_application = application;
495 if (messageObj.jwt && !results.error) {
496 // update the JWT as part of the response
497 results.token = handleJWT.updateJWT.call(q, session);
498 }
499 // MicroService responses must include the request Id to allow response handler linkage
500 if (messageObj.ms_requestId) results.ms_requestId = messageObj.ms_requestId;
501 finished(results);
502 };
503 if (this.beforeHandlers[application]) {
504 status = this.beforeHandlers[application].call(this, messageObj, session, send, fin);
505 if (status === false) return;
506 }
507
508 if (this.handlers[application][type]) {
509 this.handlers[application][type].call(this, messageObj, session, send, fin);
510 if (this.afterHandlers[application]) {
511 this.afterHandlers[application].call(this, messageObj, session, send, fin);
512 }
513 }
514 else {
515 error = {error: 'Handler not defined for ' + application + ' messages of type ' + type};
516 if (this.errorMessages && this.errorMessages[application] && this.errorMessages[application].noTypeHandler) {
517 errorObj = customError(this.errorMessages[application].noTypeHandler);
518 if (errorObj) error = errorObj;
519 }
520 finished(error);
521 }
522
523 });
524
525};