From 7c57dfaf9871f44a1402b92bb8e2571e387660d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Rynning-T=C3=B8nnesen?= Date: Thu, 21 Mar 2019 20:01:56 +0100 Subject: [PATCH 1/4] Added more security headers --- package.json | 1 + server/apps/admin.js | 27 +++++++++++++++++++++++++++ server/apps/client.js | 4 +++- 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 4eb68a23..5b696a1d 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "passport": "^0.4.0", "passport-local": "^1.0.0", "redis": "^2.8.0", + "referrer-policy": "^1.1.0", "request": "^2.88.0", "socket.io": "^2.2.0", "socket.io-redis": "^5.2.0", diff --git a/server/apps/admin.js b/server/apps/admin.js index 97b7fc25..e5cfe653 100644 --- a/server/apps/admin.js +++ b/server/apps/admin.js @@ -30,7 +30,34 @@ mongoose.connect(url); app.engine('handlebars', hbs.engine); 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'); + +var bodyParser = require('body-parser'); +var cookieParser = require("cookie-parser"); +var referrerPolicy = require('referrer-policy'); +var helmet = require('helmet'); +app.use(helmet({ + frameguard: false, + features: { + fullscreen: ["'self'"], + vibrate: ["'none'"], + payment: ['none'], + syncXhr: ["'*'"], + notifications: ["'self'"] + } +})); +app.use(referrerPolicy({ policy: 'origin-when-cross-origin' })); app.enable('view cache'); app.set('views', publicPath); app.use( bodyParser.json() ); // to support JSON-encoded bodies diff --git a/server/apps/client.js b/server/apps/client.js index 4e448e43..97d44172 100755 --- a/server/apps/client.js +++ b/server/apps/client.js @@ -63,10 +63,12 @@ app.set('trust proxy', '127.0.0.1'); var bodyParser = require('body-parser'); var cookieParser = require("cookie-parser"); -var helmet = require('helmet') +var referrerPolicy = require('referrer-policy'); +var helmet = require('helmet'); 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.urlencoded({ // to support URL-encoded bodies extended: true From dfc49156f559433b271f9b322c7c174911eb65c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Rynning-T=C3=B8nnesen?= Date: Thu, 21 Mar 2019 20:03:26 +0100 Subject: [PATCH 2/4] No crash --- server/apps/admin.js | 1 + 1 file changed, 1 insertion(+) diff --git a/server/apps/admin.js b/server/apps/admin.js index e5cfe653..09a7f715 100644 --- a/server/apps/admin.js +++ b/server/apps/admin.js @@ -23,6 +23,7 @@ var session = require('express-session'); var MongoStore = require('connect-mongo')(session); var api = require(pathThumbnails + '/routing/admin/api.js'); +var compression = require('compression'); var User = require(pathThumbnails + '/models/user.js'); var url = 'mongodb://' + mongo_db_cred.host + '/' + mongo_db_cred.users; mongoose.connect(url); From c8614bef526316bc8df3527e3a5c80843c87042c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Rynning-T=C3=B8nnesen?= Date: Thu, 21 Mar 2019 20:11:42 +0100 Subject: [PATCH 3/4] Testing feature-police --- package.json | 1 + server/apps/admin.js | 20 ++++-- server/apps/client.js | 157 +++++++++++++++++++++++------------------- 3 files changed, 99 insertions(+), 79 deletions(-) diff --git a/package.json b/package.json index 5b696a1d..c128f80b 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "express-handlebars": "^3.0.2", "express-recaptcha": "^3.0.1", "express-session": "^1.15.6", + "feature-policy": "^0.2.0", "gulp-sourcemaps": "^2.6.5", "gulp-uglify-es": "^1.0.4", "helmet": "^3.16.0", diff --git a/server/apps/admin.js b/server/apps/admin.js index 09a7f715..d7518edd 100644 --- a/server/apps/admin.js +++ b/server/apps/admin.js @@ -48,15 +48,21 @@ 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, - features: { - fullscreen: ["'self'"], - vibrate: ["'none'"], - payment: ['none'], - syncXhr: ["'*'"], - notifications: ["'self'"] - } })); app.use(referrerPolicy({ policy: 'origin-when-cross-origin' })); app.enable('view cache'); diff --git a/server/apps/client.js b/server/apps/client.js index 97d44172..b7f7ca4c 100755 --- a/server/apps/client.js +++ b/server/apps/client.js @@ -2,17 +2,17 @@ VERSION = require(pathThumbnails + '/VERSION.js'); var secure = false; var path = require('path'); try { - var cert_config = require(path.join(path.join(__dirname, '../config/'), 'cert_config.js')); - var fs = require('fs'); - var privateKey = fs.readFileSync(cert_config.privateKey).toString(); - var certificate = fs.readFileSync(cert_config.certificate).toString(); - var ca = fs.readFileSync(cert_config.ca).toString(); - var credentials = { - key: privateKey, - cert: certificate, - ca: ca - }; - secure = true; + var cert_config = require(path.join(path.join(__dirname, '../config/'), 'cert_config.js')); + var fs = require('fs'); + var privateKey = fs.readFileSync(cert_config.privateKey).toString(); + var certificate = fs.readFileSync(cert_config.certificate).toString(); + var ca = fs.readFileSync(cert_config.ca).toString(); + var credentials = { + key: privateKey, + cert: certificate, + ca: ca + }; + secure = true; } catch(err){} var add = ""; @@ -24,10 +24,10 @@ var cors = require('cors'); var Functions = require(pathThumbnails + '/handlers/functions.js'); var hbs = exphbs.create({ - defaultLayout: publicPath + '/layouts/client/main', - layoutsDir: publicPath + '/layouts/client', - partialsDir: publicPath + '/partials', - helpers: { + defaultLayout: publicPath + '/layouts/client/main', + layoutsDir: publicPath + '/layouts/client', + partialsDir: publicPath + '/partials', + helpers: { if_equal: function(a, b, opts) { if (a == b) { return opts.fn(this) @@ -36,7 +36,7 @@ var hbs = exphbs.create({ } }, decodeString: function(s) { - if(s == undefined) return s; + if(s == undefined) return s; return Functions.decodeChannelName(s); } @@ -46,13 +46,13 @@ var uniqid = require('uniqid'); 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; - } + 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); + // fallback to standard filter function + return compression.filter(req, res); } app.engine('handlebars', hbs.engine); @@ -65,21 +65,34 @@ 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 + frameguard: false, })); app.use(referrerPolicy({ policy: 'origin-when-cross-origin' })); app.use( bodyParser.json() ); // to support JSON-encoded bodies app.use(bodyParser.urlencoded({ // to support URL-encoded bodies - extended: true + extended: true })); app.use(cookieParser()); //app.set('json spaces', 2); io = require('socket.io')({ - pingTimeout: 25000, - //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*")}); + pingTimeout: 25000, + //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*")}); }); var socketIO = require(pathThumbnails +'/handlers/io.js'); @@ -96,56 +109,56 @@ api_file.sIO = app.socketIO; var ico_router = require(pathThumbnails + '/routing/client/icons_routing.js'); app.get('/robots.txt', function (req, res) { - res.type('text/plain'); - res.send("User-agent: *\nAllow: /$\nDisallow: /"); + res.type('text/plain'); + res.send("User-agent: *\nAllow: /$\nDisallow: /"); }); app.use(function (req, res, next) { - var cookie = req.cookies._uI; - var skipElements = ["/_embed", "/assets/manifest.json", "/apple-touch-icon.png"]; - if(skipElements.indexOf(req.originalUrl) > -1) { - res.header("Access-Control-Allow-Origin", "*"); - res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); - next(); - } else { - if(req.originalUrl.split("/").length > 3) { - res.header("Access-Control-Allow-Origin", "*"); - res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); - next(); - } else { - if (cookie === undefined) { - try { - //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?"); + var cookie = req.cookies._uI; + var skipElements = ["/_embed", "/assets/manifest.json", "/apple-touch-icon.png"]; + if(skipElements.indexOf(req.originalUrl) > -1) { + res.header("Access-Control-Allow-Origin", "*"); + res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); + next(); + } else { + if(req.originalUrl.split("/").length > 3) { + res.header("Access-Control-Allow-Origin", "*"); + res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); + next(); + } else { + if (cookie === undefined) { + try { + //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?"); - } catch(e) { - //console.error((new Date), "couldn't fetch origin"); - } - var user_name = Functions.hash_pass(Functions.rndName(uniqid.time(), 15)); - res.cookie('_uI', user_name, { - maxAge: 365 * 10000 * 3600000, - httpOnly: true, - secure: secure, - //sameSite: true, - }); - } 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?"); - res.cookie('_uI', cookie, { - maxAge: 365 * 10000 * 3600000, - httpOnly: true, - secure: secure, - //sameSite: true, - }); - } - res.header("Access-Control-Allow-Origin", "*"); - res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); - next(); - } - } + } catch(e) { + //console.error((new Date), "couldn't fetch origin"); + } + var user_name = Functions.hash_pass(Functions.rndName(uniqid.time(), 15)); + res.cookie('_uI', user_name, { + maxAge: 365 * 10000 * 3600000, + httpOnly: true, + secure: secure, + //sameSite: true, + }); + } 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?"); + res.cookie('_uI', cookie, { + maxAge: 365 * 10000 * 3600000, + httpOnly: true, + secure: secure, + //sameSite: true, + }); + } + res.header("Access-Control-Allow-Origin", "*"); + res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); + next(); + } + } }); app.use('/service-worker.js', function(req, res) { - res.sendFile(publicPath + '/service-worker.js'); + res.sendFile(publicPath + '/service-worker.js'); }); app.use('/', ico_router); @@ -165,8 +178,8 @@ app.use('/assets/admin', function(req, res, next) { app.use('/assets', express.static(publicPath + '/assets')); app.use(function (req, res, next) { - res.status(404); - res.redirect("/404"); + res.status(404); + res.redirect("/404"); }) module.exports = app; From dc3bd12f69353c617284fe8ba45ed10d0cf167bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Rynning-T=C3=B8nnesen?= Date: Thu, 21 Mar 2019 20:16:39 +0100 Subject: [PATCH 4/4] Better headers --- server/apps/admin.js | 236 +++++++++++++++++++++--------------------- server/apps/client.js | 6 +- 2 files changed, 121 insertions(+), 121 deletions(-) diff --git a/server/apps/admin.js b/server/apps/admin.js index d7518edd..e4c4e9c1 100644 --- a/server/apps/admin.js +++ b/server/apps/admin.js @@ -5,9 +5,9 @@ const path = require('path'); const publicPath = path.join(__dirname + "", '../public'); var exphbs = require('express-handlebars'); var hbs = exphbs.create({ - defaultLayout: publicPath + '/layouts/admin/main', - layoutsDir: publicPath + '/layouts', - partialsDir: publicPath + '/partials' + defaultLayout: publicPath + '/layouts/admin/main', + layoutsDir: publicPath + '/layouts', + partialsDir: publicPath + '/partials' }); var passport = require('passport'); @@ -34,13 +34,13 @@ 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; - } + 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); + // fallback to standard filter function + return compression.filter(req, res); } app.set('trust proxy', '127.0.0.1'); @@ -51,36 +51,36 @@ var helmet = require('helmet'); var featurePolicy = require('feature-policy'); app.use(featurePolicy({ features: { - fullscreen: ["'*'"], - vibrate: ["'none'"], + fullscreen: ["*"], + //vibrate: ["'none'"], payment: ["'none'"], microphone: ["'none'"], camera: ["'none'"], speaker: ["*"], syncXhr: ["'self'"], - notifications: ["'self'"] + //notifications: ["'self'"] } })); app.use(helmet({ - frameguard: false, + frameguard: false, })); app.use(referrerPolicy({ policy: 'origin-when-cross-origin' })); app.enable('view cache'); app.set('views', publicPath); app.use( bodyParser.json() ); // to support JSON-encoded bodies app.use(bodyParser.urlencoded({ - extended: true + extended: true })); app.use(session({ - secret: mongo_db_cred.secret, - resave: true, - saveUninitialized: true, - store: new MongoStore({ - url: url, - useNewUrlParser: true, - collection: 'sessions', - ttl: mongo_db_cred.expire - }) + secret: mongo_db_cred.secret, + resave: true, + saveUninitialized: true, + store: new MongoStore({ + url: url, + useNewUrlParser: true, + collection: 'sessions', + ttl: mongo_db_cred.expire + }) })); // session secret app.use(passport.initialize()); app.use(passport.session()); // persistent login sessions @@ -88,133 +88,133 @@ app.use(passport.session()); // persistent login sessions //app.use('/assets', express.static(publicPath + '/assets')); passport.serializeUser(function(user, done) { - done(null, user.id); + done(null, user.id); }); // used to deserialize the user passport.deserializeUser(function(id, done) { - User.findById(id, function(err, user) { - done(err, user); - }); + User.findById(id, function(err, user) { + done(err, user); + }); }); passport.use('local-signup', new LocalStrategy({ - // by default, local strategy uses username and password, we will override with username - usernameField : 'username', - passwordField : 'password', - passReqToCallback : true // allows us to pass back the entire request to the callback + // by default, local strategy uses username and password, we will override with username + usernameField : 'username', + passwordField : 'password', + passReqToCallback : true // allows us to pass back the entire request to the callback }, function(req, username, password, done) { - // asynchronous - // User.findOne wont fire unless data is sent back - process.nextTick(function() { + // asynchronous + // User.findOne wont fire unless data is sent back + process.nextTick(function() { - // 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 - var token = req.body.token; - token_db.collection("tokens").find({token: token}, function(err, docs){ - if(docs.length == 1){ - token_db.collection("tokens").remove({token: token}, function(err, docs){ - User.findOne({ 'username' : username }, function(err, user) { - // 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) { + // 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 + var token = req.body.token; + token_db.collection("tokens").find({token: token}, function(err, docs){ + if(docs.length == 1){ + token_db.collection("tokens").remove({token: token}, function(err, docs){ + User.findOne({ 'username' : username }, function(err, user) { + // if there are any errors, return the error if (err) - throw err; - return done(null, newUser); - }); - } + return done(err); - }); - }); - } else { - return done(null, false); - } - }); - }); + // 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) + throw err; + return done(null, newUser); + }); + } + + }); + }); + } else { + return done(null, false); + } + }); + }); })); passport.use('local-login', new LocalStrategy({ - // by default, local strategy uses username and password, we will override with email - usernameField : 'username', - passwordField : 'password', - passReqToCallback : true // allows us to pass back the entire request to the callback + // by default, local strategy uses username and password, we will override with email + usernameField : 'username', + passwordField : 'password', + 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 - // 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 - User.findOne({ 'username' : username }, function(err, user) { - // if there are any errors, return the error before anything else - if (err) - return done(err); + // 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 + User.findOne({ 'username' : username }, function(err, user) { + // if there are any errors, return the error before anything else + if (err) + return done(err); - // if no user is found, return the message - if (!user) - return done(null, false); // req.flash is the way to set flashdata using connect-flash + // if no user is found, return the message + if (!user) + 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 (!user.validPassword(password)) - return done(null, false); // create the loginMessage and save it to session as flashdata + // if the user is found but the password is wrong + if (!user.validPassword(password)) + 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', { - successRedirect : '/', // redirect to the secure profile section - failureRedirect : '/signup', // redirect back to the signup page if there is an error - failureFlash : true // allow flash messages + successRedirect : '/', // redirect to the secure profile section + failureRedirect : '/signup', // redirect back to the signup page if there is an error + failureFlash : true // allow flash messages })); app.post('/login', passport.authenticate('local-login', { - successRedirect : '/', // redirect to the secure profile section - failureRedirect : '/login#failed', // redirect back to the signup page if there is an error - failureFlash : true // allow flash messages + successRedirect : '/', // redirect to the secure profile section + failureRedirect : '/login#failed', // redirect back to the signup page if there is an error + failureFlash : true // allow flash messages })); app.use('/login', isLoggedInTryingToLogIn, function(req, res) { - var data = { - where_get: "not_authenticated" - }; + var data = { + 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) { - var data = { - where_get: "not_authenticated" - }; + var data = { + where_get: "not_authenticated" + }; - res.render('layouts/admin/not_authenticated', data); + res.render('layouts/admin/not_authenticated', data); }); app.use('/', api); app.use('/logout', function(req, res) { - req.logout(); - res.redirect('/login'); + req.logout(); + res.redirect('/login'); }); app.use('/assets/admin/authenticated', function(req, res, next) { @@ -228,25 +228,25 @@ app.use('/assets/admin/authenticated', function(req, res, next) { app.use('/assets', express.static(publicPath + '/assets')); app.use('/', isLoggedIn, function(req, res) { - var data = { - where_get: "authenticated", - year: new Date().getYear()+1900, - }; + var data = { + where_get: "authenticated", + year: new Date().getYear()+1900, + }; - res.render('layouts/admin/authenticated', data); + res.render('layouts/admin/authenticated', data); }); function isLoggedInTryingToLogIn(req, res, next){ - if(!req.isAuthenticated()){ - return next(); - } - res.redirect("/"); + if(!req.isAuthenticated()){ + return next(); + } + res.redirect("/"); } function isLoggedIn(req, res, next) { - if (req.isAuthenticated()) - return next(); - res.redirect('/login'); + if (req.isAuthenticated()) + return next(); + res.redirect('/login'); } //app.listen(default_port); diff --git a/server/apps/client.js b/server/apps/client.js index b7f7ca4c..b863e040 100755 --- a/server/apps/client.js +++ b/server/apps/client.js @@ -68,14 +68,14 @@ var helmet = require('helmet'); var featurePolicy = require('feature-policy'); app.use(featurePolicy({ features: { - fullscreen: ["'*'"], - vibrate: ["'none'"], + fullscreen: ["*"], + //vibrate: ["'none'"], payment: ["'none'"], microphone: ["'none'"], camera: ["'none'"], speaker: ["*"], syncXhr: ["'self'"], - notifications: ["'self'"] + //notifications: ["'self'"] } })); app.use(helmet({