Merge pull request #490 from zoff-music/feature/security-headers

Feature/security headers
This commit is contained in:
Kasper Rynning-Tønnesen
2019-03-21 20:17:06 +01:00
committed by GitHub
3 changed files with 233 additions and 182 deletions

View File

@@ -40,6 +40,7 @@
"express-handlebars": "^3.0.2", "express-handlebars": "^3.0.2",
"express-recaptcha": "^3.0.1", "express-recaptcha": "^3.0.1",
"express-session": "^1.15.6", "express-session": "^1.15.6",
"feature-policy": "^0.2.0",
"gulp-sourcemaps": "^2.6.5", "gulp-sourcemaps": "^2.6.5",
"gulp-uglify-es": "^1.0.4", "gulp-uglify-es": "^1.0.4",
"helmet": "^3.16.0", "helmet": "^3.16.0",
@@ -53,6 +54,7 @@
"passport": "^0.4.0", "passport": "^0.4.0",
"passport-local": "^1.0.0", "passport-local": "^1.0.0",
"redis": "^2.8.0", "redis": "^2.8.0",
"referrer-policy": "^1.1.0",
"request": "^2.88.0", "request": "^2.88.0",
"socket.io": "^2.2.0", "socket.io": "^2.2.0",
"socket.io-redis": "^5.2.0", "socket.io-redis": "^5.2.0",

View File

@@ -5,9 +5,9 @@ const path = require('path');
const publicPath = path.join(__dirname + "", '../public'); const publicPath = path.join(__dirname + "", '../public');
var exphbs = require('express-handlebars'); var exphbs = require('express-handlebars');
var hbs = exphbs.create({ var hbs = exphbs.create({
defaultLayout: publicPath + '/layouts/admin/main', defaultLayout: publicPath + '/layouts/admin/main',
layoutsDir: publicPath + '/layouts', layoutsDir: publicPath + '/layouts',
partialsDir: publicPath + '/partials' partialsDir: publicPath + '/partials'
}); });
var passport = require('passport'); var passport = require('passport');
@@ -23,6 +23,7 @@ var session = require('express-session');
var MongoStore = require('connect-mongo')(session); var MongoStore = require('connect-mongo')(session);
var api = require(pathThumbnails + '/routing/admin/api.js'); var api = require(pathThumbnails + '/routing/admin/api.js');
var compression = require('compression');
var User = require(pathThumbnails + '/models/user.js'); var User = require(pathThumbnails + '/models/user.js');
var url = 'mongodb://' + mongo_db_cred.host + '/' + mongo_db_cred.users; var url = 'mongodb://' + mongo_db_cred.host + '/' + mongo_db_cred.users;
mongoose.connect(url); mongoose.connect(url);
@@ -30,23 +31,56 @@ mongoose.connect(url);
app.engine('handlebars', hbs.engine); app.engine('handlebars', hbs.engine);
app.set('view engine', 'handlebars'); app.set('view engine', 'handlebars');
app.use(compression({filter: shouldCompress}))
function shouldCompress (req, res) {
if (req.headers['x-no-compression']) {
// don't compress responses with this request header
return false;
}
// fallback to standard filter function
return compression.filter(req, res);
}
app.set('trust proxy', '127.0.0.1'); app.set('trust proxy', '127.0.0.1');
var bodyParser = require('body-parser');
var cookieParser = require("cookie-parser");
var referrerPolicy = require('referrer-policy');
var helmet = require('helmet');
var featurePolicy = require('feature-policy');
app.use(featurePolicy({
features: {
fullscreen: ["*"],
//vibrate: ["'none'"],
payment: ["'none'"],
microphone: ["'none'"],
camera: ["'none'"],
speaker: ["*"],
syncXhr: ["'self'"],
//notifications: ["'self'"]
}
}));
app.use(helmet({
frameguard: false,
}));
app.use(referrerPolicy({ policy: 'origin-when-cross-origin' }));
app.enable('view cache'); app.enable('view cache');
app.set('views', publicPath); app.set('views', publicPath);
app.use( bodyParser.json() ); // to support JSON-encoded bodies app.use( bodyParser.json() ); // to support JSON-encoded bodies
app.use(bodyParser.urlencoded({ app.use(bodyParser.urlencoded({
extended: true extended: true
})); }));
app.use(session({ app.use(session({
secret: mongo_db_cred.secret, secret: mongo_db_cred.secret,
resave: true, resave: true,
saveUninitialized: true, saveUninitialized: true,
store: new MongoStore({ store: new MongoStore({
url: url, url: url,
useNewUrlParser: true, useNewUrlParser: true,
collection: 'sessions', collection: 'sessions',
ttl: mongo_db_cred.expire ttl: mongo_db_cred.expire
}) })
})); // session secret })); // session secret
app.use(passport.initialize()); app.use(passport.initialize());
app.use(passport.session()); // persistent login sessions app.use(passport.session()); // persistent login sessions
@@ -54,133 +88,133 @@ app.use(passport.session()); // persistent login sessions
//app.use('/assets', express.static(publicPath + '/assets')); //app.use('/assets', express.static(publicPath + '/assets'));
passport.serializeUser(function(user, done) { passport.serializeUser(function(user, done) {
done(null, user.id); done(null, user.id);
}); });
// used to deserialize the user // used to deserialize the user
passport.deserializeUser(function(id, done) { passport.deserializeUser(function(id, done) {
User.findById(id, function(err, user) { User.findById(id, function(err, user) {
done(err, user); done(err, user);
}); });
}); });
passport.use('local-signup', new LocalStrategy({ passport.use('local-signup', new LocalStrategy({
// by default, local strategy uses username and password, we will override with username // by default, local strategy uses username and password, we will override with username
usernameField : 'username', usernameField : 'username',
passwordField : 'password', passwordField : 'password',
passReqToCallback : true // allows us to pass back the entire request to the callback passReqToCallback : true // allows us to pass back the entire request to the callback
}, },
function(req, username, password, done) { function(req, username, password, done) {
// asynchronous // asynchronous
// User.findOne wont fire unless data is sent back // User.findOne wont fire unless data is sent back
process.nextTick(function() { process.nextTick(function() {
// find a user whose username is the same as the forms username // find a user whose username is the same as the forms username
// we are checking to see if the user trying to login already exists // we are checking to see if the user trying to login already exists
var token = req.body.token; var token = req.body.token;
token_db.collection("tokens").find({token: token}, function(err, docs){ token_db.collection("tokens").find({token: token}, function(err, docs){
if(docs.length == 1){ if(docs.length == 1){
token_db.collection("tokens").remove({token: token}, function(err, docs){ token_db.collection("tokens").remove({token: token}, function(err, docs){
User.findOne({ 'username' : username }, function(err, user) { User.findOne({ 'username' : username }, function(err, user) {
// if there are any errors, return the error // if there are any errors, return the error
if (err)
return done(err);
// check to see if theres already a user with that username
if (user) {
return done(null, false);
} else {
// if there is no user with that username
// create the user
var newUser = new User();
// set the user's local credentials
newUser.username = username;
newUser.password = newUser.generateHash(password);
// save the user
newUser.save(function(err) {
if (err) if (err)
throw err; return done(err);
return done(null, newUser);
});
}
}); // check to see if theres already a user with that username
}); if (user) {
} else { return done(null, false);
return done(null, false); } else {
}
}); // if there is no user with that username
}); // create the user
var newUser = new User();
// set the user's local credentials
newUser.username = username;
newUser.password = newUser.generateHash(password);
// save the user
newUser.save(function(err) {
if (err)
throw err;
return done(null, newUser);
});
}
});
});
} else {
return done(null, false);
}
});
});
})); }));
passport.use('local-login', new LocalStrategy({ passport.use('local-login', new LocalStrategy({
// by default, local strategy uses username and password, we will override with email // by default, local strategy uses username and password, we will override with email
usernameField : 'username', usernameField : 'username',
passwordField : 'password', passwordField : 'password',
passReqToCallback : true // allows us to pass back the entire request to the callback passReqToCallback : true // allows us to pass back the entire request to the callback
}, function(req, username, password, done) { // callback with email and password from our form }, function(req, username, password, done) { // callback with email and password from our form
// find a user whose email is the same as the forms email // find a user whose email is the same as the forms email
// we are checking to see if the user trying to login already exists // we are checking to see if the user trying to login already exists
User.findOne({ 'username' : username }, function(err, user) { User.findOne({ 'username' : username }, function(err, user) {
// if there are any errors, return the error before anything else // if there are any errors, return the error before anything else
if (err) if (err)
return done(err); return done(err);
// if no user is found, return the message // if no user is found, return the message
if (!user) if (!user)
return done(null, false); // req.flash is the way to set flashdata using connect-flash return done(null, false); // req.flash is the way to set flashdata using connect-flash
// if the user is found but the password is wrong // if the user is found but the password is wrong
if (!user.validPassword(password)) if (!user.validPassword(password))
return done(null, false); // create the loginMessage and save it to session as flashdata return done(null, false); // create the loginMessage and save it to session as flashdata
// all is well, return successful user // all is well, return successful user
return done(null, user); return done(null, user);
}); });
})); }));
app.post('/signup', passport.authenticate('local-signup', { app.post('/signup', passport.authenticate('local-signup', {
successRedirect : '/', // redirect to the secure profile section successRedirect : '/', // redirect to the secure profile section
failureRedirect : '/signup', // redirect back to the signup page if there is an error failureRedirect : '/signup', // redirect back to the signup page if there is an error
failureFlash : true // allow flash messages failureFlash : true // allow flash messages
})); }));
app.post('/login', passport.authenticate('local-login', { app.post('/login', passport.authenticate('local-login', {
successRedirect : '/', // redirect to the secure profile section successRedirect : '/', // redirect to the secure profile section
failureRedirect : '/login#failed', // redirect back to the signup page if there is an error failureRedirect : '/login#failed', // redirect back to the signup page if there is an error
failureFlash : true // allow flash messages failureFlash : true // allow flash messages
})); }));
app.use('/login', isLoggedInTryingToLogIn, function(req, res) { app.use('/login', isLoggedInTryingToLogIn, function(req, res) {
var data = { var data = {
where_get: "not_authenticated" where_get: "not_authenticated"
}; };
res.render('layouts/admin/not_authenticated', data); res.render('layouts/admin/not_authenticated', data);
}); });
app.use('/signup', isLoggedInTryingToLogIn, function(req, res) { app.use('/signup', isLoggedInTryingToLogIn, function(req, res) {
var data = { var data = {
where_get: "not_authenticated" where_get: "not_authenticated"
}; };
res.render('layouts/admin/not_authenticated', data); res.render('layouts/admin/not_authenticated', data);
}); });
app.use('/', api); app.use('/', api);
app.use('/logout', function(req, res) { app.use('/logout', function(req, res) {
req.logout(); req.logout();
res.redirect('/login'); res.redirect('/login');
}); });
app.use('/assets/admin/authenticated', function(req, res, next) { app.use('/assets/admin/authenticated', function(req, res, next) {
@@ -194,25 +228,25 @@ app.use('/assets/admin/authenticated', function(req, res, next) {
app.use('/assets', express.static(publicPath + '/assets')); app.use('/assets', express.static(publicPath + '/assets'));
app.use('/', isLoggedIn, function(req, res) { app.use('/', isLoggedIn, function(req, res) {
var data = { var data = {
where_get: "authenticated", where_get: "authenticated",
year: new Date().getYear()+1900, year: new Date().getYear()+1900,
}; };
res.render('layouts/admin/authenticated', data); res.render('layouts/admin/authenticated', data);
}); });
function isLoggedInTryingToLogIn(req, res, next){ function isLoggedInTryingToLogIn(req, res, next){
if(!req.isAuthenticated()){ if(!req.isAuthenticated()){
return next(); return next();
} }
res.redirect("/"); res.redirect("/");
} }
function isLoggedIn(req, res, next) { function isLoggedIn(req, res, next) {
if (req.isAuthenticated()) if (req.isAuthenticated())
return next(); return next();
res.redirect('/login'); res.redirect('/login');
} }
//app.listen(default_port); //app.listen(default_port);

View File

@@ -2,17 +2,17 @@ VERSION = require(pathThumbnails + '/VERSION.js');
var secure = false; var secure = false;
var path = require('path'); var path = require('path');
try { try {
var cert_config = require(path.join(path.join(__dirname, '../config/'), 'cert_config.js')); var cert_config = require(path.join(path.join(__dirname, '../config/'), 'cert_config.js'));
var fs = require('fs'); var fs = require('fs');
var privateKey = fs.readFileSync(cert_config.privateKey).toString(); var privateKey = fs.readFileSync(cert_config.privateKey).toString();
var certificate = fs.readFileSync(cert_config.certificate).toString(); var certificate = fs.readFileSync(cert_config.certificate).toString();
var ca = fs.readFileSync(cert_config.ca).toString(); var ca = fs.readFileSync(cert_config.ca).toString();
var credentials = { var credentials = {
key: privateKey, key: privateKey,
cert: certificate, cert: certificate,
ca: ca ca: ca
}; };
secure = true; secure = true;
} catch(err){} } catch(err){}
var add = ""; var add = "";
@@ -24,10 +24,10 @@ var cors = require('cors');
var Functions = require(pathThumbnails + '/handlers/functions.js'); var Functions = require(pathThumbnails + '/handlers/functions.js');
var hbs = exphbs.create({ var hbs = exphbs.create({
defaultLayout: publicPath + '/layouts/client/main', defaultLayout: publicPath + '/layouts/client/main',
layoutsDir: publicPath + '/layouts/client', layoutsDir: publicPath + '/layouts/client',
partialsDir: publicPath + '/partials', partialsDir: publicPath + '/partials',
helpers: { helpers: {
if_equal: function(a, b, opts) { if_equal: function(a, b, opts) {
if (a == b) { if (a == b) {
return opts.fn(this) return opts.fn(this)
@@ -36,7 +36,7 @@ var hbs = exphbs.create({
} }
}, },
decodeString: function(s) { decodeString: function(s) {
if(s == undefined) return s; if(s == undefined) return s;
return Functions.decodeChannelName(s); return Functions.decodeChannelName(s);
} }
@@ -46,13 +46,13 @@ var uniqid = require('uniqid');
app.use(compression({filter: shouldCompress})) app.use(compression({filter: shouldCompress}))
function shouldCompress (req, res) { function shouldCompress (req, res) {
if (req.headers['x-no-compression']) { if (req.headers['x-no-compression']) {
// don't compress responses with this request header // don't compress responses with this request header
return false; return false;
} }
// fallback to standard filter function // fallback to standard filter function
return compression.filter(req, res); return compression.filter(req, res);
} }
app.engine('handlebars', hbs.engine); app.engine('handlebars', hbs.engine);
@@ -63,21 +63,36 @@ app.set('trust proxy', '127.0.0.1');
var bodyParser = require('body-parser'); var bodyParser = require('body-parser');
var cookieParser = require("cookie-parser"); var cookieParser = require("cookie-parser");
var helmet = require('helmet') var referrerPolicy = require('referrer-policy');
app.use(helmet({ var helmet = require('helmet');
frameguard: false var featurePolicy = require('feature-policy');
app.use(featurePolicy({
features: {
fullscreen: ["*"],
//vibrate: ["'none'"],
payment: ["'none'"],
microphone: ["'none'"],
camera: ["'none'"],
speaker: ["*"],
syncXhr: ["'self'"],
//notifications: ["'self'"]
}
})); }));
app.use(helmet({
frameguard: false,
}));
app.use(referrerPolicy({ policy: 'origin-when-cross-origin' }));
app.use( bodyParser.json() ); // to support JSON-encoded bodies app.use( bodyParser.json() ); // to support JSON-encoded bodies
app.use(bodyParser.urlencoded({ // to support URL-encoded bodies app.use(bodyParser.urlencoded({ // to support URL-encoded bodies
extended: true extended: true
})); }));
app.use(cookieParser()); app.use(cookieParser());
//app.set('json spaces', 2); //app.set('json spaces', 2);
io = require('socket.io')({ io = require('socket.io')({
pingTimeout: 25000, pingTimeout: 25000,
//path: '/zoff', //path: '/zoff',
//"origins": ("https://zoff.me:443*,https://zoff.me:8080*,zoff.me:8080*,https://remote.zoff.me:443*,https://remote.zoff.me:8080*,https://fb.zoff.me:443*,https://fb.zoff.me:8080*,https://admin.zoff.me:443*,https://admin.zoff.me:8080*, http://localhost:8080*")}); //"origins": ("https://zoff.me:443*,https://zoff.me:8080*,zoff.me:8080*,https://remote.zoff.me:443*,https://remote.zoff.me:8080*,https://fb.zoff.me:443*,https://fb.zoff.me:8080*,https://admin.zoff.me:443*,https://admin.zoff.me:8080*, http://localhost:8080*")});
}); });
var socketIO = require(pathThumbnails +'/handlers/io.js'); var socketIO = require(pathThumbnails +'/handlers/io.js');
@@ -94,56 +109,56 @@ api_file.sIO = app.socketIO;
var ico_router = require(pathThumbnails + '/routing/client/icons_routing.js'); var ico_router = require(pathThumbnails + '/routing/client/icons_routing.js');
app.get('/robots.txt', function (req, res) { app.get('/robots.txt', function (req, res) {
res.type('text/plain'); res.type('text/plain');
res.send("User-agent: *\nAllow: /$\nDisallow: /"); res.send("User-agent: *\nAllow: /$\nDisallow: /");
}); });
app.use(function (req, res, next) { app.use(function (req, res, next) {
var cookie = req.cookies._uI; var cookie = req.cookies._uI;
var skipElements = ["/_embed", "/assets/manifest.json", "/apple-touch-icon.png"]; var skipElements = ["/_embed", "/assets/manifest.json", "/apple-touch-icon.png"];
if(skipElements.indexOf(req.originalUrl) > -1) { if(skipElements.indexOf(req.originalUrl) > -1) {
res.header("Access-Control-Allow-Origin", "*"); res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
next(); next();
} else { } else {
if(req.originalUrl.split("/").length > 3) { if(req.originalUrl.split("/").length > 3) {
res.header("Access-Control-Allow-Origin", "*"); res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
next(); next();
} else { } else {
if (cookie === undefined) { if (cookie === undefined) {
try { try {
//console.error((new Date), "originalUrl", req.originalUrl); //console.error((new Date), "originalUrl", req.originalUrl);
//console.error((new Date), "couldn't fetch cookie for some reason, maybe no cookie exists?", req.get('origin'), "couldn't fetch cookie for some reason, maybe no cookie exists?"); //console.error((new Date), "couldn't fetch cookie for some reason, maybe no cookie exists?", req.get('origin'), "couldn't fetch cookie for some reason, maybe no cookie exists?");
} catch(e) { } catch(e) {
//console.error((new Date), "couldn't fetch origin"); //console.error((new Date), "couldn't fetch origin");
} }
var user_name = Functions.hash_pass(Functions.rndName(uniqid.time(), 15)); var user_name = Functions.hash_pass(Functions.rndName(uniqid.time(), 15));
res.cookie('_uI', user_name, { res.cookie('_uI', user_name, {
maxAge: 365 * 10000 * 3600000, maxAge: 365 * 10000 * 3600000,
httpOnly: true, httpOnly: true,
secure: secure, secure: secure,
//sameSite: true, //sameSite: true,
}); });
} else { } else {
//process.stderr.write((new Date), "couldn't fetch cookie for some reason, maybe no cookie exists?", req, "couldn't fetch cookie for some reason, maybe no cookie exists?"); //process.stderr.write((new Date), "couldn't fetch cookie for some reason, maybe no cookie exists?", req, "couldn't fetch cookie for some reason, maybe no cookie exists?");
res.cookie('_uI', cookie, { res.cookie('_uI', cookie, {
maxAge: 365 * 10000 * 3600000, maxAge: 365 * 10000 * 3600000,
httpOnly: true, httpOnly: true,
secure: secure, secure: secure,
//sameSite: true, //sameSite: true,
}); });
} }
res.header("Access-Control-Allow-Origin", "*"); res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
next(); next();
} }
} }
}); });
app.use('/service-worker.js', function(req, res) { app.use('/service-worker.js', function(req, res) {
res.sendFile(publicPath + '/service-worker.js'); res.sendFile(publicPath + '/service-worker.js');
}); });
app.use('/', ico_router); app.use('/', ico_router);
@@ -163,8 +178,8 @@ app.use('/assets/admin', function(req, res, next) {
app.use('/assets', express.static(publicPath + '/assets')); app.use('/assets', express.static(publicPath + '/assets'));
app.use(function (req, res, next) { app.use(function (req, res, next) {
res.status(404); res.status(404);
res.redirect("/404"); res.redirect("/404");
}) })
module.exports = app; module.exports = app;