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-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-checkbox(
v-for='strategy in strategies',
@ -27,19 +28,36 @@
v-form
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.
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-subheader.pl-0 Registration
v-switch.ml-3(
v-model='auths',
v-model='allowSelfRegistration',
label='Allow self-registration',
:value='true',
color='primary',
hint='Allow any user successfully authorized by the strategy to access the wiki.',
persistent-hint
)
v-text-field.ml-3(label='Limit to specific email domains', prepend-icon='mail_outline')
v-text-field.ml-3(label='Assign to group', prepend-icon='people')
v-text-field.ml-3(
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-btn(color='primary')
@ -54,14 +72,17 @@
<script>
import _ from 'lodash'
import strategiesQuery from 'gql/admin-auth-query-strategies.gql'
import strategiesSaveMutation from 'gql/admin-auth-mutation-save-strategies.gql'
import strategiesQuery from 'gql/admin/auth/auth-query-strategies.gql'
import strategiesSaveMutation from 'gql/admin/auth/auth-mutation-save-strategies.gql'
export default {
data() {
return {
strategies: [],
selectedStrategies: ['local']
selectedStrategies: ['local'],
selfRegistration: false,
domainWhitelist: [],
autoEnrollGroups: []
}
},
computed: {

View File

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

View File

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

View File

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

View File

@ -106,7 +106,7 @@ import IconCube from 'mdi/cube'
import IconDatabase from 'mdi/database'
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 {
components: {

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
<template lang='pug'>
v-app.profile
v-app(:dark='darkMode').profile
nav-header
v-navigation-drawer.pb-0(v-model='profileDrawerShown', app, fixed, clipped, left, permanent)
v-list(dense)
@ -22,7 +22,7 @@
transition(name='profile-router')
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
v-snackbar(
@ -41,7 +41,7 @@
import VueRouter from 'vue-router'
import { mapState } from 'vuex'
/* global WIKI */
/* global WIKI, siteConfig */
const router = new VueRouter({
mode: 'history',
@ -75,7 +75,8 @@ export default {
notificationState: {
get() { return this.notification.isActive },
set(newState) { this.$store.commit('updateNotificationState', newState) }
}
},
darkMode() { return siteConfig.darkMode }
},
router
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -31,6 +31,9 @@ exports.up = knex => {
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()
table.jsonb('autoEnrollGroups').notNullable()
})
// COMMENTS ----------------------------
.createTable('comments', table => {

View File

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

View File

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

View File

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

View File

@ -24,7 +24,7 @@ module.exports = {
return arr.filter(prvFilter.test)
},
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])
}
}

View File

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