瀏覽代碼

feat: verify + activate + deactivate user from admin

NGPixel 5 年之前
父節點
當前提交
661b6044fa

+ 165 - 11
client/components/admin/admin-users-edit.vue

@@ -34,12 +34,16 @@
             v-list(dense, nav)
               v-list-item(v-if='!user.isActive', @click='activateUser')
                 v-list-item-icon
-                  v-icon(color='purple') mdi-steering
+                  v-icon(color='purple') mdi-account-key
                 v-list-item-title Activate
               v-list-item(v-else, @click='deactivateUser', :disabled='user.id == currentUserId || user.isSystem')
                 v-list-item-icon
-                  v-icon(color='purple') mdi-cancel
+                  v-icon(color='purple') mdi-account-cancel
                 v-list-item-title Deactivate
+              v-list-item(@click='verifyUser', :disabled='user.isVerified')
+                v-list-item-icon
+                  v-icon(color='blue') mdi-account-check
+                v-list-item-title Set as Verified
               v-list-item(@click='deleteUserDialog = true', :disabled='user.id == currentUserId || user.isSystem')
                 v-list-item-icon
                   v-icon(color='red') mdi-trash-can-outline
@@ -343,13 +347,12 @@
 <script>
 import _ from 'lodash'
 import { get } from 'vuex-pathify'
+import gql from 'graphql-tag'
 
 import { StatusIndicator } from 'vue-status-indicator'
 
 import userQuery from 'gql/admin/users/users-query-single.gql'
 import groupsQuery from 'gql/admin/users/users-query-groups.gql'
-import updateUserMutation from 'gql/admin/users/users-mutation-update.gql'
-import deleteUserMutation from 'gql/admin/users/users-mutation-delete.gql'
 
 export default {
   components: {
@@ -638,20 +641,106 @@ export default {
     currentUserId: get('user/id')
   },
   methods: {
+    /**
+     * Activate a user (if previously deactivated)
+     */
     async activateUser () {
-
+      this.$store.commit(`loadingStart`, 'admin-users-activate')
+      const resp = await this.$apollo.mutate({
+        mutation: gql`
+          mutation ($id: Int!) {
+            users {
+              activate(id: $id) {
+                responseResult {
+                  succeeded
+                  errorCode
+                  slug
+                  message
+                }
+              }
+            }
+          }
+        `,
+        variables: {
+          id: this.user.id
+        }
+      })
+      if (_.get(resp, 'data.users.activate.responseResult.succeeded', false)) {
+        this.$store.commit('showNotification', {
+          style: 'success',
+          message: this.$t('admin:users.userActivateSuccess'),
+          icon: 'check'
+        })
+        this.user.isActive = true
+      } else {
+        this.$store.commit('showNotification', {
+          style: 'red',
+          message: _.get(resp, 'data.users.activate.responseResult.message', 'An unexpected error occured.'),
+          icon: 'warning'
+        })
+      }
+      this.$store.commit(`loadingStop`, 'admin-users-activate')
     },
+    /**
+     * Deactivate a currently active user
+     */
     async deactivateUser () {
-      this.$store.commit('showNotification', {
-        style: 'indigo',
-        message: `Coming soon...`,
-        icon: 'directions_boat'
+      this.$store.commit(`loadingStart`, 'admin-users-deactivate')
+      const resp = await this.$apollo.mutate({
+        mutation: gql`
+          mutation ($id: Int!) {
+            users {
+              deactivate(id: $id) {
+                responseResult {
+                  succeeded
+                  errorCode
+                  slug
+                  message
+                }
+              }
+            }
+          }
+        `,
+        variables: {
+          id: this.user.id
+        }
       })
+      if (_.get(resp, 'data.users.deactivate.responseResult.succeeded', false)) {
+        this.$store.commit('showNotification', {
+          style: 'success',
+          message: this.$t('admin:users.userDeactivateSuccess'),
+          icon: 'check'
+        })
+        this.user.isActive = false
+      } else {
+        this.$store.commit('showNotification', {
+          style: 'red',
+          message: _.get(resp, 'data.users.deactivate.responseResult.message', 'An unexpected error occured.'),
+          icon: 'warning'
+        })
+      }
+      this.$store.commit(`loadingStop`, 'admin-users-deactivate')
     },
+    /**
+     * Delete a user
+     */
     async deleteUser () {
       this.$store.commit(`loadingStart`, 'admin-users-delete')
       const resp = await this.$apollo.mutate({
-        mutation: deleteUserMutation,
+        mutation: gql`
+          mutation ($id: Int!) {
+            users {
+              delete(id: $id) {
+                responseResult {
+                  succeeded
+                  errorCode
+                  slug
+                  message
+                }
+              }
+            }
+          }
+        `,
         variables: {
           id: this.user.id
         }
@@ -673,10 +762,26 @@ export default {
       this.deleteUserDialog = false
       this.$store.commit(`loadingStop`, 'admin-users-delete')
     },
+    /**
+     * Update a user
+     */
     async updateUser() {
       this.$store.commit(`loadingStart`, 'admin-users-update')
       const resp = await this.$apollo.mutate({
-        mutation: updateUserMutation,
+        mutation: gql`
+          mutation ($id: Int!, $email: String, $name: String, $newPassword: String, $groups: [Int], $location: String, $jobTitle: String, $timezone: String) {
+            users {
+              update(id: $id, email: $email, name: $name, newPassword: $newPassword, groups: $groups, location: $location, jobTitle: $jobTitle, timezone: $timezone) {
+                responseResult {
+                  succeeded
+                  errorCode
+                  slug
+                  message
+                }
+              }
+            }
+          }
+        `,
         variables: {
           id: this.user.id,
           email: this.user.email,
@@ -704,6 +809,9 @@ export default {
       }
       this.$store.commit(`loadingStop`, 'admin-users-update')
     },
+    /**
+     * Focus an input after delay
+     */
     focusField (ipt) {
       this.$nextTick(() => {
         _.delay(() => {
@@ -711,6 +819,9 @@ export default {
         }, 200)
       })
     },
+    /**
+     * Assign group to user
+     */
     assignGroup() {
       if (_.some(this.user.groups, ['id', this.newGroup])) {
         this.$store.commit('showNotification', {
@@ -723,8 +834,51 @@ export default {
         this.newGroup = 0
       }
     },
+    /**
+     * Unassign group from user
+     */
     unassignGroup(gid) {
       this.user.groups = _.reject(this.user.groups, ['id', gid])
+    },
+    /**
+     * Manually set user as verified
+     */
+    async verifyUser () {
+      this.$store.commit(`loadingStart`, 'admin-users-verify')
+      const resp = await this.$apollo.mutate({
+        mutation: gql`
+          mutation ($id: Int!) {
+            users {
+              verify(id: $id) {
+                responseResult {
+                  succeeded
+                  errorCode
+                  slug
+                  message
+                }
+              }
+            }
+          }
+        `,
+        variables: {
+          id: this.user.id
+        }
+      })
+      if (_.get(resp, 'data.users.verify.responseResult.succeeded', false)) {
+        this.$store.commit('showNotification', {
+          style: 'success',
+          message: this.$t('admin:users.userVerifySuccess'),
+          icon: 'check'
+        })
+        this.user.isVerified = true
+      } else {
+        this.$store.commit('showNotification', {
+          style: 'red',
+          message: _.get(resp, 'data.users.verify.responseResult.message', 'An unexpected error occured.'),
+          icon: 'warning'
+        })
+      }
+      this.$store.commit(`loadingStop`, 'admin-users-verify')
     }
   },
   apollo: {

+ 0 - 12
client/graph/admin/users/users-mutation-delete.gql

@@ -1,12 +0,0 @@
-mutation ($id: Int!) {
-  users {
-    delete(id: $id) {
-      responseResult {
-        succeeded
-        errorCode
-        slug
-        message
-      }
-    }
-  }
-}

+ 0 - 12
client/graph/admin/users/users-mutation-update.gql

@@ -1,12 +0,0 @@
-mutation ($id: Int!, $email: String, $name: String, $newPassword: String, $groups: [Int], $location: String, $jobTitle: String, $timezone: String) {
-  users {
-    update(id: $id, email: $email, name: $name, newPassword: $newPassword, groups: $groups, location: $location, jobTitle: $jobTitle, timezone: $timezone) {
-      responseResult {
-        succeeded
-        errorCode
-        slug
-        message
-      }
-    }
-  }
-}

+ 37 - 37
package.json

@@ -37,14 +37,14 @@
   "dependencies": {
     "@aoberoi/passport-slack": "1.0.5",
     "@azure/storage-blob": "12.0.1",
-    "@bugsnag/js": "6.4.3",
+    "@bugsnag/js": "6.5.0",
     "@exlinc/keycloak-passport": "1.0.2",
     "algoliasearch": "3.35.1",
     "apollo-fetch": "0.7.0",
-    "apollo-server": "2.9.13",
-    "apollo-server-express": "2.9.13",
+    "apollo-server": "2.9.15",
+    "apollo-server-express": "2.9.15",
     "auto-load": "3.0.4",
-    "aws-sdk": "2.590.0",
+    "aws-sdk": "2.596.0",
     "azure-search-client": "3.1.5",
     "bcryptjs-then": "1.0.1",
     "bluebird": "3.7.2",
@@ -52,7 +52,7 @@
     "brute-knex": "4.0.0",
     "chalk": "3.0.0",
     "cheerio": "1.0.0-rc.3",
-    "chokidar": "3.3.0",
+    "chokidar": "3.3.1",
     "clean-css": "4.2.1",
     "compression": "1.7.4",
     "connect-session-knex": "1.5.0",
@@ -69,7 +69,7 @@
     "express": "4.17.1",
     "express-brute": "1.0.1",
     "express-session": "1.17.0",
-    "file-type": "12.4.0",
+    "file-type": "12.4.2",
     "filesize": "6.0.1",
     "fs-extra": "8.1.0",
     "getos": "3.1.1",
@@ -90,7 +90,7 @@
     "jsonwebtoken": "8.5.1",
     "katex": "0.11.1",
     "klaw": "3.0.0",
-    "knex": "0.20.4",
+    "knex": "0.20.6",
     "lodash": "4.17.15",
     "markdown-it": "10.0.0",
     "markdown-it-abbr": "1.0.4",
@@ -109,18 +109,18 @@
     "mime-types": "2.1.25",
     "moment": "2.24.0",
     "moment-timezone": "0.5.27",
-    "mongodb": "3.4.0",
+    "mongodb": "3.4.1",
     "mssql": "6.0.1",
     "multer": "1.4.2",
-    "mysql2": "2.0.2",
-    "nanoid": "2.1.7",
+    "mysql2": "2.1.0",
+    "nanoid": "2.1.8",
     "node-2fa": "1.1.2",
     "node-cache": "5.1.0",
     "nodemailer": "6.4.2",
     "objection": "1.6.11",
     "passport": "0.4.1",
     "passport-auth0": "1.3.1",
-    "passport-azure-ad": "4.2.0",
+    "passport-azure-ad": "4.2.1",
     "passport-cas": "0.1.1",
     "passport-discord": "0.1.3",
     "passport-dropbox-oauth2": "1.1.0",
@@ -138,10 +138,10 @@
     "passport-saml": "1.2.0",
     "passport-twitch": "1.0.3",
     "pem-jwk": "2.0.0",
-    "pg": "7.14.0",
+    "pg": "7.17.0",
     "pg-hstore": "2.3.3",
-    "pg-query-stream": "2.0.1",
-    "pg-tsquery": "8.0.5",
+    "pg-query-stream": "2.1.2",
+    "pg-tsquery": "8.1.0",
     "pug": "2.0.4",
     "qr-image": "3.2.0",
     "raven": "2.6.4",
@@ -151,7 +151,7 @@
     "safe-regex": "2.1.1",
     "sanitize-filename": "1.6.3",
     "scim-query-filter-parser": "2.0.2",
-    "semver": "6.3.0",
+    "semver": "7.1.1",
     "serve-favicon": "2.5.0",
     "simple-git": "1.129.0",
     "solr-node": "1.2.1",
@@ -167,11 +167,11 @@
     "validate.js": "0.13.1",
     "winston": "3.2.1",
     "xss": "1.0.6",
-    "yargs": "15.0.2"
+    "yargs": "15.1.0"
   },
   "devDependencies": {
-    "@babel/cli": "^7.7.5",
-    "@babel/core": "^7.7.5",
+    "@babel/cli": "^7.7.7",
+    "@babel/core": "^7.7.7",
     "@babel/plugin-proposal-class-properties": "^7.7.4",
     "@babel/plugin-proposal-decorators": "^7.7.4",
     "@babel/plugin-proposal-export-namespace-from": "^7.7.4",
@@ -182,11 +182,11 @@
     "@babel/plugin-syntax-dynamic-import": "^7.7.4",
     "@babel/plugin-syntax-import-meta": "^7.7.4",
     "@babel/polyfill": "^7.7.0",
-    "@babel/preset-env": "^7.7.6",
+    "@babel/preset-env": "^7.7.7",
     "@mdi/font": "4.7.95",
     "@panter/vue-i18next": "0.15.1",
     "@requarks/ckeditor5": "12.4.0-wiki.14",
-    "@vue/babel-preset-app": "4.1.1",
+    "@vue/babel-preset-app": "4.1.2",
     "animate-sass": "0.8.2",
     "animated-number-vue": "1.0.0",
     "apollo-cache-inmemory": "1.6.5",
@@ -210,24 +210,24 @@
     "chart.js": "2.9.3",
     "clean-webpack-plugin": "3.0.0",
     "clipboard": "2.0.4",
-    "codemirror": "5.49.2",
+    "codemirror": "5.50.2",
     "copy-webpack-plugin": "5.1.1",
-    "core-js": "3.5.0",
-    "css-loader": "3.3.2",
+    "core-js": "3.6.1",
+    "css-loader": "3.4.0",
     "cssnano": "4.1.10",
     "duplicate-package-checker-webpack-plugin": "3.0.0",
     "epic-spinners": "1.1.0",
-    "eslint": "6.7.2",
+    "eslint": "6.8.0",
     "eslint-config-requarks": "1.0.7",
     "eslint-config-standard": "14.1.0",
     "eslint-plugin-import": "2.19.1",
-    "eslint-plugin-node": "10.0.0",
+    "eslint-plugin-node": "11.0.0",
     "eslint-plugin-promise": "4.2.1",
     "eslint-plugin-standard": "4.0.1",
-    "eslint-plugin-vue": "6.0.1",
+    "eslint-plugin-vue": "6.1.2",
     "fibers": "4.0.2",
     "file-loader": "5.0.2",
-    "filepond": "4.9.2",
+    "filepond": "4.9.3",
     "filepond-plugin-file-validate-type": "1.2.4",
     "filesize.js": "2.0.0",
     "graphql-persisted-document-loader": "2.0.0",
@@ -241,7 +241,7 @@
     "ignore-loader": "0.1.2",
     "jest": "24.9.0",
     "js-cookie": "2.2.1",
-    "mini-css-extract-plugin": "0.8.0",
+    "mini-css-extract-plugin": "0.9.0",
     "moment-duration-format": "2.3.2",
     "offline-plugin": "5.0.7",
     "optimize-css-assets-webpack-plugin": "5.0.3",
@@ -258,13 +258,13 @@
     "pug-plain-loader": "1.0.0",
     "raw-loader": "4.0.0",
     "resolve-url-loader": "3.1.1",
-    "sass": "1.23.7",
+    "sass": "1.24.0",
     "sass-loader": "8.0.0",
     "sass-resources-loader": "2.0.1",
     "script-ext-html-webpack-plugin": "2.1.4",
     "simple-progress-webpack-plugin": "1.1.2",
-    "style-loader": "1.0.1",
-    "terser": "4.4.2",
+    "style-loader": "1.1.2",
+    "terser": "4.5.0",
     "twemoji-awesome": "1.0.6",
     "url-loader": "3.0.0",
     "velocity-animate": "1.5.2",
@@ -273,22 +273,22 @@
     "vue-apollo": "3.0.2",
     "vue-chartjs": "3.5.0",
     "vue-clipboards": "1.3.0",
-    "vue-filepond": "5.1.3",
+    "vue-filepond": "6.0.0",
     "vue-hot-reload-api": "2.3.4",
-    "vue-loader": "15.7.2",
-    "vue-moment": "4.0.0",
+    "vue-loader": "15.8.3",
+    "vue-moment": "4.1.0",
     "vue-router": "3.1.3",
     "vue-status-indicator": "1.2.1",
     "vue-template-compiler": "2.6.11",
     "vue2-animate": "2.1.3",
     "vuedraggable": "2.23.2",
     "vuescroll": "4.14.4",
-    "vuetify": "2.1.14",
+    "vuetify": "2.2.0",
     "vuetify-loader": "1.4.3",
     "vuex": "3.1.2",
-    "vuex-pathify": "1.4.0",
+    "vuex-pathify": "1.4.1",
     "vuex-persistedstate": "2.7.0",
-    "webpack": "4.41.2",
+    "webpack": "4.41.5",
     "webpack-bundle-analyzer": "3.6.0",
     "webpack-cli": "3.3.10",
     "webpack-dev-middleware": "3.7.2",

+ 36 - 0
server/graph/resolvers/user.js

@@ -68,6 +68,42 @@ module.exports = {
         return graphHelper.generateError(err)
       }
     },
+    async verify (obj, args) {
+      try {
+        await WIKI.models.users.query().patch({ isVerified: true }).findById(args.id)
+
+        return {
+          responseResult: graphHelper.generateSuccess('User verified successfully')
+        }
+      } catch (err) {
+        return graphHelper.generateError(err)
+      }
+    },
+    async activate (obj, args) {
+      try {
+        await WIKI.models.users.query().patch({ isActive: true }).findById(args.id)
+
+        return {
+          responseResult: graphHelper.generateSuccess('User activated successfully')
+        }
+      } catch (err) {
+        return graphHelper.generateError(err)
+      }
+    },
+    async deactivate (obj, args) {
+      try {
+        if (args.id <= 2) {
+          throw new Error('Cannot deactivate system accounts.')
+        }
+        await WIKI.models.users.query().patch({ isActive: false }).findById(args.id)
+
+        return {
+          responseResult: graphHelper.generateSuccess('User deactivated successfully')
+        }
+      } catch (err) {
+        return graphHelper.generateError(err)
+      }
+    },
     resetPassword (obj, args) {
       return false
     }

+ 12 - 0
server/graph/schemas/user.graphql

@@ -59,6 +59,18 @@ type UserMutation {
     id: Int!
   ): DefaultResponse @auth(requires: ["manage:users", "manage:system"])
 
+  verify(
+    id: Int!
+  ): DefaultResponse @auth(requires: ["manage:users", "manage:system"])
+
+  activate(
+    id: Int!
+  ): DefaultResponse @auth(requires: ["manage:users", "manage:system"])
+
+  deactivate(
+    id: Int!
+  ): DefaultResponse @auth(requires: ["manage:users", "manage:system"])
+
   resetPassword(
     id: Int!
   ): DefaultResponse

文件差異過大導致無法顯示
+ 286 - 262
yarn.lock


部分文件因文件數量過多而無法顯示