Skip to content

Instantly share code, notes, and snippets.

@gnosisbit
Last active February 18, 2018 22:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gnosisbit/65fddc7c88d69ea6335c177d2c342a6f to your computer and use it in GitHub Desktop.
Save gnosisbit/65fddc7c88d69ea6335c177d2c342a6f to your computer and use it in GitHub Desktop.
basic - node.js/expressjs - web server security
var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var session = require('express-session');
var MySQLStore = require('express-mysql-session')(session);
var helmet = require('helmet');
var lusca = require('lusca');
var logger =require('morgan');
//var csrf = require('csurf');
var config = require('./config');
var expressValidator = require('express-validator');
var RateLimit = require('express-rate-limit');
var app = express();
app.use(cookieParser(config.secretkey));
//var csrfProtection = csrf({ cookie: true });//could also be csrf();
//validator
app.use(expressValidator());
/**
* If there is a strange error with the content , try helmet() that will set deny to allow sameorigin
* Set your X-Frame-Options set to DENY, SAMEORIGIN, or ALLOW-FROM
* */
app.use(helmet(config.frameguardaction));
app.use(helmet.xssFilter());
app.use(helmet.noSniff());
app.use(helmet.ieNoOpen());
//app.use(helmet.noCache());
// Sets "Strict-Transport-Security: max-age=5184000; includeSubDomains".
var sixtyDaysInSeconds = 5184000
app.use(helmet.hsts({
maxAge: sixtyDaysInSeconds
}));
var ninetyDaysInSeconds = 7776000
app.use(helmet.hpkp({
maxAge: ninetyDaysInSeconds,
sha256s: ['AbCdEf123=', 'ZyXwVu456=']// REMEMBER TO CHASNGED THAT
}));
// Sets "X-DNS-Prefetch-Control: off".
app.use(helmet.dnsPrefetchControl());
app.use(helmet.contentSecurityPolicy(config.csp));
app.use(helmet.referrerPolicy({ policy: 'same-origin' }));
// Sets "Referrer-Policy: no-referrer".
// app.use(helmet.referrerPolicy())
// OR
//app.use(referrerPolicy({ policy: 'no-referrer' }));
//check the option to switchto lusca csrf in case of condiotional use
//app.use(csrfProtection);
/**
* inside the template form (for example)
* <form action="/users" method="POST">
* <input type="text" name="email">
* <button type="submit">Submit</button>
* </form>
*
* add
* <input type="hidden" name="_csrf" value="<%=_csrf%>">
*
* So it looks like this:
* <form action="/users" method="POST">
* <input type="hidden" name="_csrf" value="<%=_csrf%>">
* <input type="text" name="email">
* <button type="submit">Submit</button>
* </form>
*
* Also XHR requests need to include the CSRF token. Here is an example of how you could do that.
*
* var csrf_token = '<%= token_value %>';
* $("body").bind("ajaxSend", function(elm, xhr, s){
* if (s.type == "POST") {
* xhr.setRequestHeader('X-CSRF-Token', csrf_token);
* }
* ));
*/
//auto append _csrf tokento every page with tag name _csrf
//middlewaqre function
/*app.use(function(req, res, next) {
res.locals._csrf = req.csrfToken();
next();
});*/
// error handler
/*
app.use(function (err, req, res, next) {
if (err.code !== 'EBADCSRFTOKEN') return next(err)
// handle CSRF token errors here
res.status(403)
res.send('form tampered with')
})
*/
// only if you're behind a reverse proxy (Heroku, Bluemix, AWS if you use an ELB, custom Nginx setup, etc)
//user below
app.enable('trust proxy');
//app.set('trust proxy', 1); // trust first proxy// given you are behind nginx so first proxy , is actually itself.
app.set('trust proxy', function (ip) {
if (ip === '127.0.0.1')return true;// || ip === 'your.real.ip') return true; // trusted IPs
else return false;
});
/**
* Protect against DDOS attacks and brute force
*/
var limiter = new RateLimit(config.limiter);
// apply to all requests
app.use(limiter);
var mysqlstoreoptions = config.mysqlstoreoptions;
var sessionStore = new MySQLStore(mysqlstoreoptions);
//tag cookies http-only
app.use(session({
key: config.sessionkey,
secret: config.secretkey,
store: sessionStore,
resave: true,
saveUninitialized:true,
cookie: config.cookie
}));
//app.use(lusca.p3p('ABCDEF'));
app.use(lusca.xssProtection(true));
//app.use(lusca.csrf());
//if you wish to add lusca.csrf support check here :
//https://stackoverflow.com/questions/27207741/hackathon-starter-using-lusca-where-is-csrf-value-being-calculated-stored
//https://groups.google.com/forum/#!topic/nodejs/Zwnw4wOAtxw
//and https://stackoverflow.com/questions/30124037/error-csrf-token-missing-hackathon-starter-plus-angularjs
/**
Debugging
express-mysql-session uses the debug module to output debug messages to the console. To output all debug messages, run your node app with the DEBUG environment variable:
DEBUG=express-mysql-session* node your-app.js
This will output log messages as well as error messages from express-mysql-sess.
*/
/**///USAGE:
/**
// Access the session as req.session
app.get('/', function(req, res, next) {
var sessData = req.session;
sessData.someAttribute = "foo";
res.send('Returning with some text');
});
And read it in another route handler:
app.get('/bar', function(req, res, next) {
var someAttribute = req.session.someAttribute;
res.send(`This will print the attribute I set earlier: ${someAttribute}`);
});
*/
var ENV = 'development';
var env = 'dev';
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
// uncomment after placing your favicon in /public
//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
app.use(logger(env));
app.use(bodyParser.json());
var parseFrom = bodyParser.urlencoded({ extended: false });
app.use(parseFrom);
app.use(express.static('public', config.stoptions))
app.use(express.static(path.join(__dirname, 'public'),config.stoptions));
//app.use(express.static(path.join(__dirname, 'public')));
/**
* Do not forget to add csrf token to all pages
* */
var index = require('./routes/index');
var users = require('./routes/users');
app.use('/', index);
app.use('/users', users);
// catch 404 and forward to error handler
app.use(function(req, res, next) {
var err = new Error('Not Found');
err.status = 404;
next(err);
});
// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
app.disable('x-powered-by');
// Globbing routing files
// config.getGlobbedFiles('./app/routes/**/*.js').forEach(function(routePath) {
//require(path.resolve(routePath))(app);
//});
//app.listen(process.env.PORT || 3030,"127.0.0.1");
//console.log('Listening on 127.0.0.1:3030');
module.exports = app;
module.exports={
secretkey:'my-super-duper-secret-key',
db:'somedb',
host:'localhost',
port:3306,
dbuser:'user',
dbpass:'pass',
frameguardaction:{ frameguard: {action: 'deny'}},
csp:{ directives: { defaultSrc: ["'self'"], styleSrc: ["'self'", 'maxcdn.bootstrapcdn.com'] }},
limiter:{ windowMs: 15*60*1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
delayMs: 0 // disable delaying - full speed until the max limit is reached
},
mysqlstoreoptions : {
host: 'localhost',
port: 3306,
user: 'user',
password: 'pass',
database: 'db',
checkExpirationInterval: 900000,// How frequently expired sessions will be cleared; milliseconds.
expiration: 86400000,// The maximum age of a valid session; milliseconds.
createDatabaseTable: true,// Whether or not to create the sessions database table, if one does not already exist.
connectionLimit: 1,// Number of connections when creating a connection pool
schema: {
tableName: 'sessions',
columnNames: {
session_id: 'session_id',
expires: 'expires',
data: 'data'
}
}
},
sessionkey:'sessionId',
cookie:{ httpOnly: true, secure: true , maxAge: 6000 },
stoptions : {
dotfiles: 'ignore',
etag: false,
extensions: ['html'],
index: false,
maxAge: '1d',
redirect: false,
setHeaders: function (res, path, stat) {
res.set('x-timestamp', Date.now())
}
},
}
{
"name": "basicexpressserversecurity-starter",
"version": "0.0.1",
"private": true,
"scripts": {
"start": "node ./server"
},
"dependencies": {
"body-parser": "~1.18.2",
"cookie-parser": "~1.4.3",
"debug": "~2.6.9",
"ejs": "~2.5.7",
"express": "~4.15.5",
"express-mysql-session": "^1.2.3",
"express-rate-limit": "^2.11.0",
"express-session": "^1.15.6",
"express-validator": "^5.0.1",
"helmet": "^3.11.0",
"lusca": "^1.5.2",
"morgan": "^1.9.0",
"serve-favicon": "~2.4.5"
}
}
#!/usr/bin/env node
/**
* Module dependencies.
*/
var app = require('../app');
var debug = require('debug')('basicexpressserversecurity-starter:server');
var http = require('http');
/**
* Get port from environment and store in Express.
*/
var port = normalizePort(process.env.PORT || '3030');
app.set('port', port);
var host = (process.env.HOST || '127.0.0.1' );
app.set('host',host);
/**
* Create HTTP server.
*/
var server = http.createServer(app);
/**
* Listen on provided port, on all network interfaces.
*/
server.listen(port,host);
server.on('error', onError);
server.on('listening', onListening);
console.log('Listening on ',host,':',port);
/**
* Normalize a port into a number, string, or false.
*/
function normalizePort(val) {
var port = parseInt(val, 10);
if (isNaN(port)) {
// named pipe
return val;
}
if (port >= 0) {
// port number
return port;
}
return false;
}
/**
* Event listener for HTTP server "error" event.
*/
function onError(error) {
if (error.syscall !== 'listen') {
throw error;
}
var bind = typeof port === 'string'
? 'Pipe ' + port
: 'Port ' + port;
// handle specific listen errors with friendly messages
switch (error.code) {
case 'EACCES':
console.error(bind + ' requires elevated privileges');
process.exit(1);
break;
case 'EADDRINUSE':
console.error(bind + ' is already in use');
process.exit(1);
break;
default:
throw error;
}
}
/**
* Event listener for HTTP server "listening" event.
*/
function onListening() {
var addr = server.address();
var bind = typeof addr === 'string'
? 'pipe ' + addr
: 'port ' + addr.port;
debug('Listening on ' + bind);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment