Last active
February 18, 2018 22:50
-
-
Save gnosisbit/65fddc7c88d69ea6335c177d2c342a6f to your computer and use it in GitHub Desktop.
basic - node.js/expressjs - web server security
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()) | |
} | |
}, | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"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" | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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