diff --git a/client/components/admin/admin-security.vue b/client/components/admin/admin-security.vue index b8f37673..909b258e 100644 --- a/client/components/admin/admin-security.vue +++ b/client/components/admin/admin-security.vue @@ -20,6 +20,15 @@ v-card-info(color='red') span Make sure to understand the implications before turning on / off a security feature. v-card-text + v-switch.mt-3( + inset + label='Block Open Redirect' + color='red darken-2' + v-model='config.securityOpenRedirect' + persistent-hint + hint='Prevents user controlled URLs from directing to websites outside of your wiki. This provides Open Redirect protection.' + ) + v-switch.mt-3( inset label='Block IFrame Embedding' @@ -145,6 +154,7 @@ export default { config: { uploadMaxFileSize: 0, uploadMaxFiles: 0, + securityOpenRedirect: true, securityIframe: true, securityReferrerPolicy: true, securityTrustProxy: true, @@ -175,6 +185,7 @@ export default { mutation ( $uploadMaxFileSize: Int $uploadMaxFiles: Int + $securityOpenRedirect: Boolean $securityIframe: Boolean $securityReferrerPolicy: Boolean $securityTrustProxy: Boolean @@ -188,6 +199,7 @@ export default { updateConfig( uploadMaxFileSize: $uploadMaxFileSize, uploadMaxFiles: $uploadMaxFiles, + securityOpenRedirect: $securityOpenRedirect, securityIframe: $securityIframe, securityReferrerPolicy: $securityReferrerPolicy, securityTrustProxy: $securityTrustProxy, @@ -210,6 +222,7 @@ export default { variables: { uploadMaxFileSize: _.toSafeInteger(_.get(this.config, 'uploadMaxFileSize', 0)), uploadMaxFiles: _.toSafeInteger(_.get(this.config, 'uploadMaxFiles', 0)), + securityOpenRedirect: _.get(this.config, 'securityOpenRedirect', false), securityIframe: _.get(this.config, 'securityIframe', false), securityReferrerPolicy: _.get(this.config, 'securityReferrerPolicy', false), securityTrustProxy: _.get(this.config, 'securityTrustProxy', false), @@ -241,6 +254,7 @@ export default { config { uploadMaxFileSize uploadMaxFiles + securityOpenRedirect securityIframe securityReferrerPolicy securityTrustProxy diff --git a/server/app/data.yml b/server/app/data.yml index 6054110d..0d020f23 100644 --- a/server/app/data.yml +++ b/server/app/data.yml @@ -54,6 +54,7 @@ defaults: iconset: 'md' darkMode: false security: + securityOpenRedirect: true securityIframe: true securityReferrerPolicy: true securityTrustProxy: true diff --git a/server/graph/resolvers/site.js b/server/graph/resolvers/site.js index e939a247..cf6a6abb 100644 --- a/server/graph/resolvers/site.js +++ b/server/graph/resolvers/site.js @@ -67,6 +67,7 @@ module.exports = { } WIKI.config.security = { + securityOpenRedirect: _.get(args, 'securityOpenRedirect', WIKI.config.security.securityOpenRedirect), securityIframe: _.get(args, 'securityIframe', WIKI.config.security.securityIframe), securityReferrerPolicy: _.get(args, 'securityReferrerPolicy', WIKI.config.security.securityReferrerPolicy), securityTrustProxy: _.get(args, 'securityTrustProxy', WIKI.config.security.securityTrustProxy), diff --git a/server/graph/schemas/site.graphql b/server/graph/schemas/site.graphql index 56f026be..6b03830c 100644 --- a/server/graph/schemas/site.graphql +++ b/server/graph/schemas/site.graphql @@ -36,6 +36,7 @@ type SiteMutation { featurePageRatings: Boolean featurePageComments: Boolean featurePersonalWikis: Boolean + securityOpenRedirect: Boolean securityIframe: Boolean securityReferrerPolicy: Boolean securityTrustProxy: Boolean @@ -67,6 +68,7 @@ type SiteConfig { featurePageRatings: Boolean! featurePageComments: Boolean! featurePersonalWikis: Boolean! + securityOpenRedirect: Boolean! securityIframe: Boolean! securityReferrerPolicy: Boolean! securityTrustProxy: Boolean! diff --git a/server/middlewares/security.js b/server/middlewares/security.js index a0659b63..f77079f1 100644 --- a/server/middlewares/security.js +++ b/server/middlewares/security.js @@ -13,7 +13,7 @@ module.exports = function (req, res, next) { req.app.disable('x-powered-by') // -> Disable Frame Embedding - if (WIKI.config.securityIframe) { + if (WIKI.config.security.securityIframe) { res.set('X-Frame-Options', 'deny') } @@ -27,14 +27,20 @@ module.exports = function (req, res, next) { res.set('X-UA-Compatible', 'IE=edge') // -> Disables referrer header when navigating to a different origin - if (WIKI.config.securityReferrerPolicy) { + if (WIKI.config.security.securityReferrerPolicy) { res.set('Referrer-Policy', 'same-origin') } // -> Enforce HSTS - if (WIKI.config.securityHSTS) { + if (WIKI.config.security.securityHSTS) { res.set('Strict-Transport-Security', `max-age=${WIKI.config.securityHSTSDuration}; includeSubDomains`) } + // -> Prevent Open Redirect from user provided URL + if (WIKI.config.security.securityOpenRedirect) { + // Strips out all repeating / character in the provided URL + req.url = req.url.replace(/(\/)(?=\/*\1)/g, "") + } + return next() }