admin-security.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430
  1. <template lang='pug'>
  2. v-container(fluid, grid-list-lg)
  3. v-layout(row wrap)
  4. v-flex(xs12)
  5. .admin-header
  6. img.animated.fadeInUp(src='/_assets/svg/icon-private.svg', alt='Security', style='width: 80px;')
  7. .admin-header-title
  8. .headline.primary--text.animated.fadeInLeft {{ $t('admin:security.title') }}
  9. .subtitle-1.grey--text.animated.fadeInLeft {{ $t('admin:security.subtitle') }}
  10. v-spacer
  11. v-btn.animated.fadeInDown(color='success', depressed, @click='save', large)
  12. v-icon(left) mdi-check
  13. span {{$t('common:actions.apply')}}
  14. v-form.pt-3
  15. v-layout(row wrap)
  16. v-flex(lg6 xs12)
  17. v-card.animated.fadeInUp
  18. v-toolbar(color='red darken-2', dark, dense, flat)
  19. v-toolbar-title.subtitle-1 Security
  20. v-card-info(color='red')
  21. span Make sure to understand the implications before turning on / off a security feature.
  22. v-card-text
  23. v-switch(
  24. inset
  25. label='Block Open Redirect'
  26. color='red darken-2'
  27. v-model='config.securityOpenRedirect'
  28. persistent-hint
  29. hint='Prevents user controlled URLs from directing to websites outside of your wiki. This provides Open Redirect protection.'
  30. )
  31. v-divider.mt-3
  32. v-switch.mt-3(
  33. inset
  34. label='Block IFrame Embedding'
  35. color='red darken-2'
  36. v-model='config.securityIframe'
  37. persistent-hint
  38. hint='Prevents other websites from embedding your wiki in an iframe. This provides clickjacking protection.'
  39. )
  40. v-divider.mt-3
  41. v-switch(
  42. inset
  43. label='Same Origin Referrer Policy'
  44. color='red darken-2'
  45. v-model='config.securityReferrerPolicy'
  46. persistent-hint
  47. hint='Limits the referrer header to same origin.'
  48. )
  49. v-divider.mt-3
  50. v-switch(
  51. inset
  52. label='Trust X-Forwarded-* Proxy Headers'
  53. color='red darken-2'
  54. v-model='config.securityTrustProxy'
  55. persistent-hint
  56. hint='Should be enabled when using a reverse-proxy like nginx, apache, CloudFlare, etc in front of Wiki.js. Turn off otherwise.'
  57. )
  58. //- v-divider.mt-3
  59. //- v-switch(
  60. //- inset
  61. //- label='Subresource Integrity (SRI)'
  62. //- color='red darken-2'
  63. //- v-model='config.securitySRI'
  64. //- persistent-hint
  65. //- hint='This ensure that resources such as CSS and JS files are not altered during delivery.'
  66. //- disabled
  67. //- )
  68. v-divider.mt-3
  69. v-switch(
  70. inset
  71. label='Enforce HSTS'
  72. color='red darken-2'
  73. v-model='config.securityHSTS'
  74. persistent-hint
  75. hint='This ensures the connection cannot be established through an insecure HTTP connection.'
  76. )
  77. v-select.mt-5(
  78. outlined
  79. label='HSTS Max Age'
  80. :items='hstsDurations'
  81. v-model='config.securityHSTSDuration'
  82. prepend-icon='mdi-subdirectory-arrow-right'
  83. :disabled='!config.securityHSTS'
  84. hide-details
  85. style='max-width: 450px;'
  86. )
  87. .pl-11.mt-3
  88. .caption Defines the duration for which the server should only deliver content through HTTPS.
  89. .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.
  90. //- v-divider.mt-3
  91. //- v-switch(
  92. //- inset
  93. //- label='Enforce CSP'
  94. //- color='red darken-2'
  95. //- v-model='config.securityCSP'
  96. //- persistent-hint
  97. //- hint='Restricts scripts to pre-approved content sources.'
  98. //- disabled
  99. //- )
  100. //- v-textarea.mt-5(
  101. //- label='CSP Directives'
  102. //- outlined
  103. //- v-model='config.securityCSPDirectives'
  104. //- prepend-icon='mdi-subdirectory-arrow-right'
  105. //- persistent-hint
  106. //- hint='One directive per line.'
  107. //- disabled
  108. //- )
  109. v-flex(lg6 xs12)
  110. v-card.animated.fadeInUp.wait-p2s
  111. v-toolbar(color='primary', dark, dense, flat)
  112. v-toolbar-title.subtitle-1 {{ $t('admin:security.uploads') }}
  113. v-card-info(color='blue')
  114. span {{$t('admin:security.uploadsInfo')}}
  115. v-card-text
  116. v-text-field.mt-3(
  117. outlined
  118. :label='$t(`admin:security.maxUploadSize`)'
  119. required
  120. v-model='config.uploadMaxFileSize'
  121. prepend-icon='mdi-progress-upload'
  122. :hint='$t(`admin:security.maxUploadSizeHint`)'
  123. persistent-hint
  124. :suffix='$t(`admin:security.maxUploadSizeSuffix`)'
  125. style='max-width: 450px;'
  126. )
  127. v-text-field.mt-3(
  128. outlined
  129. :label='$t(`admin:security.maxUploadBatch`)'
  130. required
  131. v-model='config.uploadMaxFiles'
  132. prepend-icon='mdi-upload-lock'
  133. :hint='$t(`admin:security.maxUploadBatchHint`)'
  134. persistent-hint
  135. :suffix='$t(`admin:security.maxUploadBatchSuffix`)'
  136. style='max-width: 450px;'
  137. )
  138. v-divider.mt-3
  139. v-switch(
  140. inset
  141. label='Scan and Sanitize SVG Uploads'
  142. color='primary'
  143. v-model='config.uploadScanSVG'
  144. persistent-hint
  145. hint='Should SVG uploads be scanned for vulnerabilities and stripped of any potentially unsafe content.'
  146. )
  147. v-card.mt-3.animated.fadeInUp.wait-p2s
  148. v-toolbar(flat, color='primary', dark, dense)
  149. .subtitle-1 {{$t('admin:security.login')}}
  150. //- v-card-info(color='blue')
  151. //- span {{$t('admin:security.loginInfo')}}
  152. .overline.grey--text.pa-4 {{$t('admin:security.loginScreen')}}
  153. .px-4.pb-3
  154. v-text-field(
  155. outlined
  156. :label='$t(`admin:security.loginBgUrl`)'
  157. v-model='config.authLoginBgUrl'
  158. :hint='$t(`admin:security.loginBgUrlHint`)'
  159. persistent-hint
  160. prepend-icon='mdi-image-area'
  161. append-icon='mdi-folder-image'
  162. @click:append='browseLoginBg'
  163. )
  164. v-switch(
  165. inset
  166. :label='$t(`admin:security.bypassLogin`)'
  167. color='primary'
  168. v-model='config.authAutoLogin'
  169. prepend-icon='mdi-fast-forward'
  170. persistent-hint
  171. :hint='$t(`admin:security.bypassLoginHint`)'
  172. )
  173. v-switch(
  174. inset
  175. :label='$t(`admin:security.hideLocalLogin`)'
  176. color='primary'
  177. v-model='config.authHideLocal'
  178. prepend-icon='mdi-eye-off-outline'
  179. persistent-hint
  180. :hint='$t(`admin:security.hideLocalLoginHint`)'
  181. )
  182. v-divider.mt-3
  183. .overline.grey--text.pa-4 {{$t('admin:security.loginSecurity')}}
  184. .px-4.pb-3
  185. v-switch.mt-0(
  186. inset
  187. :label='$t(`admin:security.enforce2fa`)'
  188. color='primary'
  189. v-model='config.authEnforce2FA'
  190. prepend-icon='mdi-two-factor-authentication'
  191. :hint='$t(`admin:security.enforce2faHint`)'
  192. persistent-hint
  193. )
  194. v-divider.mt-3
  195. .overline.grey--text.pa-4 {{$t('admin:security.jwt')}}
  196. .px-4.pb-3
  197. v-text-field(
  198. v-model='config.authJwtAudience'
  199. outlined
  200. prepend-icon='mdi-account-group-outline'
  201. :label='$t(`admin:auth.jwtAudience`)'
  202. :hint='$t(`admin:auth.jwtAudienceHint`)'
  203. persistent-hint
  204. )
  205. v-text-field.mt-3(
  206. v-model='config.authJwtExpiration'
  207. outlined
  208. prepend-icon='mdi-clock-outline'
  209. :label='$t(`admin:auth.tokenExpiration`)'
  210. :hint='$t(`admin:auth.tokenExpirationHint`)'
  211. persistent-hint
  212. )
  213. v-text-field.mt-3(
  214. v-model='config.authJwtRenewablePeriod'
  215. outlined
  216. prepend-icon='mdi-update'
  217. :label='$t(`admin:auth.tokenRenewalPeriod`)'
  218. :hint='$t(`admin:auth.tokenRenewalPeriodHint`)'
  219. persistent-hint
  220. )
  221. component(:is='activeModal')
  222. </template>
  223. <script>
  224. import _ from 'lodash'
  225. import { sync } from 'vuex-pathify'
  226. import gql from 'graphql-tag'
  227. import editorStore from '../../store/editor'
  228. /* global WIKI */
  229. WIKI.$store.registerModule('editor', editorStore)
  230. export default {
  231. i18nOptions: { namespaces: 'editor' },
  232. components: {
  233. editorModalMedia: () => import(/* webpackChunkName: "editor", webpackMode: "lazy" */ '../editor/editor-modal-media.vue')
  234. },
  235. data() {
  236. return {
  237. config: {
  238. uploadMaxFileSize: 0,
  239. uploadMaxFiles: 0,
  240. uploadScanSVG: true,
  241. securityOpenRedirect: true,
  242. securityIframe: true,
  243. securityReferrerPolicy: true,
  244. securityTrustProxy: true,
  245. securitySRI: true,
  246. securityHSTS: false,
  247. securityHSTSDuration: 0,
  248. securityCSP: false,
  249. securityCSPDirectives: '',
  250. authAutoLogin: false,
  251. authHideLocal: false,
  252. authLoginBgUrl: '',
  253. authJwtAudience: 'urn:wiki.js',
  254. authJwtExpiration: '30m',
  255. authJwtRenewablePeriod: '14d'
  256. },
  257. hstsDurations: [
  258. { value: 300, text: '5 minutes' },
  259. { value: 86400, text: '1 day' },
  260. { value: 604800, text: '1 week' },
  261. { value: 2592000, text: '1 month' },
  262. { value: 31536000, text: '1 year' },
  263. { value: 63072000, text: '2 years' }
  264. ]
  265. }
  266. },
  267. computed: {
  268. activeModal: sync('editor/activeModal')
  269. },
  270. methods: {
  271. async save () {
  272. try {
  273. await this.$apollo.mutate({
  274. mutation: gql`
  275. mutation (
  276. $authAutoLogin: Boolean
  277. $authEnforce2FA: Boolean
  278. $authHideLocal: Boolean
  279. $authLoginBgUrl: String
  280. $authJwtAudience: String
  281. $authJwtExpiration: String
  282. $authJwtRenewablePeriod: String
  283. $uploadMaxFileSize: Int
  284. $uploadMaxFiles: Int
  285. $uploadScanSVG: Boolean
  286. $securityOpenRedirect: Boolean
  287. $securityIframe: Boolean
  288. $securityReferrerPolicy: Boolean
  289. $securityTrustProxy: Boolean
  290. $securitySRI: Boolean
  291. $securityHSTS: Boolean
  292. $securityHSTSDuration: Int
  293. $securityCSP: Boolean
  294. $securityCSPDirectives: String
  295. ) {
  296. site {
  297. updateConfig(
  298. authAutoLogin: $authAutoLogin,
  299. authEnforce2FA: $authEnforce2FA,
  300. authHideLocal: $authHideLocal,
  301. authLoginBgUrl: $authLoginBgUrl,
  302. authJwtAudience: $authJwtAudience,
  303. authJwtExpiration: $authJwtExpiration,
  304. authJwtRenewablePeriod: $authJwtRenewablePeriod,
  305. uploadMaxFileSize: $uploadMaxFileSize,
  306. uploadMaxFiles: $uploadMaxFiles,
  307. uploadScanSVG: $uploadScanSVG
  308. securityOpenRedirect: $securityOpenRedirect,
  309. securityIframe: $securityIframe,
  310. securityReferrerPolicy: $securityReferrerPolicy,
  311. securityTrustProxy: $securityTrustProxy,
  312. securitySRI: $securitySRI,
  313. securityHSTS: $securityHSTS,
  314. securityHSTSDuration: $securityHSTSDuration,
  315. securityCSP: $securityCSP,
  316. securityCSPDirectives: $securityCSPDirectives
  317. ) {
  318. responseResult {
  319. succeeded
  320. errorCode
  321. slug
  322. message
  323. }
  324. }
  325. }
  326. }
  327. `,
  328. variables: {
  329. authAutoLogin: _.get(this.config, 'authAutoLogin', false),
  330. authEnforce2FA: _.get(this.config, 'authEnforce2FA', false),
  331. authHideLocal: _.get(this.config, 'authHideLocal', false),
  332. authLoginBgUrl: _.get(this.config, 'authLoginBgUrl', ''),
  333. authJwtAudience: _.get(this.config, 'authJwtAudience', ''),
  334. authJwtExpiration: _.get(this.config, 'authJwtExpiration', ''),
  335. authJwtRenewablePeriod: _.get(this.config, 'authJwtRenewablePeriod', ''),
  336. uploadMaxFileSize: _.toSafeInteger(_.get(this.config, 'uploadMaxFileSize', 0)),
  337. uploadMaxFiles: _.toSafeInteger(_.get(this.config, 'uploadMaxFiles', 0)),
  338. uploadScanSVG: _.get(this.config, 'uploadScanSVG', false),
  339. securityOpenRedirect: _.get(this.config, 'securityOpenRedirect', false),
  340. securityIframe: _.get(this.config, 'securityIframe', false),
  341. securityReferrerPolicy: _.get(this.config, 'securityReferrerPolicy', false),
  342. securityTrustProxy: _.get(this.config, 'securityTrustProxy', false),
  343. securitySRI: _.get(this.config, 'securitySRI', false),
  344. securityHSTS: _.get(this.config, 'securityHSTS', false),
  345. securityHSTSDuration: _.get(this.config, 'securityHSTSDuration', 0),
  346. securityCSP: _.get(this.config, 'securityCSP', false),
  347. securityCSPDirectives: _.get(this.config, 'securityCSPDirectives', '')
  348. },
  349. watchLoading (isLoading) {
  350. this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'admin-site-update')
  351. }
  352. })
  353. this.$store.commit('showNotification', {
  354. style: 'success',
  355. message: 'Configuration saved successfully.',
  356. icon: 'check'
  357. })
  358. } catch (err) {
  359. this.$store.commit('pushGraphError', err)
  360. }
  361. },
  362. browseLoginBg () {
  363. this.$store.set('editor/editorKey', 'common')
  364. this.activeModal = 'editorModalMedia'
  365. }
  366. },
  367. mounted () {
  368. this.$root.$on('editorInsert', opts => {
  369. this.config.authLoginBgUrl = opts.path
  370. })
  371. },
  372. beforeDestroy() {
  373. this.$root.$off('editorInsert')
  374. },
  375. apollo: {
  376. config: {
  377. query: gql`
  378. {
  379. site {
  380. config {
  381. authAutoLogin
  382. authEnforce2FA
  383. authHideLocal
  384. authLoginBgUrl
  385. authJwtAudience
  386. authJwtExpiration
  387. authJwtRenewablePeriod
  388. uploadMaxFileSize
  389. uploadMaxFiles
  390. uploadScanSVG
  391. securityOpenRedirect
  392. securityIframe
  393. securityReferrerPolicy
  394. securityTrustProxy
  395. securitySRI
  396. securityHSTS
  397. securityHSTSDuration
  398. securityCSP
  399. securityCSPDirectives
  400. }
  401. }
  402. }
  403. `,
  404. fetchPolicy: 'network-only',
  405. update: (data) => _.cloneDeep(data.site.config),
  406. watchLoading (isLoading) {
  407. this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'admin-security-refresh')
  408. }
  409. }
  410. }
  411. }
  412. </script>
  413. <style lang='scss'>
  414. </style>