admin-security.vue 15 KB

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