config/express.js
'use strict';
/**
* Module dependencies.
*/
var fs = require('fs'),
https = require('https'),
express = require('express'),
morgan = require('morgan'),
logger = require('./logger'),
bodyParser = require('body-parser'),
session = require('express-session'),
compression = require('compression'),
methodOverride = require('method-override'),
cookieParser = require('cookie-parser'),
helmet = require('helmet'),
passport = require('passport'),
raven = require('raven'),
MongoStore = require('connect-mongo')(session),
config = require('./config'),
consolidate = require('consolidate'),
path = require('path'),
client = new raven.Client(config.DSN),
i18n = require('i18n');
var mongoose = require('mongoose');
/**
* Configure Socket.io
*/
var configureSocketIO = function (app, db) {
// Load the Socket.io configuration
var server = require('./socket.io')(app, db);
// Return server object
return server;
};
var supportedLanguages = ['en', 'de', 'fr', 'it', 'es'];
function containsAnySupportedLanguages(preferredLanguages){
for (var i = 0; i < preferredLanguages.length; i++) {
var currIndex = supportedLanguages.indexOf(preferredLanguages[i]);
if (currIndex > -1) {
return supportedLanguages[currIndex];
}
}
return null;
}
module.exports = function(db) {
// Initialize express app
var app = express();
var url = require('url');
// Globbing model files
config.getGlobbedFiles('./app/models/**/*.js').forEach(function(modelPath) {
require(path.resolve(modelPath));
});
// Setting application local variables
app.locals.google_analytics_id = config.app.google_analytics_id;
app.locals.title = config.app.title;
app.locals.signupDisabled = config.signupDisabled;
app.locals.description = config.app.description;
app.locals.keywords = config.app.keywords;
app.locals.subdomainsDisabled = config.subdomainsDisabled;
if(config.socketPortExternallyVisible){
app.locals.socketPort = config.socketPort;
} else {
app.locals.socketPort = '';
}
if(config.socketUrl){
app.locals.socketUrl = config.socketUrl;
}
app.locals.bowerJSFiles = config.getBowerJSAssets();
app.locals.bowerCssFiles = config.getBowerCSSAssets();
app.locals.bowerOtherFiles = config.getBowerOtherAssets();
app.locals.jsFiles = config.getJavaScriptAssets();
app.locals.formJSFiles = config.getFormJavaScriptAssets();
app.locals.cssFiles = config.getCSSAssets();
app.use(function (req, res, next) {
var urlPath;
if(!config.subdomainsDisabled) {
var User = mongoose.model('User');
var subdomainPath = '/subdomain/';
var subdomains = req.subdomains;
if (subdomains.slice(0, 4).join('.') + '' === '1.0.0.127') {
subdomains = subdomains.slice(4);
}
// continue if no subdomains
if (!subdomains.length) {
return next();
}
urlPath = url.parse(req.url).path.split('/');
if (urlPath.indexOf('static') > -1) {
urlPath.splice(1, 1);
req.root = req.protocol + '://' + config.baseUrl + urlPath.join('/');
return next();
}
if (urlPath.indexOf('users') > -1 && urlPath.indexOf('me') > -1) {
return next();
}
if (subdomains.indexOf('stage') > -1 || subdomains.indexOf('admin') > -1) {
return next();
}
if (subdomains.indexOf('api') > -1) {
// rebuild url
subdomainPath += 'api' + req.url;
// TODO: check path and query strings are preserved
// reassign url
req.url = subdomainPath;
return next();
}
User.findOne({username: req.subdomains.reverse()[0]}).exec(function (err, user) {
if (err) {
req.subdomains = null;
// Error page
return res.status(404).render('404', {
error: 'Page Does Not Exist'
});
}
if (user === null) {
// Error page
return res.status(404).render('404', {
error: 'Page Does Not Exist'
});
}
// rebuild url
subdomainPath += subdomains.join('/') + req.url;
// TODO: check path and query strings are preserved
// reassign url
req.url = subdomainPath;
// Q.E.D.
return next();
});
} else {
urlPath = url.parse(req.url).path.split('/');
if (urlPath.indexOf('static') > -1 && urlPath.indexOf('view') === urlPath.indexOf('static')-1) {
urlPath.splice(1, 1);
req.url = urlPath.join('/');
}
return next();
}
});
//Setup Prerender.io
app.use(require('prerender-node').set('prerenderToken', process.env.PRERENDER_TOKEN));
// Passing the request url to environment locals
app.use(function(req, res, next) {
if(config.baseUrl === ''){
config.baseUrl = req.protocol + '://' + req.headers.host;
}
res.locals.url = req.protocol + '://' + req.headers.host + req.url;
next();
});
// Should be placed before express.static
app.use(compression({
// only compress files for the following content types
filter: function(req, res) {
return (/json|text|javascript|css/).test(res.getHeader('Content-Type'));
},
// zlib option for compression level
level: 9
}));
//Setup i18n
i18n.configure({
locales: supportedLanguages,
directory: __dirname + '/locales',
defaultLocale: 'en',
cookie: 'userLang'
});
app.use(i18n.init);
app.use(function(req, res, next) {
// express helper for natively supported engines
res.locals.__ = res.__ = function() {
return i18n.__.apply(req, arguments);
};
next();
});
// Set template engine as defined in the config files
app.engine('server.view.pug', consolidate.pug);
// Set views path and view engine
app.set('view engine', 'server.view.pug');
app.set('views', './app/views');
// Enable logger (morgan)
app.use(morgan(logger.getLogFormat(), logger.getMorganOptions()));
// Environment dependent middleware
if (process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'test') {
// Disable views cache
app.set('view cache', false);
} else if (process.env.NODE_ENV === 'production') {
app.locals.cache = 'memory';
app.set('view cache', true);
}
// Request body parsing middleware should be above methodOverride
app.use(bodyParser.urlencoded({
extended: true,
limit: '100mb'
}));
app.use(bodyParser.json({ limit: '100mb' }));
app.use(methodOverride());
// Use helmet to secure Express headers
app.use(helmet.frameguard());
app.use(helmet.xssFilter());
app.use(helmet.noSniff());
app.use(helmet.ieNoOpen());
app.use(helmet.dnsPrefetchControl());
app.use(helmet.hidePoweredBy());
// Setting the app router and static folder
app.use('/static', express.static(path.resolve('./public')));
app.use('/uploads', express.static(path.resolve('./uploads')));
// CookieParser should be above session
app.use(cookieParser());
// Express MongoDB session storage
app.use(session({
saveUninitialized: true,
resave: true,
secret: config.sessionSecret,
store: new MongoStore({
mongooseConnection: db.connection,
collection: config.sessionCollection
}),
cookie: config.sessionCookie,
name: config.sessionName
}));
// use passport session
app.use(passport.initialize());
app.use(passport.session());
//Visitor Language Detection
app.use(function(req, res, next) {
var acceptLanguage = req.headers['accept-language'];
var languages, supportedLanguage;
if(acceptLanguage){
languages = acceptLanguage.match(/[a-z]{2}(?!-)/g) || [];
supportedLanguage = containsAnySupportedLanguages(languages);
}
if(!req.user && supportedLanguage !== null){
var currLanguage = res.cookie('userLang');
if(currLanguage && currLanguage !== supportedLanguage || !currLanguage){
res.clearCookie('userLang');
res.cookie('userLang', supportedLanguage, { maxAge: 90000, httpOnly: true });
}
} else if(req.user && (!req.cookies.hasOwnProperty('userLang') || req.cookies['userLang'] !== req.user.language) ){
res.cookie('userLang', req.user.language, { maxAge: 90000, httpOnly: true });
}
next();
});
// Globbing routing files
config.getGlobbedFiles('./app/routes/**/*.js').forEach(function(routePath) {
require(path.resolve(routePath))(app);
});
// Add headers for Sentry
app.use(function (req, res, next) {
// Website you wish to allow to connect
res.setHeader('Access-Control-Allow-Origin', 'https://sentry.io');
// Request methods you wish to allow
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
// Request headers you wish to allow
res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type');
// Set to true if you need the website to include cookies in the requests sent
// to the API (e.g. in case you use sessions)
res.setHeader('Access-Control-Allow-Credentials', true);
// Pass to next layer of middleware
next();
});
// Sentry (Raven) middleware
app.use(raven.middleware.express.requestHandler(config.DSN));
// Should come before any other error middleware
app.use(raven.middleware.express.errorHandler(config.DSN));
// Assume 'not found' in the error msgs is a 404. this is somewhat silly, but valid, you can do whatever you like, set properties, use instanceof etc.
app.use(function(err, req, res, next) {
// If the error object doesn't exists
if (!err) {
return next();
}
// Log it
client.captureError(err);
// Error page
res.status(500).render('500', {
__: i18n.__,
error: err.stack
});
});
// Assume 404 since no middleware responded
app.use(function(req, res) {
client.captureError(new Error('Page Not Found'));
res.status(404).render('404', {
url: req.originalUrl,
error: 'Not Found',
__: i18n.__
});
});
if (process.env.NODE_ENV === 'secure') {
// Load SSL key and certificate
var privateKey = fs.readFileSync('./config/sslcerts/key.pem', 'utf8');
var certificate = fs.readFileSync('./config/sslcerts/cert.pem', 'utf8');
// Create HTTPS Server
var httpsServer = https.createServer({
key: privateKey,
cert: certificate
}, app);
// Return HTTPS server instance
return httpsServer;
}
app = configureSocketIO(app, db);
// Return Express server instance
return app;
};