diff --git a/client/components/admin.vue b/client/components/admin.vue
index 4decb7ba..92684c7a 100644
--- a/client/components/admin.vue
+++ b/client/components/admin.vue
@@ -89,6 +89,9 @@
v-list-item(to='/mail', color='primary', v-if='hasPermission(`manage:system`)')
v-list-item-avatar(size='24', tile): v-icon mdi-email-multiple-outline
v-list-item-title {{ $t('admin:mail.title') }}
+ v-list-item(to='/security', v-if='hasPermission(`manage:system`)')
+ v-list-item-avatar(size='24', tile): v-icon mdi-lock-check
+ v-list-item-title {{ $t('admin:security.title') }}
v-list-item(to='/ssl', v-if='hasPermission(`manage:system`)')
v-list-item-avatar(size='24', tile): v-icon mdi-cloud-lock-outline
v-list-item-title {{ $t('admin:ssl.title') }}
@@ -172,6 +175,7 @@ const router = new VueRouter({
{ path: '/storage', component: () => import(/* webpackChunkName: "admin" */ './admin/admin-storage.vue') },
{ path: '/api', component: () => import(/* webpackChunkName: "admin" */ './admin/admin-api.vue') },
{ path: '/mail', component: () => import(/* webpackChunkName: "admin" */ './admin/admin-mail.vue') },
+ { path: '/security', component: () => import(/* webpackChunkName: "admin" */ './admin/admin-security.vue') },
{ path: '/ssl', component: () => import(/* webpackChunkName: "admin" */ './admin/admin-ssl.vue') },
{ path: '/system', component: () => import(/* webpackChunkName: "admin" */ './admin/admin-system.vue') },
{ path: '/utilities', component: () => import(/* webpackChunkName: "admin" */ './admin/admin-utilities.vue') },
diff --git a/client/components/admin/admin-general.vue b/client/components/admin/admin-general.vue
index 75a41ab6..02507658 100644
--- a/client/components/admin/admin-general.vue
+++ b/client/components/admin/admin-general.vue
@@ -167,93 +167,6 @@
disabled
)
- v-card.mt-5.animated.fadeInUp.wait-p5s
- v-toolbar(color='red darken-2', dark, dense, flat)
- v-toolbar-title.subtitle-1 Security
- v-card-text
- v-alert(outlined, color='red darken-2', icon='mdi-information-outline').body-2 Make sure to understand the implications before turning on / off a security feature.
- v-switch.mt-3(
- inset
- label='Block IFrame Embedding'
- color='red darken-2'
- v-model='config.securityIframe'
- persistent-hint
- hint='Prevents other websites from embedding your wiki in an iframe. This provides clickjacking protection.'
- )
-
- v-divider.mt-3
- v-switch(
- inset
- label='Same Origin Referrer Policy'
- color='red darken-2'
- v-model='config.securityReferrerPolicy'
- persistent-hint
- hint='Limits the referrer header to same origin.'
- )
-
- v-divider.mt-3
- v-switch(
- inset
- label='Trust X-Forwarded-* Proxy Headers'
- color='red darken-2'
- v-model='config.securityTrustProxy'
- persistent-hint
- hint='Should be enabled when using a reverse-proxy like nginx, apache, CloudFlare, etc in front of Wiki.js. Turn off otherwise.'
- )
-
- v-divider.mt-3
- v-switch(
- inset
- label='Subresource Integrity (SRI)'
- color='red darken-2'
- v-model='config.securitySRI'
- persistent-hint
- hint='This ensure that resources such as CSS and JS files are not altered during delivery.'
- disabled
- )
-
- v-divider.mt-3
- v-switch(
- inset
- label='Enforce HSTS'
- color='red darken-2'
- v-model='config.securityHSTS'
- persistent-hint
- hint='This ensures the connection cannot be established through an insecure HTTP connection.'
- )
- v-select.mt-5(
- outlined
- label='HSTS Max Age'
- :items='hstsDurations'
- v-model='config.securityHSTSDuration'
- prepend-icon='mdi-subdirectory-arrow-right'
- :disabled='!config.securityHSTS'
- hide-details
- style='max-width: 450px;'
- )
- .pl-11.mt-3
- .caption Defines the duration for which the server should only deliver content through HTTPS.
- .caption It's a good idea to start with small values and make sure that nothing breaks on your wiki before moving to longer values.
-
- v-divider.mt-3
- v-switch(
- inset
- label='Enforce CSP'
- color='red darken-2'
- v-model='config.securityCSP'
- persistent-hint
- hint='Restricts scripts to pre-approved content sources.'
- disabled
- )
- v-textarea.mt-5(
- label='CSP Directives'
- outlined
- v-model='config.securityCSPDirectives'
- prepend-icon='mdi-subdirectory-arrow-right'
- persistent-hint
- hint='One directive per line.'
- disabled
- )
component(:is='activeModal')
@@ -296,24 +209,8 @@ export default {
featurePageRatings: false,
featurePageComments: false,
featurePersonalWikis: false,
- featureTinyPNG: false,
- securityIframe: true,
- securityReferrerPolicy: true,
- securityTrustProxy: true,
- securitySRI: true,
- securityHSTS: false,
- securityHSTSDuration: 0,
- securityCSP: false,
- securityCSPDirectives: ''
+ featureTinyPNG: false
},
- hstsDurations: [
- { value: 300, text: '5 minutes' },
- { value: 86400, text: '1 day' },
- { value: 604800, text: '1 week' },
- { value: 2592000, text: '1 month' },
- { value: 31536000, text: '1 year' },
- { value: 63072000, text: '2 years' }
- ],
metaRobots: [
{ text: 'Index', value: 'index' },
{ text: 'Follow', value: 'follow' },
@@ -360,14 +257,6 @@ export default {
$featurePageRatings: Boolean!
$featurePageComments: Boolean!
$featurePersonalWikis: Boolean!
- $securityIframe: Boolean!
- $securityReferrerPolicy: Boolean!
- $securityTrustProxy: Boolean!
- $securitySRI: Boolean!
- $securityHSTS: Boolean!
- $securityHSTSDuration: Int!
- $securityCSP: Boolean!
- $securityCSPDirectives: String!
) {
site {
updateConfig(
@@ -382,15 +271,7 @@ export default {
logoUrl: $logoUrl,
featurePageRatings: $featurePageRatings,
featurePageComments: $featurePageComments,
- featurePersonalWikis: $featurePersonalWikis,
- securityIframe: $securityIframe,
- securityReferrerPolicy: $securityReferrerPolicy,
- securityTrustProxy: $securityTrustProxy,
- securitySRI: $securitySRI,
- securityHSTS: $securityHSTS,
- securityHSTSDuration: $securityHSTSDuration,
- securityCSP: $securityCSP,
- securityCSPDirectives: $securityCSPDirectives
+ featurePersonalWikis: $featurePersonalWikis
) {
responseResult {
succeeded
@@ -414,15 +295,7 @@ export default {
logoUrl: _.get(this.config, 'logoUrl', ''),
featurePageRatings: _.get(this.config, 'featurePageRatings', false),
featurePageComments: _.get(this.config, 'featurePageComments', false),
- featurePersonalWikis: _.get(this.config, 'featurePersonalWikis', false),
- securityIframe: _.get(this.config, 'securityIframe', false),
- securityReferrerPolicy: _.get(this.config, 'securityReferrerPolicy', false),
- securityTrustProxy: _.get(this.config, 'securityTrustProxy', false),
- securitySRI: _.get(this.config, 'securitySRI', false),
- securityHSTS: _.get(this.config, 'securityHSTS', false),
- securityHSTSDuration: _.get(this.config, 'securityHSTSDuration', 0),
- securityCSP: _.get(this.config, 'securityCSP', false),
- securityCSPDirectives: _.get(this.config, 'securityCSPDirectives', '')
+ featurePersonalWikis: _.get(this.config, 'featurePersonalWikis', false)
},
watchLoading (isLoading) {
this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'admin-site-update')
@@ -475,14 +348,6 @@ export default {
featurePageRatings
featurePageComments
featurePersonalWikis
- securityIframe
- securityReferrerPolicy
- securityTrustProxy
- securitySRI
- securityHSTS
- securityHSTSDuration
- securityCSP
- securityCSPDirectives
}
}
}
diff --git a/client/components/admin/admin-security.vue b/client/components/admin/admin-security.vue
new file mode 100644
index 00000000..649e13dd
--- /dev/null
+++ b/client/components/admin/admin-security.vue
@@ -0,0 +1,265 @@
+
+ v-container(fluid, grid-list-lg)
+ v-layout(row wrap)
+ v-flex(xs12)
+ .admin-header
+ img.animated.fadeInUp(src='/svg/icon-private.svg', alt='Security', style='width: 80px;')
+ .admin-header-title
+ .headline.primary--text.animated.fadeInLeft {{ $t('admin:security.title') }}
+ .subtitle-1.grey--text.animated.fadeInLeft {{ $t('admin:security.subtitle') }}
+ v-spacer
+ v-btn.animated.fadeInDown(color='success', depressed, @click='save', large)
+ v-icon(left) mdi-check
+ span {{$t('common:actions.apply')}}
+ v-form.pt-3
+ v-layout(row wrap)
+ v-flex(lg6 xs12)
+ v-card.animated.fadeInUp
+ v-toolbar(color='red darken-2', dark, dense, flat)
+ v-toolbar-title.subtitle-1 Security
+ v-card-text
+ v-alert(outlined, color='red darken-2', icon='mdi-information-outline').body-2 Make sure to understand the implications before turning on / off a security feature.
+ v-switch.mt-3(
+ inset
+ label='Block IFrame Embedding'
+ color='red darken-2'
+ v-model='config.securityIframe'
+ persistent-hint
+ hint='Prevents other websites from embedding your wiki in an iframe. This provides clickjacking protection.'
+ )
+
+ v-divider.mt-3
+ v-switch(
+ inset
+ label='Same Origin Referrer Policy'
+ color='red darken-2'
+ v-model='config.securityReferrerPolicy'
+ persistent-hint
+ hint='Limits the referrer header to same origin.'
+ )
+
+ v-divider.mt-3
+ v-switch(
+ inset
+ label='Trust X-Forwarded-* Proxy Headers'
+ color='red darken-2'
+ v-model='config.securityTrustProxy'
+ persistent-hint
+ hint='Should be enabled when using a reverse-proxy like nginx, apache, CloudFlare, etc in front of Wiki.js. Turn off otherwise.'
+ )
+
+ //- v-divider.mt-3
+ //- v-switch(
+ //- inset
+ //- label='Subresource Integrity (SRI)'
+ //- color='red darken-2'
+ //- v-model='config.securitySRI'
+ //- persistent-hint
+ //- hint='This ensure that resources such as CSS and JS files are not altered during delivery.'
+ //- disabled
+ //- )
+
+ v-divider.mt-3
+ v-switch(
+ inset
+ label='Enforce HSTS'
+ color='red darken-2'
+ v-model='config.securityHSTS'
+ persistent-hint
+ hint='This ensures the connection cannot be established through an insecure HTTP connection.'
+ )
+ v-select.mt-5(
+ outlined
+ label='HSTS Max Age'
+ :items='hstsDurations'
+ v-model='config.securityHSTSDuration'
+ prepend-icon='mdi-subdirectory-arrow-right'
+ :disabled='!config.securityHSTS'
+ hide-details
+ style='max-width: 450px;'
+ )
+ .pl-11.mt-3
+ .caption Defines the duration for which the server should only deliver content through HTTPS.
+ .caption It's a good idea to start with small values and make sure that nothing breaks on your wiki before moving to longer values.
+
+ v-divider.mt-3
+ v-switch(
+ inset
+ label='Enforce CSP'
+ color='red darken-2'
+ v-model='config.securityCSP'
+ persistent-hint
+ hint='Restricts scripts to pre-approved content sources.'
+ disabled
+ )
+ v-textarea.mt-5(
+ label='CSP Directives'
+ outlined
+ v-model='config.securityCSPDirectives'
+ prepend-icon='mdi-subdirectory-arrow-right'
+ persistent-hint
+ hint='One directive per line.'
+ disabled
+ )
+
+ v-flex(lg6 xs12)
+ v-card.animated.fadeInUp.wait-p2s
+ v-toolbar(color='primary', dark, dense, flat)
+ v-toolbar-title.subtitle-1 {{ $t('admin:security.uploads') }}
+ v-card-text
+ v-text-field(
+ outlined
+ :label='$t(`admin:security.maxUploadSize`)'
+ required
+ v-model='config.uploadMaxFileSize'
+ prepend-icon='mdi-progress-upload'
+ :hint='$t(`admin:security.maxUploadSizeHint`)'
+ persistent-hint
+ :suffix='$t(`admin:security.maxUploadSizeSuffix`)'
+ style='max-width: 450px;'
+ )
+ v-text-field.mt-3(
+ outlined
+ :label='$t(`admin:security.maxUploadBatch`)'
+ required
+ v-model='config.uploadMaxFiles'
+ prepend-icon='mdi-upload-lock'
+ :hint='$t(`admin:security.maxUploadBatchHint`)'
+ persistent-hint
+ :suffix='$t(`admin:security.maxUploadBatchSuffix`)'
+ style='max-width: 450px;'
+ )
+
+
+
+
+
diff --git a/client/static/svg/icon-private.svg b/client/static/svg/icon-private.svg
new file mode 100644
index 00000000..1a716e7d
--- /dev/null
+++ b/client/static/svg/icon-private.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/config.sample.yml b/config.sample.yml
index 73f208ff..c149f702 100644
--- a/config.sample.yml
+++ b/config.sample.yml
@@ -105,18 +105,6 @@ bindIP: 0.0.0.0
logLevel: info
-# ---------------------------------------------------------------------
-# Upload Limits
-# ---------------------------------------------------------------------
-# If you're using a reverse-proxy in front of Wiki.js, you must also
-# change your proxy upload limits!
-
-uploads:
- # Maximum upload size in bytes per file (default: 5242880 (5 MB))
- maxFileSize: 5242880
- # Maximum file uploads per request (default: 10)
- maxFiles: 10
-
# ---------------------------------------------------------------------
# Offline Mode
# ---------------------------------------------------------------------
diff --git a/server/app/data.yml b/server/app/data.yml
index 83b37e1a..6054110d 100644
--- a/server/app/data.yml
+++ b/server/app/data.yml
@@ -24,9 +24,6 @@ defaults:
min: 1
bindIP: 0.0.0.0
logLevel: info
- uploads:
- maxFileSize: 5242880
- maxFiles: 10
offline: false
ha: false
# DB defaults
@@ -67,6 +64,9 @@ defaults:
securityCSPDirectives: ''
server:
sslRedir: false
+ uploads:
+ maxFileSize: 5242880
+ maxFiles: 10
flags:
ldapdebug: false
sqllog: false
diff --git a/server/controllers/upload.js b/server/controllers/upload.js
index 65cd4d15..c6a3685d 100644
--- a/server/controllers/upload.js
+++ b/server/controllers/upload.js
@@ -10,13 +10,15 @@ const sanitize = require('sanitize-filename')
/**
* Upload files
*/
-router.post('/u', multer({
- dest: path.resolve(WIKI.ROOTPATH, WIKI.config.dataPath, 'uploads'),
- limits: {
- fileSize: WIKI.config.uploads.maxFileSize,
- files: WIKI.config.uploads.maxFiles
- }
-}).array('mediaUpload'), async (req, res, next) => {
+router.post('/u', (req, res, next) => {
+ multer({
+ dest: path.resolve(WIKI.ROOTPATH, WIKI.config.dataPath, 'uploads'),
+ limits: {
+ fileSize: WIKI.config.uploads.maxFileSize,
+ files: WIKI.config.uploads.maxFiles
+ }
+ }).array('mediaUpload')(req, res, next)
+}, async (req, res, next) => {
if (!_.some(req.user.permissions, pm => _.includes(['write:assets', 'manage:system'], pm))) {
return res.status(403).json({
succeeded: false,
diff --git a/server/graph/resolvers/site.js b/server/graph/resolvers/site.js
index bae94549..e939a247 100644
--- a/server/graph/resolvers/site.js
+++ b/server/graph/resolvers/site.js
@@ -20,44 +20,69 @@ module.exports = {
logoUrl: WIKI.config.logoUrl,
...WIKI.config.seo,
...WIKI.config.features,
- ...WIKI.config.security
+ ...WIKI.config.security,
+ uploadMaxFileSize: WIKI.config.uploads.maxFileSize,
+ uploadMaxFiles: WIKI.config.uploads.maxFiles
}
}
},
SiteMutation: {
async updateConfig(obj, args, context) {
- let siteHost = _.trim(args.host)
- if (siteHost.endsWith('/')) {
- siteHost = siteHost.splice(0, -1)
- }
try {
- WIKI.config.host = siteHost
- WIKI.config.title = _.trim(args.title)
- WIKI.config.company = _.trim(args.company)
- WIKI.config.contentLicense = args.contentLicense
+ if (args.host) {
+ let siteHost = _.trim(args.host)
+ if (siteHost.endsWith('/')) {
+ siteHost = siteHost.splice(0, -1)
+ }
+ WIKI.config.host = siteHost
+ }
+
+ if (args.title) {
+ WIKI.config.title = _.trim(args.title)
+ }
+
+ if (args.company) {
+ WIKI.config.company = _.trim(args.company)
+ }
+
+ if (args.contentLicense) {
+ WIKI.config.contentLicense = args.contentLicense
+ }
+
+ if (args.logoUrl) {
+ WIKI.config.logoUrl = _.trim(args.logoUrl)
+ }
+
WIKI.config.seo = {
- description: args.description,
- robots: args.robots,
- analyticsService: args.analyticsService,
- analyticsId: args.analyticsId
+ description: _.get(args, 'description', WIKI.config.seo.description),
+ robots: _.get(args, 'robots', WIKI.config.seo.robots),
+ analyticsService: _.get(args, 'analyticsService', WIKI.config.seo.analyticsService),
+ analyticsId: _.get(args, 'analyticsId', WIKI.config.seo.analyticsId)
}
- WIKI.config.logoUrl = _.trim(args.logoUrl)
+
WIKI.config.features = {
- featurePageRatings: args.featurePageRatings,
- featurePageComments: args.featurePageComments,
- featurePersonalWikis: args.featurePersonalWikis
+ featurePageRatings: _.get(args, 'featurePageRatings', WIKI.config.features.featurePageRatings),
+ featurePageComments: _.get(args, 'featurePageComments', WIKI.config.features.featurePageComments),
+ featurePersonalWikis: _.get(args, 'featurePersonalWikis', WIKI.config.features.featurePersonalWikis)
}
+
WIKI.config.security = {
- securityIframe: args.securityIframe,
- securityReferrerPolicy: args.securityReferrerPolicy,
- securityTrustProxy: args.securityTrustProxy,
- securitySRI: args.securitySRI,
- securityHSTS: args.securityHSTS,
- securityHSTSDuration: args.securityHSTSDuration,
- securityCSP: args.securityCSP,
- securityCSPDirectives: args.securityCSPDirectives
+ securityIframe: _.get(args, 'securityIframe', WIKI.config.security.securityIframe),
+ securityReferrerPolicy: _.get(args, 'securityReferrerPolicy', WIKI.config.security.securityReferrerPolicy),
+ securityTrustProxy: _.get(args, 'securityTrustProxy', WIKI.config.security.securityTrustProxy),
+ securitySRI: _.get(args, 'securitySRI', WIKI.config.security.securitySRI),
+ securityHSTS: _.get(args, 'securityHSTS', WIKI.config.security.securityHSTS),
+ securityHSTSDuration: _.get(args, 'securityHSTSDuration', WIKI.config.security.securityHSTSDuration),
+ securityCSP: _.get(args, 'securityCSP', WIKI.config.security.securityCSP),
+ securityCSPDirectives: _.get(args, 'securityCSPDirectives', WIKI.config.security.securityCSPDirectives)
}
- await WIKI.configSvc.saveToDb(['host', 'title', 'company', 'contentLicense', 'seo', 'logoUrl', 'features', 'security'])
+
+ WIKI.config.uploads = {
+ maxFileSize: _.get(args, 'uploadMaxFileSize', WIKI.config.uploads.maxFileSize),
+ maxFiles: _.get(args, 'uploadMaxFiles', WIKI.config.uploads.maxFiles)
+ }
+
+ await WIKI.configSvc.saveToDb(['host', 'title', 'company', 'contentLicense', 'seo', 'logoUrl', 'features', 'security', 'uploads'])
if (WIKI.config.security.securityTrustProxy) {
WIKI.app.enable('trust proxy')
diff --git a/server/graph/schemas/site.graphql b/server/graph/schemas/site.graphql
index 90da127a..56f026be 100644
--- a/server/graph/schemas/site.graphql
+++ b/server/graph/schemas/site.graphql
@@ -24,26 +24,29 @@ type SiteQuery {
type SiteMutation {
updateConfig(
- host: String!
- title: String!
- description: String!
- robots: [String]!
- analyticsService: String!
- analyticsId: String!
- company: String!
- contentLicense: String!
- logoUrl: String!
- featurePageRatings: Boolean!
- featurePageComments: Boolean!
- featurePersonalWikis: Boolean!
- securityIframe: Boolean!
- securityReferrerPolicy: Boolean!
- securityTrustProxy: Boolean!
- securitySRI: Boolean!
- securityHSTS: Boolean!
- securityHSTSDuration: Int!
- securityCSP: Boolean!
- securityCSPDirectives: String!
+ host: String
+ title: String
+ description: String
+ robots: [String]
+ analyticsService: String
+ analyticsId: String
+ company: String
+ contentLicense: String
+ logoUrl: String
+ featurePageRatings: Boolean
+ featurePageComments: Boolean
+ featurePersonalWikis: Boolean
+ securityIframe: Boolean
+ securityReferrerPolicy: Boolean
+ securityTrustProxy: Boolean
+ securitySRI: Boolean
+ securityHSTS: Boolean
+ securityHSTSDuration: Int
+ securityCSP: Boolean
+ securityCSPDirectives: String
+ uploadMaxFileSize: Int
+ uploadMaxFiles: Int
+
): DefaultResponse @auth(requires: ["manage:system"])
}
@@ -72,4 +75,6 @@ type SiteConfig {
securityHSTSDuration: Int!
securityCSP: Boolean!
securityCSPDirectives: String!
+ uploadMaxFileSize: Int!
+ uploadMaxFiles: Int!
}
diff --git a/server/setup.js b/server/setup.js
index 550835d7..94bd8392 100644
--- a/server/setup.js
+++ b/server/setup.js
@@ -186,6 +186,7 @@ module.exports = () => {
'sessionSecret',
'telemetry',
'theming',
+ 'uploads',
'title'
], false)