浏览代码

feat: authentication improvements

NGPixel 6 年之前
父节点
当前提交
bcd6ceb271
共有 39 个文件被更改,包括 704 次插入523 次删除
  1. 11 15
      client/components/admin.vue
  2. 42 8
      client/components/admin/admin-auth.vue
  3. 7 1
      client/components/admin/admin-general.vue
  4. 3 5
      client/components/common/nav-header.vue
  5. 3 1
      client/graph/admin/auth/auth-query-strategies.gql
  6. 3 1
      client/store/admin.js
  7. 5 1
      client/store/index.js
  8. 15 0
      client/store/site.js
  9. 60 57
      package.json
  10. 23 10
      server/core/auth.js
  11. 1 0
      server/core/kernel.js
  12. 0 3
      server/db/migrations/2.0.0.js
  13. 17 6
      server/graph/resolvers/authentication.js
  14. 1 1
      server/graph/resolvers/storage.js
  15. 3 0
      server/graph/schemas/authentication.graphql
  16. 20 0
      server/helpers/common.js
  17. 0 13
      server/master.js
  18. 18 22
      server/models/authentication.js
  19. 2 20
      server/models/storage.js
  20. 3 0
      server/modules/authentication/auth0/definition.yml
  21. 3 0
      server/modules/authentication/azure/definition.yml
  22. 3 0
      server/modules/authentication/cas/definition.yml
  23. 3 0
      server/modules/authentication/discord/definition.yml
  24. 3 0
      server/modules/authentication/dropbox/definition.yml
  25. 3 0
      server/modules/authentication/facebook/definition.yml
  26. 3 0
      server/modules/authentication/github/definition.yml
  27. 3 0
      server/modules/authentication/google/definition.yml
  28. 16 2
      server/modules/authentication/ldap/definition.yml
  29. 3 0
      server/modules/authentication/local/definition.yml
  30. 4 1
      server/modules/authentication/microsoft/definition.yml
  31. 4 1
      server/modules/authentication/oauth2/definition.yml
  32. 35 0
      server/modules/authentication/oidc/authentication.js
  33. 16 0
      server/modules/authentication/oidc/definition.yml
  34. 29 0
      server/modules/authentication/okta/authentication.js
  35. 21 0
      server/modules/authentication/okta/definition.yml
  36. 3 0
      server/modules/authentication/slack/definition.yml
  37. 3 0
      server/modules/authentication/twitch/definition.yml
  38. 1 1
      server/views/main/welcome.pug
  39. 311 354
      yarn.lock

+ 11 - 15
client/components/admin.vue

@@ -76,7 +76,9 @@
         router-view
 
     v-footer.py-2.justify-center(app, absolute, :color='darkMode ? "" : "grey lighten-3"', inset, height='auto')
-      .caption.grey--text.text--darken-1 {{ $t('common:footer.poweredBy') }} Wiki.js
+      .caption.grey--text.text--darken-1
+        span(v-if='company && company.length > 0') {{ $t('common:footer.copyright', { company: company, year: currentYear }) }} | 
+        span {{ $t('common:footer.poweredBy') }} Wiki.js
 
     v-snackbar(
       :color='notification.style'
@@ -92,11 +94,11 @@
 
 <script>
 import VueRouter from 'vue-router'
-import { mapState } from 'vuex'
+import { get, sync } from 'vuex-pathify'
 
 import adminStore from '@/store/admin'
 
-/* global WIKI, siteConfig */
+/* global WIKI */
 
 WIKI.$store.registerModule('admin', adminStore)
 
@@ -131,23 +133,17 @@ export default {
   i18nOptions: { namespaces: 'admin' },
   data() {
     return {
+      currentYear: (new Date()).getFullYear(),
       adminDrawerShown: true
     }
   },
   computed: {
-    ...mapState({
-      notification: state => state.notification,
-      darkMode: state => state.admin.theme.dark
-    }),
-    notificationState: {
-      get() { return this.notification.isActive },
-      set(newState) { this.$store.commit('updateNotificationState', newState) }
-    }
+    company: get('site/company'),
+    notification: get('notification'),
+    darkMode: get('admin/theme@dark'),
+    notificationState: sync('notification@isActive')
   },
-  router,
-  mounted() {
-    this.$store.commit('admin/setThemeDarkMode', siteConfig.darkMode)
-  }
+  router
 }
 </script>
 

+ 42 - 8
client/components/admin/admin-auth.vue

@@ -25,31 +25,49 @@
       v-tab-item(v-for='(strategy, n) in activeStrategies', :key='strategy.key', :transition='false', :reverse-transition='false')
         v-card.pa-3(flat, tile)
           v-form
+            .authlogo
+              img(:src='strategy.logo', :alt='strategy.title')
+            v-subheader.pl-0 {{strategy.title}}
+            .caption {{strategy.description}}
+            .caption: a(:href='strategy.website') {{strategy.website}}
+            v-divider.mt-3
             v-subheader.pl-0 Strategy Configuration
             .body-1.ml-3(v-if='!strategy.config || strategy.config.length < 1') This strategy has no configuration options you can modify.
             template(v-else, v-for='cfg in strategy.config')
               v-select(
                 v-if='cfg.value.type === "string" && cfg.value.enum'
+                outline
+                background-color='grey lighten-2'
                 :items='cfg.value.enum'
                 :key='cfg.key'
-                :label='cfg.key | startCase'
+                :label='cfg.value.title'
                 v-model='cfg.value.value'
                 prepend-icon='settings_applications'
+                :hint='cfg.value.hint ? cfg.value.hint : ""'
+                persistent-hint
+                :class='cfg.value.hint ? "mb-2" : ""'
               )
               v-switch(
                 v-else-if='cfg.value.type === "boolean"'
                 :key='cfg.key'
-                :label='cfg.key | startCase'
+                :label='cfg.value.title'
                 v-model='cfg.value.value'
                 color='primary'
                 prepend-icon='settings_applications'
+                :hint='cfg.value.hint ? cfg.value.hint : ""'
+                persistent-hint
                 )
               v-text-field(
                 v-else
+                outline
+                background-color='grey lighten-2'
                 :key='cfg.key'
-                :label='cfg.key | startCase'
+                :label='cfg.value.title'
                 v-model='cfg.value.value'
                 prepend-icon='settings_applications'
+                :hint='cfg.value.hint ? cfg.value.hint : ""'
+                persistent-hint
+                :class='cfg.value.hint ? "mb-2" : ""'
                 )
             v-divider.mt-3
             v-subheader.pl-0 Registration
@@ -61,18 +79,21 @@
                 hint='Allow any user successfully authorized by the strategy to access the wiki.'
                 persistent-hint
               )
-              v-select.ml-3(
+              v-combobox.ml-3.mt-3(
                 label='Limit to specific email domains'
                 v-model='strategy.domainWhitelist'
                 prepend-icon='mail_outline'
+                outline
+                background-color='grey lighten-2'
                 persistent-hint
                 deletable-chips
                 clearable
                 multiple
                 chips
-                tags
                 )
-              v-select.ml-3(
+              v-autocomplete.ml-3(
+                outline
+                background-color='grey lighten-2'
                 :items='groups'
                 item-text='name'
                 item-value='id'
@@ -82,7 +103,6 @@
                 hint='Automatically assign new users to these groups.'
                 persistent-hint
                 deletable-chips
-                autocomplete
                 clearable
                 multiple
                 chips
@@ -173,6 +193,20 @@ export default {
 }
 </script>
 
-<style lang='scss'>
+<style lang='scss' scoped>
+
+.authlogo {
+  width: 250px;
+  height: 85px;
+  float:right;
+  display: flex;
+  justify-content: flex-end;
+  align-items: center;
+
+  img {
+    max-width: 100%;
+    max-height: 50px;
+  }
+}
 
 </style>

+ 7 - 1
client/components/admin/admin-general.vue

@@ -36,6 +36,7 @@
                   .px-3.pb-3
                     v-text-field(
                       label='Company / Organization Name'
+                      v-model='company'
                       :counter='255'
                       prepend-icon='public'
                       persistent-hint
@@ -89,15 +90,20 @@
 
 <script>
 
+import { sync } from 'vuex-pathify'
+
 export default {
   data() {
     return {
-      siteTitle: 'Wiki.js',
       metaRobotsSelection: ['Index', 'Follow'],
       metaRobots: ['Index', 'Follow', 'No Index', 'No Follow'],
       useSquareLogo: false,
       displayMascot: true
     }
+  },
+  computed: {
+    siteTitle: sync('site/title'),
+    company: sync('site/company')
   }
 }
 </script>

+ 3 - 5
client/components/common/nav-header.vue

@@ -83,9 +83,7 @@
 </template>
 
 <script>
-import { mapGetters } from 'vuex'
-
-/* global siteConfig */
+import { get } from 'vuex-pathify'
 
 export default {
   props: {
@@ -107,8 +105,8 @@ export default {
     }
   },
   computed: {
-    ...mapGetters(['isLoading']),
-    title() { return siteConfig.title }
+    isLoading: get('isLoading'),
+    title: get('site/title')
   },
   created() {
     if (this.hideSearch || this.dense) {

+ 3 - 1
client/graph/admin/auth/auth-query-strategies.gql

@@ -3,9 +3,11 @@ query {
     strategies(orderBy: "title ASC") {
       isEnabled
       key
-      props
       title
+      description
       useForm
+      logo
+      website
       config {
         key
         value

+ 3 - 1
client/store/admin.js

@@ -1,8 +1,10 @@
 import { make } from 'vuex-pathify'
 
+/* global siteConfig */
+
 const state = {
   theme: {
-    dark: false
+    dark: siteConfig.darkMode
   }
 }
 

+ 5 - 1
client/store/index.js

@@ -4,6 +4,8 @@ import Vuex from 'vuex'
 import pathify from 'vuex-pathify' // eslint-disable-line import/no-duplicates
 import { make } from 'vuex-pathify' // eslint-disable-line import/no-duplicates
 
+import site from './site'
+
 Vue.use(Vuex)
 
 const state = {
@@ -46,5 +48,7 @@ export default new Vuex.Store({
     }
   },
   actions: { },
-  modules: { }
+  modules: {
+    site
+  }
 })

+ 15 - 0
client/store/site.js

@@ -0,0 +1,15 @@
+import { make } from 'vuex-pathify'
+
+/* global siteConfig */
+
+const state = {
+  company: '',
+  mascot: true,
+  title: siteConfig.title
+}
+
+export default {
+  namespaced: true,
+  state,
+  mutations: make.mutations(state)
+}

+ 60 - 57
package.json

@@ -37,20 +37,20 @@
     "node": ">=8.11"
   },
   "dependencies": {
-    "apollo-server": "2.0.0-rc.5",
-    "apollo-server-express": "2.0.0-rc.2",
+    "apollo-server": "2.0.0",
+    "apollo-server-express": "2.0.0",
     "auto-load": "3.0.0",
     "axios": "0.18.0",
     "bcryptjs-then": "1.0.1",
     "bluebird": "3.5.1",
     "body-parser": "1.18.3",
     "bugsnag": "2.4.3",
-    "bull": "3.4.2",
+    "bull": "3.4.4",
     "chalk": "2.4.1",
     "cheerio": "1.0.0-rc.2",
     "child-process-promise": "2.2.1",
     "chokidar": "2.0.4",
-    "compression": "1.7.2",
+    "compression": "1.7.3",
     "connect-redis": "3.3.3",
     "cookie-parser": "1.4.3",
     "cors": "2.8.4",
@@ -62,29 +62,29 @@
     "express-brute": "1.0.1",
     "express-brute-redis": "0.0.1",
     "express-session": "1.15.6",
-    "file-type": "8.0.0",
+    "file-type": "8.1.0",
     "filesize.js": "1.0.2",
-    "follow-redirects": "1.5.0",
-    "fs-extra": "6.0.1",
+    "follow-redirects": "1.5.2",
+    "fs-extra": "7.0.0",
     "getos": "3.1.0",
     "graphql": "0.13.2",
     "graphql-list-fields": "2.0.2",
-    "graphql-tools": "3.0.4",
-    "i18next": "11.3.3",
+    "graphql-tools": "3.1.1",
+    "i18next": "11.5.0",
     "i18next-express-middleware": "1.2.0",
     "i18next-localstorage-cache": "1.1.1",
-    "i18next-node-fs-backend": "1.0.0",
+    "i18next-node-fs-backend": "1.2.1",
     "image-size": "0.6.3",
     "ioredis": "3.2.2",
     "js-yaml": "3.12.0",
     "jsonwebtoken": "8.3.0",
-    "klaw": "2.1.1",
-    "knex": "0.15.0",
+    "klaw": "3.0.0",
+    "knex": "0.15.2",
     "lodash": "4.17.10",
-    "markdown-it": "8.4.1",
+    "markdown-it": "8.4.2",
     "markdown-it-abbr": "1.0.4",
     "markdown-it-anchor": "5.0.2",
-    "markdown-it-attrs": "2.2.0",
+    "markdown-it-attrs": "2.3.1",
     "markdown-it-emoji": "1.4.0",
     "markdown-it-expand-tabs": "1.0.13",
     "markdown-it-external-links": "0.0.6",
@@ -96,19 +96,20 @@
     "markdown-it-sup": "1.0.0",
     "markdown-it-task-lists": "2.1.1",
     "mathjax-node": "2.1.1",
-    "mime-types": "2.1.18",
+    "mime-types": "2.1.19",
     "moment": "2.22.2",
     "moment-timezone": "0.5.21",
-    "mongodb": "3.1.0",
+    "mongodb": "3.1.1",
     "mssql": "4.1.0",
     "multer": "1.3.1",
-    "mysql2": "1.5.3",
+    "mysql2": "1.6.1",
     "node-2fa": "1.1.2",
+    "node-cache": "4.2.0",
     "oauth2orize": "1.11.0",
-    "objection": "1.1.10",
-    "ora": "2.1.0",
+    "objection": "1.2.2",
+    "ora": "3.0.0",
     "passport": "0.4.0",
-    "passport-auth0": "0.6.1",
+    "passport-auth0": "1.0.0",
     "passport-azure-ad-oauth2": "0.0.4",
     "passport-cas": "0.1.1",
     "passport-discord": "0.1.3",
@@ -119,12 +120,14 @@
     "passport-ldapauth": "2.0.0",
     "passport-local": "1.0.0",
     "passport-oauth2": "1.4.0",
+    "passport-okta-oauth": "0.0.1",
+    "passport-openidconnect": "0.0.2",
     "passport-slack": "0.0.7",
     "passport-twitch": "1.0.3",
     "passport-windowslive": "1.0.2",
     "pg": "7.4.3",
     "pg-hstore": "2.3.2",
-    "pm2": "2.10.4",
+    "pm2": "3.0.3",
     "pug": "2.0.3",
     "qr-image": "3.2.0",
     "raven": "2.6.3",
@@ -135,30 +138,30 @@
     "scim-query-filter-parser": "1.1.0",
     "semver": "5.5.0",
     "serve-favicon": "2.5.0",
-    "sqlite3": "4.0.1",
+    "sqlite3": "4.0.2",
     "uuid": "3.3.2",
-    "validator": "10.4.0",
+    "validator": "10.5.0",
     "validator-as-promised": "1.0.2",
     "winston": "3.0.0",
     "yargs": "12.0.1"
   },
   "devDependencies": {
-    "@panter/vue-i18next": "0.11.0",
-    "@vue/cli": "3.0.0-rc.3",
-    "apollo-cache-inmemory": "1.2.5",
-    "apollo-client": "2.3.5",
+    "@panter/vue-i18next": "0.12.0",
+    "@vue/cli": "3.0.0-rc.10",
+    "apollo-cache-inmemory": "1.2.6",
+    "apollo-client": "2.3.7",
     "apollo-fetch": "0.7.0",
     "apollo-link": "1.2.2",
     "apollo-link-batch-http": "1.2.2",
     "apollo-link-error": "1.1.0",
     "apollo-link-http": "1.5.4",
     "apollo-link-persisted-queries": "0.2.1",
-    "autoprefixer": "8.6.4",
+    "autoprefixer": "9.1.0",
     "babel-cli": "6.26.0",
     "babel-core": "6.26.3",
-    "babel-eslint": "8.2.5",
-    "babel-jest": "23.2.0",
-    "babel-loader": "7.1.4",
+    "babel-eslint": "8.2.6",
+    "babel-jest": "23.4.2",
+    "babel-loader": "7.1.5",
     "babel-plugin-graphql-tag": "1.6.0",
     "babel-plugin-lodash": "3.3.4",
     "babel-plugin-transform-imports": "1.5.0",
@@ -170,18 +173,18 @@
     "chart.js": "2.7.2",
     "clean-webpack-plugin": "0.1.19",
     "copy-webpack-plugin": "4.5.2",
-    "css-loader": "0.28.11",
-    "cssnano": "4.0.0-rc.2",
+    "css-loader": "1.0.0",
+    "cssnano": "4.0.5",
     "duplicate-package-checker-webpack-plugin": "3.0.0",
     "epic-spinners": "1.0.3",
-    "eslint": "5.0.1",
+    "eslint": "5.2.0",
     "eslint-config-requarks": "1.0.7",
     "eslint-config-standard": "11.0.0",
     "eslint-plugin-import": "2.13.0",
-    "eslint-plugin-node": "6.0.1",
+    "eslint-plugin-node": "7.0.1",
     "eslint-plugin-promise": "3.8.0",
     "eslint-plugin-standard": "3.1.0",
-    "eslint-plugin-vue": "4.5.0",
+    "eslint-plugin-vue": "4.7.1",
     "file-loader": "1.1.11",
     "graphiql": "0.11.11",
     "graphql-persisted-document-loader": "1.0.1",
@@ -192,28 +195,28 @@
     "html-webpack-pug-plugin": "0.3.0",
     "i18next-xhr-backend": "1.5.1",
     "ignore-loader": "0.1.2",
-    "jest": "23.2.0",
+    "jest": "23.4.2",
     "jest-junit": "5.1.0",
     "js-cookie": "2.2.0",
     "lodash-webpack-plugin": "0.11.5",
     "mini-css-extract-plugin": "0.4.1",
-    "node-sass": "4.9.0",
+    "node-sass": "4.9.2",
     "offline-plugin": "5.0.5",
-    "optimize-css-assets-webpack-plugin": "4.0.3",
+    "optimize-css-assets-webpack-plugin": "5.0.0",
     "postcss-cssnext": "3.1.0",
-    "postcss-flexbugs-fixes": "3.3.1",
+    "postcss-flexbugs-fixes": "4.1.0",
     "postcss-flexibility": "2.0.0",
     "postcss-import": "11.1.0",
-    "postcss-loader": "2.1.5",
-    "postcss-preset-env": "5.2.1",
+    "postcss-loader": "2.1.6",
+    "postcss-preset-env": "5.3.0",
     "postcss-selector-parser": "5.0.0-rc.3",
     "pug-lint": "2.5.0",
     "pug-loader": "2.4.0",
     "pug-plain-loader": "1.0.0",
     "raw-loader": "0.5.1",
-    "react": "16.4.1",
-    "react-dom": "16.4.1",
-    "sass-loader": "7.0.3",
+    "react": "16.4.2",
+    "react-dom": "16.4.2",
+    "sass-loader": "7.1.0",
     "sass-resources-loader": "1.3.3",
     "script-ext-html-webpack-plugin": "2.0.1",
     "simple-progress-webpack-plugin": "1.1.2",
@@ -222,32 +225,32 @@
     "stylus-loader": "3.0.2",
     "twemoji-awesome": "1.0.6",
     "url-loader": "1.0.1",
-    "vee-validate": "2.1.0-beta.5",
-    "velocity-animate": "1.5.1",
-    "vue": "2.5.16",
+    "vee-validate": "2.1.0-beta.7",
+    "velocity-animate": "1.5.2",
+    "vue": "2.5.17",
     "vue-apollo": "3.0.0-beta.19",
     "vue-chartjs": "3.3.2",
     "vue-clipboards": "1.2.4",
     "vue-codemirror": "4.0.5",
     "vue-hot-reload-api": "2.3.0",
-    "vue-loader": "15.2.4",
-    "vue-material-design-icons": "1.5.1",
+    "vue-loader": "15.2.6",
+    "vue-material-design-icons": "1.6.0",
     "vue-moment": "4.0.0",
     "vue-router": "3.0.1",
     "vue-simple-breakpoints": "1.0.3",
-    "vue-template-compiler": "2.5.16",
+    "vue-template-compiler": "2.5.17",
     "vue-tour": "1.0.1",
     "vuedraggable": "2.16.0",
-    "vuetify": "1.1.1",
+    "vuetify": "1.1.9",
     "vuex": "3.0.1",
-    "vuex-pathify": "1.1.0",
+    "vuex-pathify": "1.1.2",
     "vuex-persistedstate": "2.5.4",
-    "webpack": "4.14.0",
+    "webpack": "4.16.4",
     "webpack-bundle-analyzer": "2.13.1",
-    "webpack-cli": "3.0.8",
+    "webpack-cli": "3.1.0",
     "webpack-dev-middleware": "3.1.3",
-    "webpack-hot-middleware": "2.22.2",
-    "webpack-merge": "4.1.3",
+    "webpack-hot-middleware": "2.22.3",
+    "webpack-merge": "4.1.4",
     "whatwg-fetch": "2.0.4",
     "write-file-webpack-plugin": "4.3.2"
   },

+ 23 - 10
server/core/auth.js

@@ -2,6 +2,13 @@ const passport = require('passport')
 const fs = require('fs-extra')
 const _ = require('lodash')
 const path = require('path')
+const NodeCache = require('node-cache')
+
+const userCache = new NodeCache({
+  stdTTL: 10,
+  checkperiod: 600,
+  deleteOnExpire: true
+})
 
 /* global WIKI */
 
@@ -17,16 +24,22 @@ module.exports = {
     })
 
     passport.deserializeUser(function (id, done) {
-      WIKI.models.users.query().findById(id).then((user) => {
-        if (user) {
-          done(null, user)
-        } else {
-          done(new Error(WIKI.lang.t('auth:errors:usernotfound')), null)
-        }
-        return true
-      }).catch((err) => {
-        done(err, null)
-      })
+      const usr = userCache.get(id)
+      if (usr) {
+        done(null, usr)
+      } else {
+        WIKI.models.users.query().findById(id).then((user) => {
+          if (user) {
+            userCache.set(id, user)
+            done(null, user)
+          } else {
+            done(new Error(WIKI.lang.t('auth:errors:usernotfound')), null)
+          }
+          return true
+        }).catch((err) => {
+          done(err, null)
+        })
+      }
     })
 
     return this

+ 1 - 0
server/core/kernel.js

@@ -47,6 +47,7 @@ module.exports = {
    * Post-Master Boot Sequence
    */
   async postBootMaster() {
+    await WIKI.models.authentication.refreshStrategiesFromDisk()
     await WIKI.auth.activateStrategies()
     await WIKI.models.storage.refreshTargetsFromDisk()
     await WIKI.queue.start()

+ 0 - 3
server/db/migrations/2.0.0.js

@@ -27,9 +27,7 @@ exports.up = knex => {
     .createTable('authentication', table => {
       table.increments('id').primary()
       table.string('key').notNullable().unique()
-      table.string('title').notNullable()
       table.boolean('isEnabled').notNullable().defaultTo(false)
-      table.boolean('useForm').notNullable().defaultTo(false)
       table.jsonb('config').notNullable()
       table.boolean('selfRegistration').notNullable().defaultTo(false)
       table.jsonb('domainWhitelist').notNullable()
@@ -108,7 +106,6 @@ exports.up = knex => {
     .createTable('storage', table => {
       table.increments('id').primary()
       table.string('key').notNullable().unique()
-      table.string('title').notNullable()
       table.boolean('isEnabled').notNullable().defaultTo(false)
       table.enum('mode', ['sync', 'push', 'pull']).notNullable().defaultTo('push')
       table.jsonb('config')

+ 17 - 6
server/graph/resolvers/authentication.js

@@ -17,12 +17,23 @@ module.exports = {
   AuthenticationQuery: {
     async strategies(obj, args, context, info) {
       let strategies = await WIKI.models.authentication.getStrategies()
-      strategies = strategies.map(stg => ({
-        ...stg,
-        config: _.sortBy(_.transform(stg.config, (res, value, key) => {
-          res.push({ key, value: JSON.stringify(value) })
-        }, []), 'key')
-      }))
+      strategies = strategies.map(stg => {
+        const strategyInfo = _.find(WIKI.data.authentication, ['key', stg.key]) || {}
+        return {
+          ...strategyInfo,
+          ...stg,
+          config: _.sortBy(_.transform(stg.config, (res, value, key) => {
+            const configData = _.get(strategyInfo.props, key, {})
+            res.push({
+              key,
+              value: JSON.stringify({
+                ...configData,
+                value
+              })
+            })
+          }, []), 'key')
+        }
+      })
       if (args.filter) { strategies = graphHelper.filter(strategies, args.filter) }
       if (args.orderBy) { strategies = graphHelper.orderBy(strategies, args.orderBy) }
       return strategies

+ 1 - 1
server/graph/resolvers/storage.js

@@ -15,8 +15,8 @@ module.exports = {
       let targets = await WIKI.models.storage.getTargets()
       targets = targets.map(tgt => {
         const targetInfo = _.find(WIKI.data.storage, ['key', tgt.key]) || {}
-        console.info(targetInfo)
         return {
+          ...targetInfo,
           ...tgt,
           config: _.sortBy(_.transform(tgt.config, (res, value, key) => {
             const configData = _.get(targetInfo.props, key, {})

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

@@ -51,7 +51,10 @@ type AuthenticationStrategy {
   key: String!
   props: [String]
   title: String!
+  description: String
   useForm: Boolean!
+  logo: String
+  website: String
   icon: String
   config: [KeyValuePair]
   selfRegistration: Boolean!

+ 20 - 0
server/helpers/common.js

@@ -1,3 +1,5 @@
+const _ = require('lodash')
+
 module.exports = {
   /**
    * Get default value of type
@@ -14,5 +16,23 @@ module.exports = {
       case 'boolean':
         return false
     }
+  },
+  parseModuleProps (props) {
+    return _.transform(props, (result, value, key) => {
+      let defaultValue = ''
+      if (_.isPlainObject(value)) {
+        defaultValue = !_.isNil(value.default) ? value.default : this.getTypeDefaultValue(value.type)
+      } else {
+        defaultValue = this.getTypeDefaultValue(value)
+      }
+      _.set(result, key, {
+        default: defaultValue,
+        type: (value.type || value).toLowerCase(),
+        title: value.title || _.startCase(key),
+        hint: value.hint || false,
+        enum: value.enum || false
+      })
+      return result
+    }, {})
   }
 }

+ 0 - 13
server/master.js

@@ -139,19 +139,6 @@ module.exports = async () => {
 
   app.use('/', ctrl.auth)
 
-  // app.use('/graphql', (req, res, next) => {
-  //   graphqlApollo.graphqlExpress({
-  //     schema: graphqlSchema,
-  //     context: { req, res },
-  //     formatError: (err) => {
-  //       return {
-  //         message: err.message
-  //       }
-  //     }
-  //   })(req, res, next)
-  // })
-  // app.use('/graphiql', graphqlApollo.graphiqlExpress({ endpointURL: '/graphql' }))
-
   app.use('/', mw.auth, ctrl.common)
 
   // ----------------------------------------

+ 18 - 22
server/models/authentication.js

@@ -16,14 +16,12 @@ module.exports = class Authentication extends Model {
   static get jsonSchema () {
     return {
       type: 'object',
-      required: ['key', 'title', 'isEnabled', 'useForm'],
+      required: ['key', 'isEnabled'],
 
       properties: {
         id: {type: 'integer'},
         key: {type: 'string'},
-        title: {type: 'string'},
         isEnabled: {type: 'boolean'},
-        useForm: {type: 'boolean'},
         config: {type: 'object'},
         selfRegistration: {type: 'boolean'},
         domainWhitelist: {type: 'object'},
@@ -52,39 +50,37 @@ module.exports = class Authentication extends Model {
         const def = await fs.readFile(path.join(WIKI.SERVERPATH, 'modules/authentication', dir, 'definition.yml'), 'utf8')
         diskStrategies.push(yaml.safeLoad(def))
       }
+      WIKI.data.authentication = diskStrategies.map(strategy => ({
+        ...strategy,
+        props: commonHelper.parseModuleProps(strategy.props)
+      }))
 
       let newStrategies = []
-      _.forEach(diskStrategies, strategy => {
+      for (let strategy of WIKI.data.authentication) {
         if (!_.some(dbStrategies, ['key', strategy.key])) {
           newStrategies.push({
             key: strategy.key,
-            title: strategy.title,
             isEnabled: false,
-            useForm: strategy.useForm,
             config: _.transform(strategy.props, (result, value, key) => {
-              if (_.isPlainObject(value)) {
-                let cfgValue = {
-                  type: value.type.toLowerCase(),
-                  value: !_.isNil(value.default) ? value.default : commonHelper.getTypeDefaultValue(value.type)
-                }
-                if (_.isArray(value.enum)) {
-                  cfgValue.enum = value.enum
-                }
-                _.set(result, key, cfgValue)
-              } else {
-                _.set(result, key, {
-                  type: value.toLowerCase(),
-                  value: commonHelper.getTypeDefaultValue(value)
-                })
-              }
+              _.set(result, key, value.default)
               return result
             }, {}),
             selfRegistration: false,
             domainWhitelist: { v: [] },
             autoEnrollGroups: { v: [] }
           })
+        } else {
+          const strategyConfig = _.get(_.find(dbStrategies, ['key', strategy.key]), 'config', {})
+          await WIKI.models.authentication.query().patch({
+            config: _.transform(strategy.props, (result, value, key) => {
+              if (!_.has(result, key)) {
+                _.set(result, key, value.default)
+              }
+              return result
+            }, strategyConfig)
+          }).where('key', strategy.key)
         }
-      })
+      }
       if (newStrategies.length > 0) {
         await WIKI.models.authentication.query().insert(newStrategies)
         WIKI.logger.info(`Loaded ${newStrategies.length} new authentication strategies: [ OK ]`)

+ 2 - 20
server/models/storage.js

@@ -16,12 +16,11 @@ module.exports = class Storage extends Model {
   static get jsonSchema () {
     return {
       type: 'object',
-      required: ['key', 'title', 'isEnabled'],
+      required: ['key', 'isEnabled'],
 
       properties: {
         id: {type: 'integer'},
         key: {type: 'string'},
-        title: {type: 'string'},
         isEnabled: {type: 'boolean'},
         mode: {type: 'string'},
         config: {type: 'object'}
@@ -46,22 +45,7 @@ module.exports = class Storage extends Model {
       }
       WIKI.data.storage = diskTargets.map(target => ({
         ...target,
-        props: _.transform(target.props, (result, value, key) => {
-          let defaultValue = ''
-          if (_.isPlainObject(value)) {
-            defaultValue = !_.isNil(value.default) ? value.default : commonHelper.getTypeDefaultValue(value.type)
-          } else {
-            defaultValue = commonHelper.getTypeDefaultValue(value)
-          }
-          _.set(result, key, {
-            default: defaultValue,
-            type: (value.type || value).toLowerCase(),
-            title: value.title || _.startCase(key),
-            hint: value.hint || false,
-            enum: value.enum || false
-          })
-          return result
-        }, {})
+        props: commonHelper.parseModuleProps(target.props)
       }))
 
       // -> Insert new targets
@@ -70,7 +54,6 @@ module.exports = class Storage extends Model {
         if (!_.some(dbTargets, ['key', target.key])) {
           newTargets.push({
             key: target.key,
-            title: target.title,
             isEnabled: false,
             mode: 'push',
             config: _.transform(target.props, (result, value, key) => {
@@ -81,7 +64,6 @@ module.exports = class Storage extends Model {
         } else {
           const targetConfig = _.get(_.find(dbTargets, ['key', target.key]), 'config', {})
           await WIKI.models.storage.query().patch({
-            title: target.title,
             config: _.transform(target.props, (result, value, key) => {
               if (!_.has(result, key)) {
                 _.set(result, key, value.default)

+ 3 - 0
server/modules/authentication/auth0/definition.yml

@@ -1,6 +1,9 @@
 key: auth0
 title: Auth0
+description: Auth0 provides universal identity platform for web, mobile, IoT, and internal applications.
 author: requarks.io
+logo: https://static.requarks.io/logo/auth0.svg
+website: https://auth0.com/
 useForm: false
 props:
   domain: String

+ 3 - 0
server/modules/authentication/azure/definition.yml

@@ -1,6 +1,9 @@
 key: azure
 title: Azure Active Directory
+description: Azure Active Directory (Azure AD) is Microsoft’s multi-tenant, cloud-based directory, and identity management service that combines core directory services, application access management, and identity protection into a single solution.
 author: requarks.io
+logo: https://static.requarks.io/logo/azure.svg
+website: https://azure.microsoft.com/services/active-directory/
 useForm: false
 props:
   clientId: String

+ 3 - 0
server/modules/authentication/cas/definition.yml

@@ -1,6 +1,9 @@
 key: cas
 title: CAS
+description: The Central Authentication Service (CAS) is a single sign-on protocol for the web.
 author: requarks.io
+logo: https://static.requarks.io/logo/cas.svg
+website: https://wiki.js.org
 useForm: false
 props:
   ssoBaseURL: String

+ 3 - 0
server/modules/authentication/discord/definition.yml

@@ -1,6 +1,9 @@
 key: discord
 title: Discord
+description: Discord is a proprietary freeware VoIP application designed for gaming communities, that specializes in text, video and audio communication between users in a chat channel.
 author: requarks.io
+logo: https://static.requarks.io/logo/discord.svg
+website: https://discordapp.com/
 useForm: false
 props:
   clientId: String

+ 3 - 0
server/modules/authentication/dropbox/definition.yml

@@ -1,6 +1,9 @@
 key: dropbox
 title: Dropbox
+description: Dropbox is a file hosting service that offers cloud storage, file synchronization, personal cloud, and client software.
 author: requarks.io
+logo: https://static.requarks.io/logo/dropbox.svg
+website: https://dropbox.com
 useForm: false
 props:
   clientId: String

+ 3 - 0
server/modules/authentication/facebook/definition.yml

@@ -1,6 +1,9 @@
 key: facebook
 title: Facebook
+description: Facebook is an online social media and social networking service company.
 author: requarks.io
+logo: https://static.requarks.io/logo/facebook.svg
+website: https://facebook.com/
 useForm: false
 props:
   clientId: String

+ 3 - 0
server/modules/authentication/github/definition.yml

@@ -1,6 +1,9 @@
 key: github
 title: GitHub
+description: GitHub Inc. is a web-based hosting service for version control using Git.
 author: requarks.io
+logo: https://static.requarks.io/logo/github.svg
+website: https://github.com
 useForm: false
 props:
   clientId: String

+ 3 - 0
server/modules/authentication/google/definition.yml

@@ -1,6 +1,9 @@
 key: google
 title: Google
+description: Google specializes in Internet-related services and products, which include online advertising technologies, search engine, cloud computing, software, and hardware.
 author: requarks.io
+logo: https://static.requarks.io/logo/google.svg
+website: https://console.developers.google.com/
 useForm: false
 props:
   clientId: String

+ 16 - 2
server/modules/authentication/ldap/definition.yml

@@ -1,22 +1,36 @@
 key: ldap
 title: LDAP / Active Directory
+description: Active Directory is a directory service that Microsoft developed for the Windows domain networks.
 author: requarks.io
+logo: https://static.requarks.io/logo/active-directory.svg
+website: https://www.microsoft.com/windowsserver
 useForm: true
 props:
   url:
+    title: URL
     type: String
     default: 'ldap://serverhost:389'
+    hint: (e.g. ldap://serverhost:389)
   bindDn:
+    title: Bind DN
     type: String
     default: cn='root'
-  bindCredentials: String
+    hint: The dstinguished name (dn) of the account used for binding.
+  bindCredentials:
+    type: String
+    hint: The password of the account used for binding.
   searchBase:
     type: String
     default: 'o=users,o=example.com'
   searchFilter:
     type: String
     default: '(uid={{username}})'
+    hint: The query to use to match username. {{username}} must be present.
   tlsEnabled:
+    title: Use TLS
     type: Boolean
     default: false
-  tlsCertPath: String
+  tlsCertPath:
+    title: TLS Certificate Path
+    type: String
+    hint: Absolute path to the TLS certificate on the server.

+ 3 - 0
server/modules/authentication/local/definition.yml

@@ -1,5 +1,8 @@
 key: local
 title: Local
+description: Built-in authentication for Wiki.js
 author: requarks.io
+logo: https://static.requarks.io/logo/wikijs.svg
+website: https://wiki.js.org
 useForm: true
 props: {}

+ 4 - 1
server/modules/authentication/microsoft/definition.yml

@@ -1,6 +1,9 @@
 key: microsoft
-title: Microsoft Account
+title: Microsoft
+description: Microsoft is a software company, best known for it's Windows, Office, Azure, Xbox and Surface products.
 author: requarks.io
+logo: https://static.requarks.io/logo/microsoft.svg
+website: https://apps.dev.microsoft.com/
 useForm: false
 props:
   clientId: String

+ 4 - 1
server/modules/authentication/oauth2/definition.yml

@@ -1,6 +1,9 @@
 key: oauth2
-title: OAuth2
+title: Generic OAuth2
+description: OAuth 2 is an authorization framework that enables applications to obtain limited access to user accounts on an HTTP service.
 author: requarks.io
+logo: https://static.requarks.io/logo/oauth2.svg
+website: https://oauth.net/2/
 useForm: false
 props:
   clientId: String

+ 35 - 0
server/modules/authentication/oidc/authentication.js

@@ -0,0 +1,35 @@
+const _ = require('lodash')
+
+/* global WIKI */
+
+// ------------------------------------
+// OpenID Connect Account
+// ------------------------------------
+
+const OpenIDConnectStrategy = require('passport-openidconnect').Strategy
+
+module.exports = {
+  init (passport, conf) {
+    passport.use('oidc',
+      new OpenIDConnectStrategy({
+        authorizationURL: conf.authorizationURL,
+        tokenURL: conf.tokenURL,
+        clientID: conf.clientId,
+        clientSecret: conf.clientSecret,
+        issuer: conf.issuer,
+        callbackURL: conf.callbackURL
+      }, (iss, sub, profile, jwtClaims, accessToken, refreshToken, params, cb) => {
+        WIKI.models.users.processProfile({
+          id: jwtClaims.sub,
+          provider: 'oidc',
+          email: _.get(jwtClaims, conf.emailClaim),
+          name: _.get(jwtClaims, conf.usernameClaim)
+        }).then((user) => {
+          return cb(null, user) || true
+        }).catch((err) => {
+          return cb(err, null) || true
+        })
+      })
+    )
+  }
+}

+ 16 - 0
server/modules/authentication/oidc/definition.yml

@@ -0,0 +1,16 @@
+key: oidc
+title: Generic OpenID Connect
+description: OpenID Connect 1.0 is a simple identity layer on top of the OAuth 2.0 protocol.
+author: requarks.io
+logo: https://static.requarks.io/logo/oidc.svg
+website: http://openid.net/connect/
+useForm: false
+props:
+  clientId: String
+  clientSecret: String
+  authorizationURL: String
+  tokenURL: String
+  issuer: String
+  userInfoUrl: String
+  emailClaim: String
+  usernameClaim: String

+ 29 - 0
server/modules/authentication/okta/authentication.js

@@ -0,0 +1,29 @@
+/* global WIKI */
+
+// ------------------------------------
+// Okta Account
+// ------------------------------------
+
+const OktaStrategy = require('passport-okta-oauth').Strategy
+
+module.exports = {
+  init (passport, conf) {
+    passport.use('okta',
+      new OktaStrategy({
+        audience: conf.audience,
+        clientID: conf.clientId,
+        clientSecret: conf.clientSecret,
+        idp: conf.idp,
+        callbackURL: conf.callbackURL,
+        response_type: 'code',
+        scope: ['openid', 'email', 'profile']
+      }, (accessToken, refreshToken, profile, cb) => {
+        WIKI.models.users.processProfile(profile).then((user) => {
+          return cb(null, user) || true
+        }).catch((err) => {
+          return cb(err, null) || true
+        })
+      })
+    )
+  }
+}

+ 21 - 0
server/modules/authentication/okta/definition.yml

@@ -0,0 +1,21 @@
+key: okta
+title: Okta
+description: Okta provide secure identity management and single sign-on to any application.
+author: requarks.io
+logo: https://static.requarks.io/logo/okta.svg
+website: https://www.okta.com/
+useForm: false
+props:
+  clientId:
+    type: String
+    hint: 20 chars alphanumeric string
+  clientSecret:
+    type: String
+    hint: 40 chars alphanumeric string with a hyphen(s)
+  idp:
+    title: Identity Provider ID (idp)
+    type: String
+    hint: (optional) 20 chars alphanumeric string
+  audience:
+    type: String
+    hint: Okta domain (e.g. https://example.okta.com, https://example.oktapreview.com)

+ 3 - 0
server/modules/authentication/slack/definition.yml

@@ -1,6 +1,9 @@
 key: slack
 title: Slack
+description: Slack is a cloud-based set of proprietary team collaboration tools and services.
 author: requarks.io
+logo: https://static.requarks.io/logo/slack.svg
+website: https://api.slack.com/docs/oauth
 useForm: false
 props:
   clientId: String

+ 3 - 0
server/modules/authentication/twitch/definition.yml

@@ -1,6 +1,9 @@
 key: twitch
 title: Twitch
+description: Twitch is a live streaming video platform.
 author: requarks.io
+logo: https://static.requarks.io/logo/twitch.svg
+website: https://dev.twitch.tv/docs/authentication/
 useForm: false
 props:
   clientId: String

+ 1 - 1
server/views/main/welcome.pug

@@ -4,7 +4,7 @@ block body
   #app.is-fullscreen
     v-app
       .onboarding
-        img.animated.zoomIn(src='/svg/logo-wikijs.svg', alt='Wiki.js')
+        img.animated.fadeIn(src='/svg/logo-wikijs.svg', alt='Wiki.js')
         .headline= t('welcome.title')
         .subheading.mt-3= t('welcome.subtitle')
         v-btn.mt-5(color='primary', href='/e/home', large)

文件差异内容过多而无法显示
+ 311 - 354
yarn.lock


部分文件因为文件数量过多而无法显示