admin-security.vue 15 KB

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