app.js (186 lines of code) (raw):
// Copyright (c) 2017 Chandan B N. All rights reserved.
const express = require('express');
const path = require('path');
const fs = require('fs');
const mongoose = require('mongoose');
const flash = require('connect-flash');
const https = require('https');
const pug = require('pug');
// ASF
const asf = require('./custom/asf.js')
// END ASF
// TODO: don't use express-session for large-scale production use
const session = require('express-session');
const passport = require('passport');
const crypto = require('crypto');
const compress = require('compression');
const dotenv = require('dotenv').config()
if (dotenv.error) {
console.log(".env was not loaded.");
}
const conf = require('./config/conf');
const optSet = require('./models/set');
if(!process.env.NODE_ENV) {
process.env.NODE_ENV = "production";
}
mongoose.Promise = global.Promise;
mongoose.set('strictQuery', false);
mongoose.connect(conf.database, {
keepAlive: true,
}).catch(function(e){
console.log("Error"+e.message);
});
const db = mongoose.connection;
//Check connection
db.once('open', function () {
console.log('Connected to MongoDB');
});
//Check for db errors
db.on('error', function (err) {
console.error(err.message);
console.error('Check mongodb connection URL configuration. Ensure Mongodb server is running!');
});
const app = express();
var rateLimit = require('express-rate-limit');
var limiter = rateLimit({
windowMs: 1*60*1000, // 1 minute
max: 200
});
// apply rate limiter to all requests
app.use(limiter);
app.disable('x-powered-by');
// enable compression
app.use(compress());
app.set('env', 'production');
// View engine
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');
// make conf available for pug
app.locals.conf = conf;
app.locals.pugLib = pug;
// parse urlencoded forms
app.use(express.urlencoded({
extended: true
}));
// parse application/json
app.use(express.json({limit:'16mb'}));
// serve files under public freely
app.use(express.static('public'));
// Express Session middleware
app.use(session({
secret: crypto.randomBytes(64).toString('hex'),
resave: true,
saveUninitialized: true,
cookie: {
secure: process.env.NODE_ENV == "production",
httpOnly: true,
sameSite: 'lax',
}
}));
// Passport config
require('./config/passport')(passport);
app.use(passport.initialize());
app.use(passport.session());
// ASF
asf.asfinit(app);
// END ASF
// Express Messages Middleware
// This shows error messages on the client
app.use(require('connect-flash')());
app.use(function (req, res, next) {
res.locals.user = req.user || null;
// ASF
// res.locals.startTime = Date.now();
// END ASF
res.locals.messages = require('express-messages')(req, res);
next();
});
// add this to route for authenticating before certain requests.
function ensureAuthenticated(req, res, next) {
if (req.isAuthenticated()) {
return next();
} else {
req.session.returnTo = req.originalUrl;
res.redirect('/users/login')
}
}
function ensureConnected(req, res, next) {
if (mongoose.connection.readyState == 1) {
return next();
} else {
req.session.returnTo = req.originalUrl;
req.flash('error', 'Database error! Ensure mongod is up and check the settings on the server.')
res.status(500);
res.render('splash', {
title: 'Vulnogram'
});
}
}
app.use(ensureConnected);
//delete return redirect path
app.use(function (req, res, next) {
res.setHeader('X-Frame-Options', 'SAMEORIGIN');
res.setHeader('X-XSS-Protection', '1; mode=block');
// ASF
// we don't use web integrations that need CORS, so safer to disable:
//res.setHeader("Access-Control-Allow-Origin", "*");// XXX investigate
//res.setHeader("Access-Control-Request-Headers", "cve-api-cna,cve-api-secret,cve-api-submitter");
// END ASF
// Based on INFRA-25518
res.setHeader('Content-Security-Policy', "default-src 'self' data: 'unsafe-inline'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdnjs.cloudflare.com; style-src 'self' 'unsafe-inline'; connect-src 'self' https://whimsy.apache.org; frame-ancestors 'self';");
if (process.env.NODE_ENV == "production") {
// Have the browser remember to use https for a year:
res.setHeader('Strict-Transport-Security', 'max-age=31536000');
}
if (req.path != '/users/login' && req.session.returnTo) {
delete req.session.returnTo
}
next()
})
// ASF
asf.asfroutes(ensureAuthenticated, app);
// END ASF
// set up routes
let users = require('./routes/users');
app.use('/users', users.public);
app.use('/users', ensureAuthenticated, users.protected);
let docs = require('./routes/doc');
app.locals.confOpts = {};
// ASF
app.locals.docs = {};
// END ASF
var sections = require('./models/sections.js')();
for(section of sections) {
var s = optSet(section, ['default', 'custom']);
//var s = conf.sections[section];
if(s.facet && s.facet.ID) {
// ASF
if (conf.sections.includes(section)){
app.locals.confOpts[section] = s;
}
let r = docs(section, s);
app.locals.docs[section] = r;
// END ASF
app.use('/' + section, ensureAuthenticated, r.router);
}
}
app.use('/home/stats', ensureAuthenticated, async function(req, res, next){
var sections = [];
for(section of conf.sections){
var s = {};
try {
var s = await db.collection(section+'s').stats();
} catch (e){
};
if (s === {}) {
try {
var s = await db.collection(section).stats();
} catch (e){
};
};
sections.push({
name: section,
items: s.count,
size: s.size,
avgSize: s.avgObjSize
});
}
res.render('list',
{
docs: sections,
columns: ['name', 'items', 'size', 'avgSize'],
fields: {
'name': {
className: 'icn'
}
}
})
});
app.use(function (req, res, next) {
res.locals.confOpts = app.locals.confOpts;
next();
});
//Configuring a reviewToken in conf file allows sharing drafts with 'people who have a link containing the configurable token'
let review = require('./routes/review');
if (review.public) {
app.use('/review', express.static('public'));
app.use('/review', review.public);
}
app.use('/review', ensureAuthenticated, review.protected);
if(conf.customRoutes) {
for(r of conf.customRoutes) {
app.use(r.path, require(r.route));
}
}
app.get('/', function (req, res, next) {
res.redirect(conf.homepage? conf.homepage : '/home');
});
if(conf.httpsOptions) {
https.createServer(conf.httpsOptions, app).listen(conf.serverPort, conf.serverHost, function () {
console.log('Server started at https://' + conf.serverHost + ':' + conf.serverPort);
});
} else {
app.listen(conf.serverPort, conf.serverHost, function () {
console.log('Server started at http://' + conf.serverHost + ':' + conf.serverPort);
});
}