UNPKG

9.96 kBJavaScriptView Raw
1// Express JS Configuration and init
2'use strict';
3
4const cluster = require('./cluster');
5const http = require('http');
6const https = require('https');
7const express = require('express');
8const morgan = require('morgan');
9const FileStreamRotator = require('file-stream-rotator');
10const PrettyError = require('pretty-error');
11const favicon = require('serve-favicon');
12const cookieParser = require('cookie-parser');
13const bodyParser = require('body-parser');
14const compress = require('compression');
15const session = require('express-session');
16const cors = require('cors');
17const helmet = require('helmet');
18const lusca = require('lusca');
19const chalk = require('chalk');
20const fs = require('fs');
21const path = require('path');
22const extensionParser = require('mio-extensions');
23const jwt = require('jwt-simple');
24const netjet = require('netjet');
25const rateLimit = require('express-rate-limit');
26const killSniff = require('mio-killsniff');
27const winston = require('winston');
28const moduleComposer = require('./moduleComposer');
29
30// List of modules to load
31var bannedModules = [];
32function _canILoadModule(name){
33 if(!bannedModules || bannedModules.length==0 || bannedModules.indexOf(name)==-1){
34 return true;
35 }
36
37 return false;
38}
39
40// Create Web Server
41function create(){
42 cluster.printOnce(()=>{
43 winston.info('-'.repeat(70));
44 winston.info('Init WebServer on', chalk.blue(global.mio.config.http.port), 'port');
45 });
46
47 // Init HTTP server
48 var app = express();
49 global.mio.app = app;
50 app.set('port', global.mio.config.http.port);
51 var server;
52
53 // Check if it is ssl server and use it if it is
54 if(global.mio.config.http.ssl){
55 var privateKeyFile = path.join(__dirname, '../', global.mio.config.http.ssl.privateKey);
56 var certificateFile = path.join(__dirname, '../', global.mio.config.http.ssl.certificate);
57
58 var privateKey = fs.readFileSync( privateKeyFile );
59 var certificate = fs.readFileSync( certificateFile );
60
61 server = https.createServer({
62 key: privateKey,
63 cert: certificate
64 }, app);
65 }else{
66 server = http.createServer(app);
67 }
68 server.listen(global.mio.config.http.port);
69
70 // Handle Errors
71 server.on('error', onError);
72 function onError(error) {
73 if (error.syscall !== 'listen') {
74 throw error;
75 }
76
77 var bind = typeof port === 'string'
78 ? 'Pipe ' + global.mio.config.http.port
79 : 'Port ' + global.mio.config.http.port;
80
81 // handle specific listen errors with friendly messages
82 switch (error.code) {
83 case 'EACCES':
84 winston.error(bind + ' requires elevated privileges');
85 process.exit(1);
86 break;
87 case 'EADDRINUSE':
88 winston.error(bind + ' is already in use');
89 process.exit(1);
90 break;
91 default:
92 throw error;
93 }
94 }
95
96 // Create Empty list of HTTP modules
97 app.locals.http = {modules: []};
98
99 return app;
100}
101
102// Load template engine
103function loadTemplateEngine(app){
104 app.set('view engine', 'vash');
105 app.set('views', global.mio.appPath);
106
107 app.locals.http.modules.push('vash');
108}
109
110// Load body and coockie parsers
111function loadParsersModules(app){
112 // Configuring HTTP Body Parser
113 if(_canILoadModule('bodyParser')){
114 app.use(bodyParser.json({ limit: (global.mio.config.http.maxBodySize || '5mb') }));
115 app.use(bodyParser.urlencoded({ extended: false }));
116 app.locals.http.modules.push('bodyParser('+(global.mio.config.http.maxBodySize || '5mb')+')');
117 }
118
119 // Configuring HTTP Compressor
120 if(_canILoadModule('compress')){
121 app.use(compress());
122 app.locals.http.modules.push('compress');
123 }
124
125 // Extract requested type
126 if(_canILoadModule('extensionParser')){
127 app.use(extensionParser.extract);
128 app.use((req, res, next)=>{
129 req._ext = extensionParser.render;
130 return next();
131 });
132 app.locals.http.modules.push('extensionParser');
133 }
134}
135
136// Load sessions and cookies moddules
137function loadStorageModules(app){
138 // Configuring HTTP Cookie Parser
139 if(_canILoadModule('cookieParser')){
140 app.use(cookieParser());
141 app.locals.http.modules.push('cookieParser');
142 }
143
144 // Configuring sessions
145 if(_canILoadModule('sessions')){
146 app.use(session({
147 secret: global.mio.config.solt.sessions,
148 resave: true,
149 saveUninitialized: true,
150 cookie: { secure: true }
151 }));
152 app.locals.http.modules.push('sessions');
153 }
154}
155
156// Load security modules
157function loadSecurityModules(app){
158 // Secure User-Agent
159 if(_canILoadModule('killSniff')){
160 app.use(killSniff({domains: global.mio.config.domains}));
161 app.locals.http.modules.push('killSniff');
162 }
163
164 // Trust balancer/first proxy
165 if(_canILoadModule('trustProxy')){
166 app.set('trust proxy', 1);
167 app.locals.http.modules.push('trustProxy(1)');
168 }
169
170 // Configuring rate limit
171 if(_canILoadModule('limiter')){
172 var limiter = new rateLimit({
173 windowMs: 15*60*1000, // 15 minutes
174 max: 300, // limit each IP to 100 requests per windowMs
175 delayMs: 0 // disable delaying - full speed until the max limit is reached
176 });
177 app.use(limiter);
178 app.locals.http.modules.push('limiter');
179 }
180
181 // Configuring HTTP CORSa/Allow CORS request
182 if(_canILoadModule('cors')){
183 app.use(cors());
184 app.locals.http.modules.push('cors');
185 }
186
187 // Configuring HTTP Helmet Security
188 if(_canILoadModule('helmet')){
189 app.use(helmet());
190 app.locals.http.modules.push('helmet');
191 }
192
193 // Configuring LUSCA security
194 if(_canILoadModule('lusca')){
195 app.use(lusca({
196 csrf: true,
197 xframe: 'SAMEORIGIN',
198 p3p: 'ABCDEF',
199 hsts: {maxAge: 31536000, includeSubDomains: true, preload: true},
200 xssProtection: true,
201 nosniff: true
202 }));
203 app.locals.http.modules.push('lusca');
204 }
205
206 // Configuring JWT
207 if(_canILoadModule('JWT')){
208 app.use((req, res, next)=>{
209 if(req.cookies.token){
210 req.user = jwt.decode(req.cookies.token, global.mio.config.solt.jwt);
211
212 if(req.user.expire && req.user.expire < Date.now()){
213 return res.end('Access token has expired', 400);
214 }
215 }
216
217 return next();
218 });
219
220 app.locals.http.modules.push('JWT');
221 }
222}
223
224// Use log file
225function loadDebugModules(app){
226 // Configuring HTTP Morgan Logger
227 if(_canILoadModule('morgan')){
228 if(global.mio.config.http.logFile){
229 var logFile;
230 if (fs.existsSync('/var/log/'+global.name)) {
231 logFile = path.join('/var/log/', global.name, global.mio.config.http.logFile+'-%DATE%.log');
232 }else{
233 logFile = path.join(global.mio.appPath, '/log/', global.mio.config.http.logFile+'-%DATE%.log');
234 }
235
236 // create a rotating write stream
237 var accessLogStream = FileStreamRotator.getStream({
238 date_format: 'YYYYMMDD',
239 filename: logFile,
240 frequency: 'daily',
241 verbose: false
242 });
243
244 app.use(morgan('combined', {stream: accessLogStream}));
245 app.locals.http.modules.push('morgan('+global.mio.config.http.logFile+')');
246 }else{
247 app.use(morgan('dev'));
248 app.locals.http.modules.push('morgan');
249 }
250 }
251
252 // Showing stack errors
253 if(_canILoadModule('showStackError')){
254 app.set('showStackError', true);
255 app.locals.http.modules.push('showStackError');
256 }
257
258 // Add status page module
259 if(_canILoadModule('status-page')){
260 app.use(require('express-status-monitor')({title: global.name+' Status'}));
261 app.locals.http.modules.push('status-page');
262 }
263}
264
265// Load HTTP steroids
266function loadBoosters(app){
267 // Add preload headers in html links using netjet
268 if(_canILoadModule('netjet')){
269 app.use(netjet());
270 app.locals.http.modules.push('netjet');
271 }
272}
273
274// Show Loaded HTTP modules
275function loadedModules(app){
276 // HTTP Loaded Modules Message
277 cluster.printOnce(()=>{
278 winston.info('HTTP Modules:', chalk.green(app.locals.http.modules.join(', ')));
279 });
280}
281
282// Load static routes
283function loadStatic(app){
284 var staticFilesPath = path.join(global.mio.appPath, global.mio.config.http.assets.static);
285
286 // Add FavIcon
287 if(_canILoadModule('favicon')){
288 app.use(favicon(staticFilesPath + global.mio.config.http.assets.favicon));
289 }
290
291 // Add static files path
292 if(_canILoadModule('static')){
293 app.use('/', express.static(staticFilesPath));
294 }
295
296 cluster.printOnce(()=>{
297 winston.info('HTTP Static: '+ chalk.blue(staticFilesPath));
298 });
299}
300
301// Load Routes
302function loadRoutes(app){
303 // HTTP Routes
304 var modulesNames = [];
305
306 // Load routes
307 for (var mod in moduleComposer.modules) {
308 if(_canILoadModule(mod)){
309 if (moduleComposer.modules.hasOwnProperty(mod)) {
310 if(moduleComposer.modules[mod].routes){
311 app.use(moduleComposer.modules[mod].routes);
312 modulesNames.push(mod);
313 }
314 }
315 }
316 }
317
318 cluster.printOnce(()=>{
319 winston.info('Loaded routes for modules:', chalk.green(modulesNames.join(', ')));
320 });
321}
322
323// Load error handlers
324function loadErrorHandlers(app){
325 // Catch 404 error
326 app.use((req, res, next)=>{
327 var err = new Error('Not Found');
328 err.status = 404;
329 next(err);
330 });
331
332 // Configure error logger
333 var pe = new PrettyError();
334 pe.skipNodeFiles();
335 pe.skipPackage('express');
336
337 // Error Path
338 app.use(function (err, req, res, next) {
339 // If the error object doesn't exists
340 if (!err) return next();
341
342 // Log it
343 winston.info(pe.render(err));
344
345 // Pass with error code
346 if(err.status){
347 global.mio.error(req, res, err.status);
348 }else{
349 global.mio.error(req, res, 404);
350 }
351 });
352}
353
354// Magic Init
355function init(){
356 const app = create();
357
358 // Generate list of modules to load
359 bannedModules = global.mio.config.http.bannedModules || [];
360
361 // Load HTTP Modules
362 loadTemplateEngine(app);
363 loadParsersModules(app);
364 loadStorageModules(app);
365 loadSecurityModules(app);
366 loadDebugModules(app);
367 loadBoosters(app);
368 loadedModules(app);
369
370 // Load HTTP Routes
371 loadStatic(app);
372 loadRoutes(app, config);
373 loadErrorHandlers(app);
374
375 return app;
376}
377
378module.exports = init();