feat: auth self-registration config + gql grouping

This commit is contained in:
NGPixel 2018-06-25 02:44:40 -04:00
parent 49834461a6
commit 0afa65fa58
39 changed files with 104 additions and 50 deletions

View File

@ -9,7 +9,8 @@
v-tab-item(key='settings', :transition='false', :reverse-transition='false') v-tab-item(key='settings', :transition='false', :reverse-transition='false')
v-card.pa-3(flat, tile) v-card.pa-3(flat, tile)
v-subheader.pl-0.pb-2 Select which authentication strategies to enable: .body-2.grey--text.text--darken-1 Select which authentication strategies to enable:
.caption.grey--text.pb-2 Some strategies require additional configuration in their dedicated tab (when selected).
v-form v-form
v-checkbox( v-checkbox(
v-for='strategy in strategies', v-for='strategy in strategies',
@ -27,19 +28,36 @@
v-form v-form
v-subheader.pl-0 Strategy Configuration 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. .body-1.ml-3(v-if='!strategy.config || strategy.config.length < 1') This strategy has no configuration options you can modify.
v-text-field(v-else, v-for='cfg in strategy.config', :key='cfg.key', :label='cfg.key', prepend-icon='settings_applications') v-text-field(
v-else
v-for='cfg in strategy.config'
:key='cfg.key'
:label='cfg.key'
v-model='cfg.value'
prepend-icon='settings_applications'
)
v-divider v-divider
v-subheader.pl-0 Registration v-subheader.pl-0 Registration
v-switch.ml-3( v-switch.ml-3(
v-model='auths', v-model='allowSelfRegistration',
label='Allow self-registration', label='Allow self-registration',
:value='true', :value='true',
color='primary', color='primary',
hint='Allow any user successfully authorized by the strategy to access the wiki.', hint='Allow any user successfully authorized by the strategy to access the wiki.',
persistent-hint persistent-hint
) )
v-text-field.ml-3(label='Limit to specific email domains', prepend-icon='mail_outline') v-text-field.ml-3(
v-text-field.ml-3(label='Assign to group', prepend-icon='people') label='Limit to specific email domains'
prepend-icon='mail_outline'
hint='Domain(s) seperated by comma. (e.g. domain1.com, domain2.com)'
persistent-hint
)
v-text-field.ml-3(
label='Assign to group'
prepend-icon='people'
hint='Automatically assign new users to these groups.'
persistent-hint
)
v-card-chin v-card-chin
v-btn(color='primary') v-btn(color='primary')
@ -54,14 +72,17 @@
<script> <script>
import _ from 'lodash' import _ from 'lodash'
import strategiesQuery from 'gql/admin-auth-query-strategies.gql' import strategiesQuery from 'gql/admin/auth/auth-query-strategies.gql'
import strategiesSaveMutation from 'gql/admin-auth-mutation-save-strategies.gql' import strategiesSaveMutation from 'gql/admin/auth/auth-mutation-save-strategies.gql'
export default { export default {
data() { data() {
return { return {
strategies: [], strategies: [],
selectedStrategies: ['local'] selectedStrategies: ['local'],
selfRegistration: false,
domainWhitelist: [],
autoEnrollGroups: []
} }
}, },
computed: { computed: {

View File

@ -114,11 +114,11 @@
import Criterias from '../common/criterias.vue' import Criterias from '../common/criterias.vue'
import UserSearch from '../common/user-search.vue' import UserSearch from '../common/user-search.vue'
import groupQuery from 'gql/admin-groups-query-single.gql' import groupQuery from 'gql/admin/groups/groups-query-single.gql'
import assignUserMutation from 'gql/admin-groups-mutation-assign.gql' import assignUserMutation from 'gql/admin/groups/groups-mutation-assign.gql'
import deleteGroupMutation from 'gql/admin-groups-mutation-delete.gql' import deleteGroupMutation from 'gql/admin/groups/groups-mutation-delete.gql'
import unassignUserMutation from 'gql/admin-groups-mutation-unassign.gql' import unassignUserMutation from 'gql/admin/groups/groups-mutation-unassign.gql'
import updateGroupMutation from 'gql/admin-groups-mutation-update.gql' import updateGroupMutation from 'gql/admin/groups/groups-mutation-update.gql'
export default { export default {
components: { components: {

View File

@ -13,7 +13,7 @@
.dialog-header.is-short New Group .dialog-header.is-short New Group
v-card-text v-card-text
v-text-field(v-model='newGroupName', label='Group Name', autofocus, counter='255', @keyup.enter='createGroup') v-text-field(v-model='newGroupName', label='Group Name', autofocus, counter='255', @keyup.enter='createGroup')
v-card-actions v-card-chin
v-spacer v-spacer
v-btn(flat, @click='newGroupDialog = false') Cancel v-btn(flat, @click='newGroupDialog = false') Cancel
v-btn(color='primary', @click='createGroup') Create v-btn(color='primary', @click='createGroup') Create
@ -45,9 +45,9 @@
<script> <script>
import _ from 'lodash' import _ from 'lodash'
import groupsQuery from 'gql/admin-groups-query-list.gql' import groupsQuery from 'gql/admin/groups/groups-query-list.gql'
import createGroupMutation from 'gql/admin-groups-mutation-create.gql' import createGroupMutation from 'gql/admin/groups/groups-mutation-create.gql'
import deleteGroupMutation from 'gql/admin-groups-mutation-delete.gql' import deleteGroupMutation from 'gql/admin/groups/groups-mutation-delete.gql'
export default { export default {
data() { data() {

View File

@ -128,9 +128,9 @@ import _ from 'lodash'
/* global WIKI */ /* global WIKI */
import localesQuery from 'gql/admin-locale-query-list.gql' import localesQuery from 'gql/admin/locale/locale-query-list.gql'
import localesDownloadMutation from 'gql/admin-locale-mutation-download.gql' import localesDownloadMutation from 'gql/admin/locale/locale-mutation-download.gql'
import localesSaveMutation from 'gql/admin-locale-mutation-save.gql' import localesSaveMutation from 'gql/admin/locale/locale-mutation-save.gql'
export default { export default {
data() { data() {

View File

@ -106,7 +106,7 @@ import IconCube from 'mdi/cube'
import IconDatabase from 'mdi/database' import IconDatabase from 'mdi/database'
import IconNodeJs from 'mdi/nodejs' import IconNodeJs from 'mdi/nodejs'
import systemInfoQuery from 'gql/admin-system-query-info.gql' import systemInfoQuery from 'gql/admin/system/system-query-info.gql'
export default { export default {
components: { components: {

View File

@ -50,7 +50,7 @@
<script> <script>
import _ from 'lodash' import _ from 'lodash'
import themeSaveMutation from 'gql/admin-theme-mutation-save.gql' import themeSaveMutation from 'gql/admin/theme/theme-mutation-save.gql'
export default { export default {
data() { data() {

View File

@ -48,7 +48,7 @@
<script> <script>
import _ from 'lodash' import _ from 'lodash'
import searchUsersQuery from 'gql/common-users-query-search.gql' import searchUsersQuery from 'gql/common/common-users-query-search.gql'
export default { export default {
filters: { filters: {

View File

@ -56,9 +56,9 @@
import _ from 'lodash' import _ from 'lodash'
import { mapState } from 'vuex' import { mapState } from 'vuex'
import strategiesQuery from 'gql/login-query-strategies.gql' import strategiesQuery from 'gql/login/login-query-strategies.gql'
import loginMutation from 'gql/login-mutation-login.gql' import loginMutation from 'gql/login/login-mutation-login.gql'
import tfaMutation from 'gql/login-mutation-tfa.gql' import tfaMutation from 'gql/login/login-mutation-tfa.gql'
export default { export default {
i18nOptions: { namespaces: 'auth' }, i18nOptions: { namespaces: 'auth' },

View File

@ -1,5 +1,5 @@
<template lang='pug'> <template lang='pug'>
v-app.profile v-app(:dark='darkMode').profile
nav-header nav-header
v-navigation-drawer.pb-0(v-model='profileDrawerShown', app, fixed, clipped, left, permanent) v-navigation-drawer.pb-0(v-model='profileDrawerShown', app, fixed, clipped, left, permanent)
v-list(dense) v-list(dense)
@ -22,7 +22,7 @@
transition(name='profile-router') transition(name='profile-router')
router-view router-view
v-footer.py-2.justify-center(app, absolute, color='grey lighten-3', inset, height='auto') v-footer.py-2.justify-center(app, absolute, :color='darkMode ? "" : "grey lighten-3"', inset, height='auto')
.caption.grey--text.text--darken-1 Powered by Wiki.js .caption.grey--text.text--darken-1 Powered by Wiki.js
v-snackbar( v-snackbar(
@ -41,7 +41,7 @@
import VueRouter from 'vue-router' import VueRouter from 'vue-router'
import { mapState } from 'vuex' import { mapState } from 'vuex'
/* global WIKI */ /* global WIKI, siteConfig */
const router = new VueRouter({ const router = new VueRouter({
mode: 'history', mode: 'history',
@ -75,7 +75,8 @@ export default {
notificationState: { notificationState: {
get() { return this.notification.isActive }, get() { return this.notification.isActive },
set(newState) { this.$store.commit('updateNotificationState', newState) } set(newState) { this.$store.commit('updateNotificationState', newState) }
} },
darkMode() { return siteConfig.darkMode }
}, },
router router
} }

View File

@ -18,8 +18,7 @@
v-divider v-divider
v-subheader.pl-0 Timezone v-subheader.pl-0 Timezone
v-select.grey.lighten-5(solo, flat) v-select.grey.lighten-5(solo, flat)
v-divider.my-0 v-card-chin
v-card-actions.grey.lighten-4
v-spacer v-spacer
v-btn(color='primary') v-btn(color='primary')
v-icon(left) chevron_right v-icon(left) chevron_right
@ -32,8 +31,7 @@
v-card-text v-card-text
v-subheader.pl-0 Default Editor v-subheader.pl-0 Default Editor
v-select.grey.lighten-5(solo, flat) v-select.grey.lighten-5(solo, flat)
v-divider.my-0 v-card-chin
v-card-actions.grey.lighten-4
v-spacer v-spacer
v-btn(color='primary') v-btn(color='primary')
v-icon(left) chevron_right v-icon(left) chevron_right
@ -42,10 +40,14 @@
</template> </template>
<script> <script>
/* global siteConfig */
export default { export default {
data() { data() {
return { } return { }
},
computed: {
darkMode() { return siteConfig.darkMode }
} }
} }
</script> </script>

View File

@ -17,8 +17,7 @@
v-text-field(label='Name', :counter='255', v-model='name', prepend-icon='person') v-text-field(label='Name', :counter='255', v-model='name', prepend-icon='person')
v-text-field(label='Job Title', :counter='255', prepend-icon='accessibility') v-text-field(label='Job Title', :counter='255', prepend-icon='accessibility')
v-text-field(label='Location / Office', :counter='255', prepend-icon='location_on') v-text-field(label='Location / Office', :counter='255', prepend-icon='location_on')
v-divider.my-0 v-card-chin
v-card-actions.grey.lighten-4
v-spacer v-spacer
v-btn(color='primary') v-btn(color='primary')
v-icon(left) chevron_right v-icon(left) chevron_right
@ -29,8 +28,13 @@
.subheading Authentication .subheading Authentication
v-card-text v-card-text
v-subheader.pl-0 Provider v-subheader.pl-0 Provider
v-toolbar(flat, color='purple lighten-5', dense).purple--text.text--darken-4 v-toolbar(
v-icon(color='purple darken-4') supervised_user_circle flat
:color='darkMode ? "grey darken-2" : "purple lighten-5"'
dense
:class='darkMode ? "grey--text text--lighten-1" : "purple--text text--darken-4"'
)
v-icon(:color='darkMode ? "grey lighten-1" : "purple darken-4"') supervised_user_circle
.subheading.ml-3 Local .subheading.ml-3 Local
v-divider v-divider
v-subheader.pl-0 Two-Factor Authentication (2FA) v-subheader.pl-0 Two-Factor Authentication (2FA)
@ -72,12 +76,16 @@
</template> </template>
<script> <script>
/* global siteConfig */
export default { export default {
data() { data() {
return { return {
name: 'John Doe' name: 'John Doe'
} }
},
computed: {
darkMode() { return siteConfig.darkMode }
} }
} }
</script> </script>

View File

@ -1,6 +1,6 @@
query { query {
authentication { authentication {
strategies { strategies(orderBy: "title ASC") {
isEnabled isEnabled
key key
props props
@ -10,6 +10,9 @@ query {
key key
value value
} }
selfRegistration
domainWhitelist
autoEnrollGroups
} }
} }
} }

View File

@ -6,7 +6,7 @@ import _ from 'lodash'
/* global siteConfig, graphQL */ /* global siteConfig, graphQL */
import localeQuery from 'gql/common-locale-query.gql' import localeQuery from 'gql/common/common-locale-query.gql'
module.exports = { module.exports = {
VueI18Next, VueI18Next,

View File

@ -39,12 +39,14 @@ module.exports = {
_.pull(currentStrategies, 'session') _.pull(currentStrategies, 'session')
_.forEach(currentStrategies, stg => { passport.unuse(stg) }) _.forEach(currentStrategies, stg => { passport.unuse(stg) })
// Load enable strategies // Load enabled strategies
const enabledStrategies = await WIKI.db.authentication.getEnabledStrategies() const enabledStrategies = await WIKI.db.authentication.getStrategies()
console.info(enabledStrategies)
for (let idx in enabledStrategies) { for (let idx in enabledStrategies) {
const stg = enabledStrategies[idx] const stg = enabledStrategies[idx]
if (!stg.isEnabled) { continue }
const strategy = require(`../modules/authentication/${stg.key}`) const strategy = require(`../modules/authentication/${stg.key}`)
stg.config.callbackURL = `${WIKI.config.host}/login/${stg.key}/callback` // TODO: config.host stg.config.callbackURL = `${WIKI.config.host}/login/${stg.key}/callback` // TODO: config.host
strategy.init(passport, stg.config) strategy.init(passport, stg.config)

View File

@ -31,6 +31,9 @@ exports.up = knex => {
table.boolean('isEnabled').notNullable().defaultTo(false) table.boolean('isEnabled').notNullable().defaultTo(false)
table.boolean('useForm').notNullable().defaultTo(false) table.boolean('useForm').notNullable().defaultTo(false)
table.jsonb('config').notNullable() table.jsonb('config').notNullable()
table.boolean('selfRegistration').notNullable().defaultTo(false)
table.jsonb('domainWhitelist').notNullable()
table.jsonb('autoEnrollGroups').notNullable()
}) })
// COMMENTS ---------------------------- // COMMENTS ----------------------------
.createTable('comments', table => { .createTable('comments', table => {

View File

@ -22,13 +22,21 @@ module.exports = class Authentication extends Model {
title: {type: 'string'}, title: {type: 'string'},
isEnabled: {type: 'boolean'}, isEnabled: {type: 'boolean'},
useForm: {type: 'boolean'}, useForm: {type: 'boolean'},
config: {type: 'object'} config: {type: 'object'},
selfRegistration: {type: 'boolean'},
domainWhitelist: {type: 'object'},
autoEnrollGroups: {type: 'object'}
} }
} }
} }
static async getEnabledStrategies() { static async getStrategies() {
return WIKI.db.authentication.query().where({ isEnabled: true }) const strategies = await WIKI.db.authentication.query()
return strategies.map(str => ({
...str,
domainWhitelist: _.get(str.domainWhitelist, 'v', []),
autoEnrollGroups: _.get(str.autoEnrollGroups, 'v', [])
}))
} }
static async refreshStrategiesFromDisk() { static async refreshStrategiesFromDisk() {
@ -46,7 +54,10 @@ module.exports = class Authentication extends Model {
config: _.reduce(strategy.props, (result, value, key) => { config: _.reduce(strategy.props, (result, value, key) => {
_.set(result, value, '') _.set(result, value, '')
return result return result
}, {}) }, {}),
selfRegistration: false,
domainWhitelist: { v: [] },
autoEnrollGroups: { v: [] }
}) })
} }
}) })

View File

@ -16,7 +16,7 @@ module.exports = {
}, },
AuthenticationQuery: { AuthenticationQuery: {
async strategies(obj, args, context, info) { async strategies(obj, args, context, info) {
let strategies = await WIKI.db.authentication.getEnabledStrategies() let strategies = await WIKI.db.authentication.getStrategies()
strategies = strategies.map(stg => ({ strategies = strategies.map(stg => ({
...stg, ...stg,
config: _.transform(stg.config, (res, value, key) => { config: _.transform(stg.config, (res, value, key) => {

View File

@ -56,6 +56,9 @@ type AuthenticationStrategy {
useForm: Boolean! useForm: Boolean!
icon: String icon: String
config: [KeyValuePair] config: [KeyValuePair]
selfRegistration: Boolean!
domainWhitelist: [String]!
autoEnrollGroups: [String]!
} }
type AuthenticationLoginResponse { type AuthenticationLoginResponse {

View File

@ -24,7 +24,7 @@ module.exports = {
return arr.filter(prvFilter.test) return arr.filter(prvFilter.test)
}, },
orderBy (arr, orderString) { orderBy (arr, orderString) {
let orderParams = _.zip(orderString.split(',').map(ord => _.trim(ord).split(' ').map(_.trim))) let orderParams = _.zip(...orderString.split(',').map(ord => _.trim(ord).split(' ').map(_.trim)))
return _.orderBy(arr, orderParams[0], orderParams[1]) return _.orderBy(arr, orderParams[0], orderParams[1])
} }
} }

View File

@ -8,7 +8,7 @@ const GoogleStrategy = require('passport-google-oauth20').Strategy
module.exports = { module.exports = {
key: 'google', key: 'google',
title: 'Google ID', title: 'Google',
useForm: false, useForm: false,
props: ['clientId', 'clientSecret'], props: ['clientId', 'clientSecret'],
init (passport, conf) { init (passport, conf) {