You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

416 lines
16 KiB

  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-card.mt-3.animated.fadeInUp.wait-p2s
  139. v-toolbar(flat, color='primary', dark, dense)
  140. .subtitle-1 {{$t('admin:security.login')}}
  141. //- v-card-info(color='blue')
  142. //- span {{$t('admin:security.loginInfo')}}
  143. .overline.grey--text.pa-4 {{$t('admin:security.loginScreen')}}
  144. .px-4.pb-3
  145. v-text-field(
  146. outlined
  147. :label='$t(`admin:security.loginBgUrl`)'
  148. v-model='config.authLoginBgUrl'
  149. :hint='$t(`admin:security.loginBgUrlHint`)'
  150. persistent-hint
  151. prepend-icon='mdi-image-area'
  152. append-icon='mdi-folder-image'
  153. @click:append='browseLoginBg'
  154. )
  155. v-switch(
  156. inset
  157. :label='$t(`admin:security.bypassLogin`)'
  158. color='primary'
  159. v-model='config.authAutoLogin'
  160. prepend-icon='mdi-fast-forward'
  161. persistent-hint
  162. :hint='$t(`admin:security.bypassLoginHint`)'
  163. )
  164. v-switch(
  165. inset
  166. :label='$t(`admin:security.hideLocalLogin`)'
  167. color='primary'
  168. v-model='config.authHideLocal'
  169. prepend-icon='mdi-eye-off-outline'
  170. persistent-hint
  171. :hint='$t(`admin:security.hideLocalLoginHint`)'
  172. )
  173. v-divider.mt-3
  174. .overline.grey--text.pa-4 {{$t('admin:security.loginSecurity')}}
  175. .px-4.pb-3
  176. v-switch.mt-0(
  177. inset
  178. :label='$t(`admin:security.enforce2fa`)'
  179. color='primary'
  180. v-model='config.authEnforce2FA'
  181. prepend-icon='mdi-two-factor-authentication'
  182. :hint='$t(`admin:security.enforce2faHint`)'
  183. persistent-hint
  184. )
  185. v-divider.mt-3
  186. .overline.grey--text.pa-4 {{$t('admin:security.jwt')}}
  187. .px-4.pb-3
  188. v-text-field(
  189. v-model='config.authJwtAudience'
  190. outlined
  191. prepend-icon='mdi-account-group-outline'
  192. :label='$t(`admin:auth.jwtAudience`)'
  193. :hint='$t(`admin:auth.jwtAudienceHint`)'
  194. persistent-hint
  195. )
  196. v-text-field.mt-3(
  197. v-model='config.authJwtExpiration'
  198. outlined
  199. prepend-icon='mdi-clock-outline'
  200. :label='$t(`admin:auth.tokenExpiration`)'
  201. :hint='$t(`admin:auth.tokenExpirationHint`)'
  202. persistent-hint
  203. )
  204. v-text-field.mt-3(
  205. v-model='config.authJwtRenewablePeriod'
  206. outlined
  207. prepend-icon='mdi-update'
  208. :label='$t(`admin:auth.tokenRenewalPeriod`)'
  209. :hint='$t(`admin:auth.tokenRenewalPeriodHint`)'
  210. persistent-hint
  211. )
  212. component(:is='activeModal')
  213. </template>
  214. <script>
  215. import _ from 'lodash'
  216. import { sync } from 'vuex-pathify'
  217. import gql from 'graphql-tag'
  218. import editorStore from '../../store/editor'
  219. /* global WIKI */
  220. WIKI.$store.registerModule('editor', editorStore)
  221. export default {
  222. i18nOptions: { namespaces: 'editor' },
  223. components: {
  224. editorModalMedia: () => import(/* webpackChunkName: "editor", webpackMode: "lazy" */ '../editor/editor-modal-media.vue')
  225. },
  226. data() {
  227. return {
  228. config: {
  229. uploadMaxFileSize: 0,
  230. uploadMaxFiles: 0,
  231. securityOpenRedirect: true,
  232. securityIframe: true,
  233. securityReferrerPolicy: true,
  234. securityTrustProxy: true,
  235. securitySRI: true,
  236. securityHSTS: false,
  237. securityHSTSDuration: 0,
  238. securityCSP: false,
  239. securityCSPDirectives: '',
  240. authAutoLogin: false,
  241. authHideLocal: false,
  242. authLoginBgUrl: '',
  243. authJwtAudience: 'urn:wiki.js',
  244. authJwtExpiration: '30m',
  245. authJwtRenewablePeriod: '14d'
  246. },
  247. hstsDurations: [
  248. { value: 300, text: '5 minutes' },
  249. { value: 86400, text: '1 day' },
  250. { value: 604800, text: '1 week' },
  251. { value: 2592000, text: '1 month' },
  252. { value: 31536000, text: '1 year' },
  253. { value: 63072000, text: '2 years' }
  254. ]
  255. }
  256. },
  257. computed: {
  258. activeModal: sync('editor/activeModal')
  259. },
  260. methods: {
  261. async save () {
  262. try {
  263. await this.$apollo.mutate({
  264. mutation: gql`
  265. mutation (
  266. $authAutoLogin: Boolean
  267. $authEnforce2FA: Boolean
  268. $authHideLocal: Boolean
  269. $authLoginBgUrl: String
  270. $authJwtAudience: String
  271. $authJwtExpiration: String
  272. $authJwtRenewablePeriod: String
  273. $uploadMaxFileSize: Int
  274. $uploadMaxFiles: Int
  275. $securityOpenRedirect: Boolean
  276. $securityIframe: Boolean
  277. $securityReferrerPolicy: Boolean
  278. $securityTrustProxy: Boolean
  279. $securitySRI: Boolean
  280. $securityHSTS: Boolean
  281. $securityHSTSDuration: Int
  282. $securityCSP: Boolean
  283. $securityCSPDirectives: String
  284. ) {
  285. site {
  286. updateConfig(
  287. authAutoLogin: $authAutoLogin,
  288. authEnforce2FA: $authEnforce2FA,
  289. authHideLocal: $authHideLocal,
  290. authLoginBgUrl: $authLoginBgUrl,
  291. authJwtAudience: $authJwtAudience,
  292. authJwtExpiration: $authJwtExpiration,
  293. authJwtRenewablePeriod: $authJwtRenewablePeriod,
  294. uploadMaxFileSize: $uploadMaxFileSize,
  295. uploadMaxFiles: $uploadMaxFiles,
  296. securityOpenRedirect: $securityOpenRedirect,
  297. securityIframe: $securityIframe,
  298. securityReferrerPolicy: $securityReferrerPolicy,
  299. securityTrustProxy: $securityTrustProxy,
  300. securitySRI: $securitySRI,
  301. securityHSTS: $securityHSTS,
  302. securityHSTSDuration: $securityHSTSDuration,
  303. securityCSP: $securityCSP,
  304. securityCSPDirectives: $securityCSPDirectives
  305. ) {
  306. responseResult {
  307. succeeded
  308. errorCode
  309. slug
  310. message
  311. }
  312. }
  313. }
  314. }
  315. `,
  316. variables: {
  317. authAutoLogin: _.get(this.config, 'authAutoLogin', false),
  318. authEnforce2FA: _.get(this.config, 'authEnforce2FA', false),
  319. authHideLocal: _.get(this.config, 'authHideLocal', false),
  320. authLoginBgUrl: _.get(this.config, 'authLoginBgUrl', ''),
  321. authJwtAudience: _.get(this.config, 'authJwtAudience', ''),
  322. authJwtExpiration: _.get(this.config, 'authJwtExpiration', ''),
  323. authJwtRenewablePeriod: _.get(this.config, 'authJwtRenewablePeriod', ''),
  324. uploadMaxFileSize: _.toSafeInteger(_.get(this.config, 'uploadMaxFileSize', 0)),
  325. uploadMaxFiles: _.toSafeInteger(_.get(this.config, 'uploadMaxFiles', 0)),
  326. securityOpenRedirect: _.get(this.config, 'securityOpenRedirect', false),
  327. securityIframe: _.get(this.config, 'securityIframe', false),
  328. securityReferrerPolicy: _.get(this.config, 'securityReferrerPolicy', false),
  329. securityTrustProxy: _.get(this.config, 'securityTrustProxy', false),
  330. securitySRI: _.get(this.config, 'securitySRI', false),
  331. securityHSTS: _.get(this.config, 'securityHSTS', false),
  332. securityHSTSDuration: _.get(this.config, 'securityHSTSDuration', 0),
  333. securityCSP: _.get(this.config, 'securityCSP', false),
  334. securityCSPDirectives: _.get(this.config, 'securityCSPDirectives', '')
  335. },
  336. watchLoading (isLoading) {
  337. this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'admin-site-update')
  338. }
  339. })
  340. this.$store.commit('showNotification', {
  341. style: 'success',
  342. message: 'Configuration saved successfully.',
  343. icon: 'check'
  344. })
  345. } catch (err) {
  346. this.$store.commit('pushGraphError', err)
  347. }
  348. },
  349. browseLoginBg () {
  350. this.$store.set('editor/editorKey', 'common')
  351. this.activeModal = 'editorModalMedia'
  352. }
  353. },
  354. mounted () {
  355. this.$root.$on('editorInsert', opts => {
  356. this.config.authLoginBgUrl = opts.path
  357. })
  358. },
  359. beforeDestroy() {
  360. this.$root.$off('editorInsert')
  361. },
  362. apollo: {
  363. config: {
  364. query: gql`
  365. {
  366. site {
  367. config {
  368. authAutoLogin
  369. authEnforce2FA
  370. authHideLocal
  371. authLoginBgUrl
  372. authJwtAudience
  373. authJwtExpiration
  374. authJwtRenewablePeriod
  375. uploadMaxFileSize
  376. uploadMaxFiles
  377. securityOpenRedirect
  378. securityIframe
  379. securityReferrerPolicy
  380. securityTrustProxy
  381. securitySRI
  382. securityHSTS
  383. securityHSTSDuration
  384. securityCSP
  385. securityCSPDirectives
  386. }
  387. }
  388. }
  389. `,
  390. fetchPolicy: 'network-only',
  391. update: (data) => _.cloneDeep(data.site.config),
  392. watchLoading (isLoading) {
  393. this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'admin-security-refresh')
  394. }
  395. }
  396. }
  397. }
  398. </script>
  399. <style lang='scss'>
  400. </style>