2
0
Эх сурвалжийг харах

feat: utilities section (wip) + auth utilities

Nick 6 жил өмнө
parent
commit
604941fe6a

+ 2 - 2
client/components/admin.vue

@@ -83,8 +83,8 @@
             v-list-tile(to='/system', v-if='hasPermission(`manage:system`)')
               v-list-tile-avatar: v-icon tune
               v-list-tile-title {{ $t('admin:system.title') }}
-            v-list-tile(to='/utilities', v-if='hasPermission(`manage:system`)', disabled)
-              v-list-tile-avatar: v-icon(color='grey lighten-2') build
+            v-list-tile(to='/utilities', v-if='hasPermission(`manage:system`)')
+              v-list-tile-avatar: v-icon build
               v-list-tile-title {{ $t('admin:utilities.title') }}
             v-list-tile(to='/webhooks', v-if='hasPermission(`manage:system`)', disabled)
               v-list-tile-avatar: v-icon(color='grey lighten-2') ac_unit

+ 93 - 0
client/components/admin/admin-utilities-auth.vue

@@ -0,0 +1,93 @@
+<template lang='pug'>
+  v-card
+    v-toolbar(flat, color='primary', dark, dense)
+      .subheading {{ $t('admin:utilities.authTitle') }}
+    v-card-text
+      v-subheader.pl-0 Generate New Authentication Public / Private Key Certificates
+      .body-1 This will invalidate all current session tokens and cause all users to be logged out.
+      .body-1.red--text You will need to log back in after the operation.
+      v-btn(outline, color='primary', @click='regenCerts', :disabled='loading').ml-0.mt-3
+        v-icon(left) build
+        span Proceed
+      v-divider.my-3
+      v-subheader.pl-0 Reset Guest User
+      .body-1 This will reset the guest user to its default parameters and permissions.
+      v-btn(outline, color='primary', @click='resetGuest', :disabled='loading').ml-0.mt-3
+        v-icon(left) build
+        span Proceed
+</template>
+
+<script>
+import _ from 'lodash'
+import Cookies from 'js-cookie'
+import utilityAuthRegencertsMutation from 'gql/admin/utilities/utilities-mutation-auth-regencerts.gql'
+import utilityAuthResetguestMutation from 'gql/admin/utilities/utilities-mutation-auth-resetguest.gql'
+
+export default {
+  data: () => {
+    return {
+      loading: false
+    }
+  },
+  methods: {
+    async regenCerts() {
+      this.loading = true
+      this.$store.commit(`loadingStart`, 'admin-utilities-auth-regencerts')
+
+      try {
+        const respRaw = await this.$apollo.mutate({
+          mutation: utilityAuthRegencertsMutation
+        })
+        const resp = _.get(respRaw, 'data.authentication.regenerateCertificates.responseResult', {})
+        if (resp.succeeded) {
+          this.$store.commit('showNotification', {
+            message: 'New Certificates generated successfully.',
+            style: 'success',
+            icon: 'check'
+          })
+          Cookies.remove('jwt')
+          _.delay(() => {
+            window.location.assign('/login')
+          }, 1000)
+        } else {
+          throw new Error(resp.message)
+        }
+      } catch (err) {
+        this.$store.commit('pushGraphError', err)
+      }
+
+      this.$store.commit(`loadingStop`, 'admin-utilities-auth-regencerts')
+      this.loading = false
+    },
+    async resetGuest() {
+      this.loading = true
+      this.$store.commit(`loadingStart`, 'admin-utilities-auth-resetguest')
+
+      try {
+        const respRaw = await this.$apollo.mutate({
+          mutation: utilityAuthResetguestMutation
+        })
+        const resp = _.get(respRaw, 'data.authentication.resetGuestUser.responseResult', {})
+        if (resp.succeeded) {
+          this.$store.commit('showNotification', {
+            message: 'Guest user was reset successfully.',
+            style: 'success',
+            icon: 'check'
+          })
+        } else {
+          throw new Error(resp.message)
+        }
+      } catch (err) {
+        this.$store.commit('pushGraphError', err)
+      }
+
+      this.$store.commit(`loadingStop`, 'admin-utilities-auth-resetguest')
+      this.loading = false
+    }
+  }
+}
+</script>
+
+<style lang='scss'>
+
+</style>

+ 32 - 0
client/components/admin/admin-utilities-cache.vue

@@ -0,0 +1,32 @@
+<template lang='pug'>
+  v-card
+    v-toolbar(flat, color='primary', dark, dense)
+      .subheading {{ $t('admin:utilities.cacheTitle') }}
+    v-card-text
+      v-subheader.pl-0 Flush Pages Cache
+      .body-1 Pages are cached to disk for better performance. You can flush the cache to force all content to be fetched from the DB again.
+      v-btn(outline, color='primary').ml-0.mt-3
+        v-icon(left) build
+        span Proceed
+      v-divider.my-3
+      v-subheader.pl-0 Flush Assets Cache
+      .body-1 Assets such as images and other files are cached to disk for better performance. You can flush the cache to force all assets to be fetched from the DB again.
+      v-btn(outline, color='primary').ml-0.mt-3
+        v-icon(left) build
+        span Proceed
+      v-divider.my-3
+      v-subheader.pl-0 Flush Temporary Uploads
+      .body-1 New uploads are temporarily saved to disk while they are being processed. They are automatically deleted after processing, but you can force an immediate cleanup using this tool.
+      v-btn(outline, color='primary').ml-0.mt-3
+        v-icon(left) build
+        span Proceed
+</template>
+
+<script>
+export default {
+}
+</script>
+
+<style lang='scss'>
+
+</style>

+ 77 - 0
client/components/admin/admin-utilities-importv1.vue

@@ -0,0 +1,77 @@
+<template lang='pug'>
+  v-card.wiki-form
+    v-toolbar(flat, color='primary', dark, dense)
+      .subheading {{ $t('admin:utilities.importv1Title') }}
+    v-card-text
+      .text-xs-center
+        img.animated.fadeInUp.wait-p1s(src='/svg/icon-software.svg')
+        .body-2 Import from Wiki.js 1.x
+      v-divider.my-4
+      .body-1 Data from a Wiki.js 1.x installation can be imported easily using this tool. What do you want to import?
+      v-checkbox(
+        label='Content'
+        value='content'
+        color='deep-orange darken-2'
+        v-model='importFilters'
+        hide-details
+      )
+      v-checkbox(
+        label='Uploads'
+        value='uploads'
+        color='deep-orange darken-2'
+        v-model='importFilters'
+        hide-details
+      )
+      v-checkbox(
+        label='Users'
+        value='users'
+        color='deep-orange darken-2'
+        v-model='importFilters'
+        hide-details
+      )
+      v-divider.my-3
+      v-text-field.mt-3(
+        outline
+        label='MongoDB Connection String'
+        hint='The connection string to connect to the Wiki.js 1.x MongoDB database.'
+        persistent-hint
+        v-model='dbConnStr'
+        v-if='needDB'
+      )
+      v-text-field.mt-3(
+        outline
+        label='Content Repo Path'
+        hint='The full path to where the Wiki.js 1.x content is stored on disk.'
+        persistent-hint
+        v-model='contentPath'
+        v-if='needDisk'
+      )
+    v-card-chin
+      v-btn(depressed, color='deep-orange darken-2', :disabled='!needDB && !needDisk').ml-0
+        v-icon(left, color='white') label_important
+        span.white--text Start Import
+</template>
+
+<script>
+export default {
+  data() {
+    return {
+      importFilters: ['content', 'uploads'],
+      dbConnStr: 'mongodb://',
+      contentPath: '/wiki-v1/repo'
+    }
+  },
+  computed: {
+    needDB() {
+      return this.importFilters.indexOf('users') >= 0
+    },
+    needDisk() {
+      return this.importFilters.indexOf('content') >= 0 || this.importFilters.indexOf('uploads') >= 0
+    }
+  }
+}
+</script>
+
+<style lang='scss'>
+
+</style>

+ 80 - 0
client/components/admin/admin-utilities-telemetry.vue

@@ -0,0 +1,80 @@
+<template lang='pug'>
+  v-card
+    v-toolbar(flat, color='primary', dark, dense)
+      .subheading {{ $t('admin:utilities.telemetryTitle') }}
+    v-form
+      v-card-text
+        v-subheader What is telemetry?
+        .body-1.pl-3 Telemetry allows the developers of Wiki.js to improve the software by collecting basic anonymized data about its usage and the host info. #[br] This is entirely optional and #[strong absolutely no] private data (such as content or personal data) is collected.
+        .body-1.pt-3.pl-3 For maximum privacy, a random client ID is generated during setup. This ID is used to group requests together while keeping complete anonymity. You can reset and generate a new one below at any time.
+        v-divider.my-3
+        v-subheader What is collected?
+        .body-1.pl-3 When telemetry is enabled, only the following data is transmitted:
+        v-list
+          v-list-tile
+            v-list-tile-avatar: v-icon info_outline
+            v-list-tile-content
+              v-list-tile-title.body-1 Version of Wiki.js installed
+              v-list-tile-subtitle.caption: em e.g. v2.0.123
+          v-list-tile
+            v-list-tile-avatar: v-icon info_outline
+            v-list-tile-content
+              v-list-tile-title.body-1 Basic OS information
+              v-list-tile-subtitle.caption: em Platform (Linux, macOS or Windows), Total CPU cores and DB type (PostgreSQL, MySQL, MariaDB, SQLite or SQL Server)
+          v-list-tile
+            v-list-tile-avatar: v-icon info_outline
+            v-list-tile-content
+              v-list-tile-title.body-1 Crash debug data
+              v-list-tile-subtitle.caption: em Stack trace of the error
+          v-list-tile
+            v-list-tile-avatar: v-icon info_outline
+            v-list-tile-content
+              v-list-tile-title.body-1 Setup analytics
+              v-list-tile-subtitle.caption: em Installation checkpoint reached
+        .body-1.pl-3 Note that any data collected is stored for a maximum of 180 days, after which it is permanently deleted.
+        v-divider.my-3
+        v-subheader What is it used for?
+        .body-1.pl-3 Telemetry is used by developers to improve Wiki.js, mostly for the following reasons:
+        v-list(dense)
+          v-list-tile
+            v-list-tile-avatar: v-icon info_outline
+            v-list-tile-content: v-list-tile-title.body-1 Identify critical bugs more easily and fix them in a timely manner.
+          v-list-tile
+            v-list-tile-avatar: v-icon info_outline
+            v-list-tile-content: v-list-tile-title.body-1 Understand the upgrade rate of current installations.
+          v-list-tile
+            v-list-tile-avatar: v-icon info_outline
+            v-list-tile-content: v-list-tile-title.body-1  Optimize performance and testing scenarios based on most popular environments.
+        .body-1.pl-3 Only authorized developers have access to the data. It is not shared to any 3rd party nor is it used for any other application than improving Wiki.js.
+        v-divider.my-3
+        v-subheader Settings
+        .pl-3
+          v-switch.mt-0(
+            v-model='telemetry',
+            label='Enable Telemetry',
+            :value='true',
+            color='primary',
+            hint='Allow Wiki.js to transmit telemetry data.',
+            persistent-hint
+          )
+          .subheading.mt-3.grey--text.text--darken-1 Client ID
+          .body-1 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+      v-card-chin
+        v-btn(depressed, color='success', @click='updateTelemetry')
+          v-icon(left) chevron_right
+          | Save Changes
+        v-spacer
+        v-btn(outline, color='grey', @click='resetClientId')
+          v-icon(left) autorenew
+          span Reset Client ID
+
+</template>
+
+<script>
+export default {
+}
+</script>
+
+<style lang='scss'>
+
+</style>

+ 57 - 178
client/components/admin/admin-utilities.vue

@@ -5,196 +5,75 @@
         .admin-header
           img(src='/svg/icon-maintenance.svg', alt='Utilities', style='width: 80px;')
           .admin-header-title
-            .headline.primary--text Utilities
-            .subheading.grey--text Maintenance and troubleshooting tools
+            .headline.primary--text {{$t('admin:utilities.title')}}
+            .subheading.grey--text {{$t('admin:utilities.subtitle')}}
 
-        v-card.mt-3
-          v-tabs(color='grey darken-2', fixed-tabs, slider-color='white', show-arrows, dark)
-            v-tab(key='tools') Tools
-            v-tab(key='cache') Cache
-            v-tab(key='telemetry') Telemetry
-            v-tab(key='support') Support
+      v-flex(lg3, xs12)
+        v-card.animated.fadeInUp
+          v-toolbar(flat, color='primary', dark, dense)
+            .subheading {{$t('admin:utilities.tools')}}
+          v-list(two-line, dense).py-0
+            template(v-for='(tool, idx) in tools')
+              v-list-tile(:key='tool.key', @click='selectedTool = tool.key', :disabled='!tool.isAvailable')
+                v-list-tile-avatar
+                  v-icon(:color='!tool.isAvailable ? `grey lighten-1` : (selectedTool === tool.key ? `blue ` : `grey darken-1`)') {{ tool.icon }}
+                v-list-tile-content
+                  v-list-tile-title.body-2(:class='!tool.isAvailable ? `grey--text` : (selectedTool === tool.key ? `primary--text` : ``)') {{ $t('admin:utilities.' + tool.i18nKey + 'Title') }}
+                  v-list-tile-sub-title.caption(:class='!tool.isAvailable ? `grey--text text--lighten-1` : (selectedTool === tool.key ? `blue--text ` : ``)') {{ $t('admin:utilities.' + tool.i18nKey + 'Subtitle') }}
+                v-list-tile-avatar(v-if='selectedTool === tool.key')
+                  v-icon.animated.fadeInLeft(color='primary') arrow_forward_ios
+              v-divider(v-if='idx < tools.length - 1')
 
-            v-tab-item(key='tools', :transition='false', :reverse-transition='false')
-              v-container.pa-2(fluid, grid-list-sm, :class='$vuetify.dark ? "" : "grey lighten-5"')
-                v-layout(row, wrap)
-                  v-flex(xs12, sm6)
-                    v-card
-                      v-toolbar(:color='$vuetify.dark ? "" : "grey darken-3"', dark, dense, flat)
-                        v-toolbar-title
-                          .subheading Authentication
-                        v-spacer
-                        v-chip(label, color='white', small).grey--text.text--darken-2 coming soon
-                      v-subheader Flush User Sessions
-                      v-card-text.pt-0.pl-4
-                        .body-1 This will cause all users to be logged out. You will need to log back in after the operation.
-                        v-btn(depressed).ml-0
-                          v-icon(left, color='grey') build
-                          span Proceed
-                      v-divider.my-0
-                      v-subheader Reset Guest User
-                      v-card-text.pt-0.pl-4
-                        .body-1 This will reset the guest user to its default parameters and permissions.
-                        v-btn(depressed).ml-0
-                          v-icon(left, color='grey') build
-                          span Proceed
-                    v-card.mt-3
-                      v-toolbar(:color='$vuetify.dark ? "" : "grey darken-3"', dark, dense, flat)
-                        v-toolbar-title
-                          .subheading Modules
-                        v-spacer
-                        v-chip(label, color='white', small).grey--text.text--darken-2 coming soon
-                      v-subheader Rescan Modules
-                      v-card-text.pt-0.pl-4
-                        .body-1 Look for new modules on disk. Existing configurations will be merged.
-                        v-select.mt-3(
-                          v-model='rescanModuleType'
-                          :items='moduleTypes'
-                          label='Modules Type'
-                          outline
-                          background-color='grey lighten-1'
-                          hide-details
-                          dense
-                        )
-                        v-btn.ml-0(color='primary', depressed, dark)
-                          v-icon(left) chevron_right
-                          span Rescan
-                  v-flex(xs12, sm6)
-                    v-card
-                      v-toolbar(:color='$vuetify.dark ? "" : "grey darken-3"', dark, dense, flat)
-                        v-toolbar-title
-                          .subheading Maintenance Mode
-                        v-spacer
-                        v-chip(label, color='white', small).grey--text.text--darken-2 coming soon
-                      v-card-text
-                        .body-1 Maintenance mode restrict access to the site to administrators only, regarless of current permissions.
-                        v-btn.mt-3.ml-0(color='orange darken-2', depressed, dark)
-                          icon-home-alert.mr-2(fillColor='#FFFFFF')
-                          | Turn On Maintenance Mode
-                    v-card.mt-3
-                      v-toolbar(:color='$vuetify.dark ? "" : "grey darken-3"', dark, dense, flat)
-                        v-toolbar-title
-                          .subheading Graph Endpoint
-                        v-spacer
-                        v-chip(label, color='white', small).grey--text.text--darken-2 coming soon
-                      v-card-text
-                        .body-1 The Graph API Endpoint from which remote resources like locales, themes and plugins are fetched.
-                        .caption.red--text Do not change unless you know what you're doing!
-                        v-text-field.my-2(outline, hide-details, background-color='grey lighten-1', label='Graph Endpoint', value='https://graph.requarks.io')
-                        v-btn.ml-0(color='primary', depressed, dark)
-                          v-icon(left) chevron_right
-                          span Save
-
-            v-tab-item(key='cache', :transition='false', :reverse-transition='false')
-              v-card
-                v-card-text Coming soon
-
-            v-tab-item(key='telemetry', :transition='false', :reverse-transition='false')
-              v-card
-                v-form
-                  v-card-text
-                    v-subheader What is telemetry?
-                    .body-1.pl-3 Telemetry allows the developers of Wiki.js to improve the software by collecting basic anonymized data about its usage and the host info. #[br] This is entirely optional and #[strong absolutely no] private data (such as content or personal data) is collected.
-                    .body-1.pt-3.pl-3 For maximum privacy, a random client ID is generated during setup. This ID is used to group requests together while keeping complete anonymity. You can reset and generate a new one below at any time.
-                    v-divider.my-3
-                    v-subheader What is collected?
-                    .body-1.pl-3 When telemetry is enabled, only the following data is transmitted:
-                    v-list(dense)
-                      v-list-tile
-                        v-list-tile-avatar: v-icon info_outline
-                        v-list-tile-content: v-list-tile-title.caption Version of Wiki.js installed
-                      v-list-tile
-                        v-list-tile-avatar: v-icon info_outline
-                        v-list-tile-content: v-list-tile-title.caption Basic OS information (platform, CPU cores count, DB type)
-                      v-list-tile
-                        v-list-tile-avatar: v-icon info_outline
-                        v-list-tile-content: v-list-tile-title.caption Crash debug data
-                      v-list-tile
-                        v-list-tile-avatar: v-icon info_outline
-                        v-list-tile-content: v-list-tile-title.caption Setup analytics (installation checkpoint reached)
-                    .body-2.pl-3
-                    v-divider.my-3
-                    v-subheader Settings
-                    .pl-3
-                      v-switch.mt-0(
-                        v-model='telemetry',
-                        label='Enable Telemetry',
-                        :value='true',
-                        color='primary',
-                        hint='Allow Wiki.js to transmit telemetry data.',
-                        persistent-hint
-                      )
-                      .subheading.mt-3.grey--text.text--darken-1 Client ID
-                      .body-1 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
-                  v-card-chin
-                    v-btn(color='primary', @click='updateTelemetry')
-                      v-icon(left) chevron_right
-                      | Save Changes
-                    v-spacer
-                    v-btn(outline, color='grey', @click='resetClientId')
-                      v-icon(left) autorenew
-                      span Reset Client ID
-
-            v-tab-item(key='support', :transition='false', :reverse-transition='false')
-              v-card.pa-3
-                v-subheader Report a bug
-                .body-1.pl-3 Bugs can be reported using GitHub issues on the project repository page.
-                v-btn.ml-3.mt-3(depressed, dark, color='grey darken-2', href='https://github.com/Requarks/wiki/issues', target='_blank')
-                  icon-github-circle.mr-3(fillColor='#FFFFFF')
-                  span Submit an issue
-                v-divider.my-3
-                v-subheader Suggest a New Feature / Enhancement
-                .body-1.pl-3 Have an idea for a new feature or something that could be improved?
-                v-btn.ml-3.mt-3(depressed, dark, color='indigo', href='https://requests.requarks.io/wiki', target='_blank')
-                  v-icon(left) lightbulb_outline
-                  span Submit an idea
-                v-divider.my-3
-                v-subheader Questions / Comments
-                .body-1.pl-3 Join our gitter channel. We are very active and friendly!
-                v-btn.ml-3.mt-3(depressed, dark, color='pink', href='https://gitter.im/Requarks/wiki', target='_blank')
-                  v-icon(left) chat
-                  span Launch Gitter
+      v-flex.animated.fadeInUp.wait-p2s(xs12, lg9)
+        transition(name='admin-router')
+          component(:is='selectedTool')
 
 </template>
 
 <script>
-import IconGithubCircle from 'mdi/GithubCircle'
-import IconHomeAlert from 'mdi/HomeAlert'
 
 export default {
   components: {
-    IconGithubCircle,
-    IconHomeAlert
+    UtilityAuth: () => import(/* webpackChunkName: "admin" */ './admin-utilities-auth.vue'),
+    UtilityCache: () => import(/* webpackChunkName: "admin" */ './admin-utilities-cache.vue'),
+    UtilityImportv1: () => import(/* webpackChunkName: "admin" */ './admin-utilities-importv1.vue'),
+    UtilityTelemetry: () => import(/* webpackChunkName: "admin" */ './admin-utilities-telemetry.vue')
   },
   data() {
     return {
-      tab: '0',
-      moduleTypes: [
-        { text: 'Authentication', value: 'authentication' },
-        { text: 'Editor', value: 'editor' },
-        { text: 'Logging', value: 'logging' },
-        { text: 'Rendering', value: 'renderer' },
-        { text: 'Search Engine', value: 'search' },
-        { text: 'Storage', value: 'storage' }
-      ],
-      rescanModuleType: 'authentication',
-      telemetry: true
-    }
-  },
-  methods: {
-    updateTelemetry() {
-      this.$store.commit('showNotification', {
-        style: 'indigo',
-        message: `Coming soon...`,
-        icon: 'directions_boat'
-      })
-    },
-    resetClientId() {
-      this.$store.commit('showNotification', {
-        style: 'indigo',
-        message: `Coming soon...`,
-        icon: 'directions_boat'
-      })
+      selectedTool: 'UtilityAuth',
+      tools: [
+        {
+          key: 'UtilityAuth',
+          icon: 'lock_outline',
+          i18nKey: 'auth',
+          isAvailable: true
+        },
+        {
+          key: 'UtilityCache',
+          icon: 'invert_colors',
+          i18nKey: 'cache',
+          isAvailable: true
+        },
+        {
+          key: 'UtilityGraphEndpoint',
+          icon: 'settings_ethernet',
+          i18nKey: 'graphEndpoint',
+          isAvailable: false
+        },
+        {
+          key: 'UtilityImportv1',
+          icon: 'present_to_all',
+          i18nKey: 'importv1',
+          isAvailable: true
+        },
+        {
+          key: 'UtilityTelemetry',
+          icon: 'wifi_tethering',
+          i18nKey: 'telemetry',
+          isAvailable: true
+        }
+      ]
     }
   }
 }

+ 12 - 0
client/graph/admin/utilities/utilities-mutation-auth-regencerts.gql

@@ -0,0 +1,12 @@
+mutation {
+  authentication {
+    regenerateCertificates {
+      responseResult {
+        succeeded
+        errorCode
+        slug
+        message
+      }
+    }
+  }
+}

+ 12 - 0
client/graph/admin/utilities/utilities-mutation-auth-resetguest.gql

@@ -0,0 +1,12 @@
+mutation {
+  authentication {
+    resetGuestUser {
+      responseResult {
+        succeeded
+        errorCode
+        slug
+        message
+      }
+    }
+  }
+}

+ 13 - 0
client/static/svg/icon-software.svg

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Layer_1" x="0px" y="0px" viewBox="0 0 48 48" style="enable-background:new 0 0 48 48;" xml:space="preserve" width="96px" height="96px">
+<path style="fill:#891212;" d="M24,28L6,35V20l18-7V28z"/>
+<path style="fill:#891212;" d="M42,20l-18-7v15l18,7V20z"/>
+<path style="fill:#891212;" d="M26,18.5c0,1.379-1.121,2.5-2.5,2.5S21,19.879,21,18.5s1.121-2.5,2.5-2.5S26,17.121,26,18.5z"/>
+<path style="fill:#90CAF9;" d="M23.5,6C16.598,6,11,11.598,11,18.5S16.598,31,23.5,31S36,25.402,36,18.5S30.402,6,23.5,6z   M23.5,20.805c-1.273,0-2.305-1.031-2.305-2.305s1.031-2.305,2.305-2.305s2.305,1.031,2.305,2.305S24.77,20.805,23.5,20.805z"/>
+<path style="fill:#CFE8F9;" d="M20.676,19.844L13,25.344c0.879,1.316,2.516,2.938,4.188,4l5.43-7.531  C21.684,21.516,20.957,20.781,20.676,19.844z"/>
+<path style="fill:#CFE8F9;" d="M30.27,8l-5.965,8.164c0.938,0.266,1.68,0.977,1.988,1.895l8.117-5.648  C33.41,10.621,31.984,9.109,30.27,8z"/>
+<path style="fill:#F44336;" d="M24,27L6,20v15l18,7V27z"/>
+<path style="fill:#C62828;" d="M42,35l-18,7V27l18-7V35z"/>
+<path style="fill:#1E88E5;" d="M23.5,15c-1.934,0-3.5,1.566-3.5,3.5s1.566,3.5,3.5,3.5s3.5-1.566,3.5-3.5S25.434,15,23.5,15z   M23.5,20c-0.828,0-1.5-0.672-1.5-1.5s0.672-1.5,1.5-1.5s1.5,0.672,1.5,1.5S24.328,20,23.5,20z"/>
+</svg>

+ 2 - 2
dev/index.js

@@ -46,12 +46,12 @@ const init = {
         atomic: 400
       })
       devWatcher.on('ready', () => {
-        devWatcher.on('all', () => {
+        devWatcher.on('all', _.debounce(() => {
           console.warn(chalk.yellow.bold('--- >>>>>>>>>>>>>>>>>>>>>>>>>>>> ---'))
           console.warn(chalk.yellow.bold('--- Changes detected: Restarting ---'))
           console.warn(chalk.yellow.bold('--- <<<<<<<<<<<<<<<<<<<<<<<<<<<< ---'))
           this.reload()
-        })
+        }, 500))
       })
     })
   },

+ 70 - 0
server/core/auth.js

@@ -4,6 +4,9 @@ const _ = require('lodash')
 const path = require('path')
 const jwt = require('jsonwebtoken')
 const moment = require('moment')
+const Promise = require('bluebird')
+const crypto = Promise.promisifyAll(require('crypto'))
+const pem2jwk = require('pem-jwk').pem2jwk
 
 const securityHelper = require('../helpers/security')
 
@@ -236,5 +239,72 @@ module.exports = {
   async reloadGroups() {
     const groupsArray = await WIKI.models.groups.query()
     this.groups = _.keyBy(groupsArray, 'id')
+  },
+
+  /**
+   * Generate New Authentication Public / Private Key Certificates
+   */
+  async regenerateCertificates() {
+    WIKI.logger.info('Regenerating certificates...')
+
+    _.set(WIKI.config, 'sessionSecret', (await crypto.randomBytesAsync(32)).toString('hex'))
+    const certs = crypto.generateKeyPairSync('rsa', {
+      modulusLength: 2048,
+      publicKeyEncoding: {
+        type: 'pkcs1',
+        format: 'pem'
+      },
+      privateKeyEncoding: {
+        type: 'pkcs1',
+        format: 'pem',
+        cipher: 'aes-256-cbc',
+        passphrase: WIKI.config.sessionSecret
+      }
+    })
+
+    _.set(WIKI.config, 'certs', {
+      jwk: pem2jwk(certs.publicKey),
+      public: certs.publicKey,
+      private: certs.privateKey
+    })
+
+    await WIKI.configSvc.saveToDb([
+      'certs',
+      'sessionSecret'
+    ])
+
+    await WIKI.auth.activateStrategies()
+
+    WIKI.logger.info('Regenerated certificates: [ COMPLETED ]')
+  },
+
+  /**
+   * Reset Guest User
+   */
+  async resetGuestUser() {
+    WIKI.logger.info('Resetting guest account...')
+    const guestGroup = await WIKI.models.groups.query().where('id', 2).first()
+
+    await WIKI.models.users.query().delete().where({
+      providerKey: 'local',
+      email: 'guest@example.com'
+    }).orWhere('id', 2)
+
+    const guestUser = await WIKI.models.users.query().insert({
+      id: 2,
+      provider: 'local',
+      email: 'guest@example.com',
+      name: 'Guest',
+      password: '',
+      locale: 'en',
+      defaultEditor: 'markdown',
+      tfaIsActive: false,
+      isSystem: true,
+      isActive: true,
+      isVerified: true
+    })
+    await guestUser.$relatedQuery('groups').relate(guestGroup.id)
+
+    WIKI.logger.info('Guest user has been reset: [ COMPLETED ]')
   }
 }

+ 41 - 0
server/graph/resolvers/authentication.js

@@ -13,6 +13,9 @@ module.exports = {
     async authentication() { return {} }
   },
   AuthenticationQuery: {
+    /**
+     * Fetch active authentication strategies
+     */
     async strategies(obj, args, context, info) {
       let strategies = await WIKI.models.authentication.getStrategies(args.isEnabled)
       strategies = strategies.map(stg => {
@@ -38,6 +41,9 @@ module.exports = {
     }
   },
   AuthenticationMutation: {
+    /**
+     * Perform Login
+     */
     async login(obj, args, context) {
       try {
         const authResult = await WIKI.models.users.login(args, context)
@@ -54,6 +60,9 @@ module.exports = {
         return graphHelper.generateError(err)
       }
     },
+    /**
+     * Perform 2FA Login
+     */
     async loginTFA(obj, args, context) {
       try {
         const authResult = await WIKI.models.users.loginTFA(args, context)
@@ -65,6 +74,9 @@ module.exports = {
         return graphHelper.generateError(err)
       }
     },
+    /**
+     * Register a new account
+     */
     async register(obj, args, context) {
       try {
         await WIKI.models.users.register({ ...args, verify: true }, context)
@@ -75,6 +87,9 @@ module.exports = {
         return graphHelper.generateError(err)
       }
     },
+    /**
+     * Update Authentication Strategies
+     */
     async updateStrategies(obj, args, context) {
       try {
         WIKI.config.auth = {
@@ -103,6 +118,32 @@ module.exports = {
       } catch (err) {
         return graphHelper.generateError(err)
       }
+    },
+    /**
+     * Generate New Authentication Public / Private Key Certificates
+     */
+    async regenerateCertificates(obj, args, context) {
+      try {
+        await WIKI.auth.regenerateCertificates()
+        return {
+          responseResult: graphHelper.generateSuccess('Certificates have been regenerated successfully.')
+        }
+      } catch (err) {
+        return graphHelper.generateError(err)
+      }
+    },
+    /**
+     * Reset Guest User
+     */
+    async resetGuestUser(obj, args, context) {
+      try {
+        await WIKI.auth.resetGuestUser()
+        return {
+          responseResult: graphHelper.generateSuccess('Guest user has been reset successfully.')
+        }
+      } catch (err) {
+        return graphHelper.generateError(err)
+      }
     }
   },
   AuthenticationStrategy: {

+ 3 - 0
server/graph/schemas/authentication.graphql

@@ -46,6 +46,9 @@ type AuthenticationMutation {
     strategies: [AuthenticationStrategyInput]!
     config: AuthenticationConfigInput
   ): DefaultResponse @auth(requires: ["manage:system"])
+
+  regenerateCertificates: DefaultResponse @auth(requires: ["manage:system"])
+  resetGuestUser: DefaultResponse @auth(requires: ["manage:system"])
 }
 
 # -----------------------------------------------

+ 1 - 1
server/models/users.js

@@ -336,7 +336,7 @@ module.exports = class User extends Model {
 
   static async loginTFA(opts, context) {
     if (opts.securityCode.length === 6 && opts.loginToken.length === 64) {
-      let result = null // await WIKI.redis.get(`tfa:${opts.loginToken}`)
+      let result = await WIKI.redis.get(`tfa:${opts.loginToken}`)
       if (result) {
         let userId = _.toSafeInteger(result)
         if (userId && userId > 0) {

+ 1 - 1
server/modules/storage/git/storage.js

@@ -351,7 +351,7 @@ module.exports = {
       new stream.Transform({
         objectMode: true,
         transform: async (page, enc, cb) => {
-          const fileName = `${page.path}.${getFileExtension(page.contentType)}`
+          let fileName = `${page.path}.${getFileExtension(page.contentType)}`
           if (WIKI.config.lang.namespacing && WIKI.config.lang.code !== page.localeCode) {
             fileName = `${page.localeCode}/${fileName}`
           }