feat: new login experience (#2139)
* feat: multiple auth instances * fix: auth setup + strategy initialization * feat: admin auth - add strategy * feat: redirect on login - group setting * feat: oauth2 generic - props definitions * feat: new login UI (wip) * feat: new login UI (wip) * feat: admin security login settings * feat: tabset editor indicators + print view improvements * fix: code styling
This commit is contained in:
parent
1c4829f70f
commit
c009cc1392
7
client/.modernizrrc.js
Normal file
7
client/.modernizrrc.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
module.exports = {
|
||||||
|
classPrefix: 'mdz-',
|
||||||
|
options: ['setClasses'],
|
||||||
|
'feature-detects': [
|
||||||
|
'css/backdropfilter'
|
||||||
|
]
|
||||||
|
}
|
@ -129,7 +129,7 @@
|
|||||||
v-list-item-avatar(size='24', tile): v-icon mdi-heart-outline
|
v-list-item-avatar(size='24', tile): v-icon mdi-heart-outline
|
||||||
v-list-item-title {{ $t('admin:contribute.title') }}
|
v-list-item-title {{ $t('admin:contribute.title') }}
|
||||||
|
|
||||||
v-content(:class='$vuetify.theme.dark ? "grey darken-5" : "grey lighten-5"')
|
v-main(:class='$vuetify.theme.dark ? "grey darken-5" : "grey lighten-5"')
|
||||||
transition(name='admin-router')
|
transition(name='admin-router')
|
||||||
router-view
|
router-view
|
||||||
|
|
||||||
|
@ -18,82 +18,75 @@
|
|||||||
|
|
||||||
v-flex(lg3, xs12)
|
v-flex(lg3, xs12)
|
||||||
v-card.animated.fadeInUp
|
v-card.animated.fadeInUp
|
||||||
v-toolbar(flat, color='primary', dark, dense)
|
v-toolbar(flat, color='teal', dark, dense)
|
||||||
.subtitle-1 {{$t('admin:auth.strategies')}}
|
.subtitle-1 {{$t('admin:auth.activeStrategies')}}
|
||||||
v-list(two-line, dense).py-0
|
v-list(two-line, dense).py-0
|
||||||
template(v-for='(str, idx) in strategies')
|
draggable(
|
||||||
v-list-item(:key='str.key', @click='selectedStrategy = str.key', :disabled='!str.isAvailable')
|
v-model='activeStrategies'
|
||||||
v-list-item-avatar(size='24')
|
handle='.is-handle'
|
||||||
v-icon(color='grey', v-if='!str.isAvailable') mdi-minus-box-outline
|
direction='vertical'
|
||||||
v-icon(color='primary', v-else-if='str.isEnabled && str.key !== `local`', v-ripple, @click='str.isEnabled = false') mdi-checkbox-marked-outline
|
:store='order'
|
||||||
v-icon(color='primary', v-else-if='str.isEnabled && str.key === `local`') mdi-checkbox-marked-outline
|
)
|
||||||
v-icon(color='grey', v-else, v-ripple, @click='str.isEnabled = true') mdi-checkbox-blank-outline
|
transition-group
|
||||||
|
v-list-item(
|
||||||
|
v-for='(str, idx) in activeStrategies'
|
||||||
|
:key='str.key'
|
||||||
|
@click='selectedStrategy = str.key'
|
||||||
|
:class='selectedStrategy === str.key ? ($vuetify.theme.dark ? `grey darken-5` : `teal lighten-5`) : ``'
|
||||||
|
)
|
||||||
|
v-list-item-avatar.is-handle(size='24')
|
||||||
|
v-icon(:color='selectedStrategy === str.key ? `teal` : `grey`') mdi-drag-horizontal
|
||||||
v-list-item-content
|
v-list-item-content
|
||||||
v-list-item-title.body-2(:class='!str.isAvailable ? `grey--text` : (selectedStrategy === str.key ? `primary--text` : ``)') {{ str.title }}
|
v-list-item-title.body-2(:class='selectedStrategy === str.key ? `teal--text` : ``') {{ str.displayName }}
|
||||||
v-list-item-subtitle: .caption(:class='!str.isAvailable ? `grey--text text--lighten-1` : (selectedStrategy === str.key ? `blue--text ` : ``)') {{ str.description }}
|
v-list-item-subtitle: .caption(:class='selectedStrategy === str.key ? `teal--text ` : ``') {{ str.strategy.title }}
|
||||||
v-list-item-avatar(v-if='selectedStrategy === str.key', size='24')
|
v-list-item-avatar(v-if='selectedStrategy === str.key', size='24')
|
||||||
v-icon.animated.fadeInLeft(color='primary', large) mdi-chevron-right
|
v-icon.animated.fadeInLeft(color='teal', large) mdi-chevron-right
|
||||||
|
v-card-chin
|
||||||
|
v-menu(offset-y, bottom, min-width='250px', max-width='550px', max-height='50vh', style='flex: 1 1;', center)
|
||||||
|
template(v-slot:activator='{ on }')
|
||||||
|
v-btn(v-on='on', color='primary', depressed, block)
|
||||||
|
v-icon(left) mdi-plus
|
||||||
|
span {{$t('admin:auth.addStrategy')}}
|
||||||
|
v-list(dense)
|
||||||
|
template(v-for='(str, idx) of strategies')
|
||||||
|
v-list-item(
|
||||||
|
:key='str.key'
|
||||||
|
:disabled='str.isDisabled'
|
||||||
|
@click='addStrategy(str)'
|
||||||
|
)
|
||||||
|
v-list-item-avatar(height='24', width='48', tile)
|
||||||
|
v-img(:src='str.logo', width='48px', height='24px', contain, :style='str.isDisabled ? `opacity: .25;` : ``')
|
||||||
|
v-list-item-content
|
||||||
|
v-list-item-title {{str.title}}
|
||||||
|
v-list-item-subtitle: .caption(:style='str.isDisabled ? `opacity: .4;` : ``') {{str.description}}
|
||||||
v-divider(v-if='idx < strategies.length - 1')
|
v-divider(v-if='idx < strategies.length - 1')
|
||||||
|
|
||||||
v-card.mt-3.animated.fadeInUp.wait-p2s
|
|
||||||
v-toolbar(flat, color='primary', dark, dense)
|
|
||||||
.subtitle-1 {{$t('admin:auth.globalAdvSettings')}}
|
|
||||||
v-card-text
|
|
||||||
v-text-field.md2(
|
|
||||||
v-model='jwtAudience'
|
|
||||||
outlined
|
|
||||||
prepend-icon='mdi-account-group-outline'
|
|
||||||
:label='$t(`admin:auth.jwtAudience`)'
|
|
||||||
:hint='$t(`admin:auth.jwtAudienceHint`)'
|
|
||||||
persistent-hint
|
|
||||||
)
|
|
||||||
v-text-field.mt-3.md2(
|
|
||||||
v-model='jwtExpiration'
|
|
||||||
outlined
|
|
||||||
prepend-icon='mdi-clock-outline'
|
|
||||||
:label='$t(`admin:auth.tokenExpiration`)'
|
|
||||||
:hint='$t(`admin:auth.tokenExpirationHint`)'
|
|
||||||
persistent-hint
|
|
||||||
)
|
|
||||||
v-text-field.mt-3.md2(
|
|
||||||
v-model='jwtRenewablePeriod'
|
|
||||||
outlined
|
|
||||||
prepend-icon='mdi-update'
|
|
||||||
:label='$t(`admin:auth.tokenRenewalPeriod`)'
|
|
||||||
:hint='$t(`admin:auth.tokenRenewalPeriodHint`)'
|
|
||||||
persistent-hint
|
|
||||||
)
|
|
||||||
|
|
||||||
v-flex(xs12, lg9)
|
v-flex(xs12, lg9)
|
||||||
v-card.animated.fadeInUp.wait-p2s
|
v-card.animated.fadeInUp.wait-p2s
|
||||||
v-toolbar(color='primary', dense, flat, dark)
|
v-toolbar(color='primary', dense, flat, dark)
|
||||||
.subtitle-1 {{strategy.title}}
|
.subtitle-1 {{strategy.displayName}} #[em ({{strategy.strategy.title}})]
|
||||||
|
v-spacer
|
||||||
|
v-btn(small, outlined, dark, color='white', :disabled='strategy.key === `local`', @click='deleteStrategy()')
|
||||||
|
v-icon(left) mdi-close
|
||||||
|
span {{$t('common:actions.delete')}}
|
||||||
|
v-card-info(color='blue')
|
||||||
|
div
|
||||||
|
span {{strategy.strategy.description}}
|
||||||
|
.caption: a(:href='strategy.strategy.website') {{strategy.strategy.website}}
|
||||||
v-spacer
|
v-spacer
|
||||||
v-switch(
|
|
||||||
dark
|
|
||||||
color='blue lighten-5'
|
|
||||||
label='Active'
|
|
||||||
v-model='strategy.isEnabled'
|
|
||||||
hide-details
|
|
||||||
inset
|
|
||||||
:disabled='strategy.key === `local`'
|
|
||||||
)
|
|
||||||
v-card-text
|
|
||||||
v-form
|
|
||||||
.authlogo
|
.authlogo
|
||||||
img(:src='strategy.logo', :alt='strategy.title')
|
img(:src='strategy.strategy.logo', :alt='strategy.strategy.title')
|
||||||
.body-2.pt-3 {{strategy.description}}
|
v-card-text
|
||||||
.body-2.pt-3.pb-5: a(:href='strategy.website') {{strategy.website}}
|
.overline.mb-5 {{$t('admin:auth.strategyConfiguration')}}
|
||||||
i18next.body-2(path='admin:auth.strategyState', tag='div', v-if='strategy.isEnabled')
|
v-text-field.mb-3(
|
||||||
v-chip(color='green', small, dark, label, place='state') {{$t('admin:auth.strategyStateActive')}}
|
outlined
|
||||||
span(v-if='selectedStrategy === `local`', place='locked') {{$t('admin:auth.strategyStateLocked')}}
|
label='Display Name'
|
||||||
span(v-else, place='locked', v-text='')
|
v-model='strategy.displayName'
|
||||||
i18next.body-2(path='admin:auth.strategyState', tag='div', v-else)
|
prepend-icon='mdi-format-title'
|
||||||
v-chip(color='red', small, dark, label, place='state') {{$t('admin:auth.strategyStateInactive')}}
|
hint='The title shown to the end user for this authentication strategy.'
|
||||||
v-divider.mt-3
|
persistent-hint
|
||||||
.overline.my-5 {{$t('admin:auth.strategyConfiguration')}}
|
)
|
||||||
.body-2.ml-3(v-if='!strategy.config || strategy.config.length < 1'): em {{$t('admin:auth.strategyNoConfiguration')}}
|
template(v-for='cfg in strategy.config')
|
||||||
template(v-else, v-for='cfg in strategy.config')
|
|
||||||
v-select.mb-3(
|
v-select.mb-3(
|
||||||
v-if='cfg.value.type === "string" && cfg.value.enum'
|
v-if='cfg.value.type === "string" && cfg.value.enum'
|
||||||
outlined
|
outlined
|
||||||
@ -236,13 +229,18 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
|
import gql from 'graphql-tag'
|
||||||
|
import { v4 as uuid } from 'uuid'
|
||||||
|
|
||||||
import groupsQuery from 'gql/admin/auth/auth-query-groups.gql'
|
import groupsQuery from 'gql/admin/auth/auth-query-groups.gql'
|
||||||
import strategiesQuery from 'gql/admin/auth/auth-query-strategies.gql'
|
|
||||||
import strategiesSaveMutation from 'gql/admin/auth/auth-mutation-save-strategies.gql'
|
|
||||||
import hostQuery from 'gql/admin/auth/auth-query-host.gql'
|
import hostQuery from 'gql/admin/auth/auth-query-host.gql'
|
||||||
|
|
||||||
|
import draggable from 'vuedraggable'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
components: {
|
||||||
|
draggable
|
||||||
|
},
|
||||||
filters: {
|
filters: {
|
||||||
startCase(val) { return _.startCase(val) }
|
startCase(val) { return _.startCase(val) }
|
||||||
},
|
},
|
||||||
@ -250,62 +248,107 @@ export default {
|
|||||||
return {
|
return {
|
||||||
groups: [],
|
groups: [],
|
||||||
strategies: [],
|
strategies: [],
|
||||||
|
activeStrategies: [],
|
||||||
selectedStrategy: '',
|
selectedStrategy: '',
|
||||||
host: '',
|
host: '',
|
||||||
strategy: {},
|
strategy: {
|
||||||
jwtAudience: 'urn:wiki.js',
|
strategy: {}
|
||||||
jwtExpiration: '30m',
|
}
|
||||||
jwtRenewablePeriod: '14d'
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
activeStrategies() {
|
order: {
|
||||||
return _.filter(this.strategies, 'isEnabled')
|
get () {
|
||||||
|
return this.strategies
|
||||||
|
},
|
||||||
|
set (val) {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
selectedStrategy(newValue, oldValue) {
|
selectedStrategy(newValue, oldValue) {
|
||||||
this.strategy = _.find(this.strategies, ['key', newValue]) || {}
|
this.strategy = _.find(this.activeStrategies, ['key', newValue]) || {}
|
||||||
},
|
},
|
||||||
strategies(newValue, oldValue) {
|
activeStrategies(newValue, oldValue) {
|
||||||
this.selectedStrategy = 'local'
|
this.selectedStrategy = 'local'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async refresh() {
|
async refresh() {
|
||||||
await this.$apollo.queries.strategies.refetch()
|
await this.$apollo.queries.strategies.refetch()
|
||||||
|
await this.$apollo.queries.activeStrategies.refetch()
|
||||||
this.$store.commit('showNotification', {
|
this.$store.commit('showNotification', {
|
||||||
message: this.$t('admin:auth.refreshSuccess'),
|
message: this.$t('admin:auth.refreshSuccess'),
|
||||||
style: 'success',
|
style: 'success',
|
||||||
icon: 'cached'
|
icon: 'cached'
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
addStrategy (str) {
|
||||||
|
const newStr = {
|
||||||
|
key: uuid(),
|
||||||
|
strategy: str,
|
||||||
|
config: str.props.map(c => ({
|
||||||
|
key: c.key,
|
||||||
|
value: {
|
||||||
|
...c,
|
||||||
|
value: c.default
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
order: this.activeStrategies.length,
|
||||||
|
displayName: str.title,
|
||||||
|
selfRegistration: false,
|
||||||
|
domainWhitelist: [],
|
||||||
|
autoEnrollGroups: []
|
||||||
|
}
|
||||||
|
this.activeStrategies = [...this.activeStrategies, newStr]
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.selectedStrategy = newStr.key
|
||||||
|
})
|
||||||
|
},
|
||||||
|
deleteStrategy () {
|
||||||
|
this.activeStrategies = _.reject(this.activeStrategies, ['key', this.strategy.key])
|
||||||
|
},
|
||||||
async save() {
|
async save() {
|
||||||
this.$store.commit(`loadingStart`, 'admin-auth-savestrategies')
|
this.$store.commit(`loadingStart`, 'admin-auth-savestrategies')
|
||||||
try {
|
try {
|
||||||
await this.$apollo.mutate({
|
const resp = await this.$apollo.mutate({
|
||||||
mutation: strategiesSaveMutation,
|
mutation: gql`
|
||||||
|
mutation($strategies: [AuthenticationStrategyInput]!) {
|
||||||
|
authentication {
|
||||||
|
updateStrategies(strategies: $strategies) {
|
||||||
|
responseResult {
|
||||||
|
succeeded
|
||||||
|
errorCode
|
||||||
|
slug
|
||||||
|
message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
variables: {
|
variables: {
|
||||||
config: {
|
strategies: this.activeStrategies.map(str => ({
|
||||||
audience: this.jwtAudience,
|
key: str.key,
|
||||||
tokenExpiration: this.jwtExpiration,
|
strategyKey: str.strategy.key,
|
||||||
tokenRenewal: this.jwtRenewablePeriod
|
displayName: str.displayName,
|
||||||
},
|
order: str.order,
|
||||||
strategies: this.strategies.map(str => _.pick(str, [
|
config: str.config.map(cfg => ({...cfg, value: JSON.stringify({ v: cfg.value.value })})),
|
||||||
'isEnabled',
|
selfRegistration: str.selfRegistration,
|
||||||
'key',
|
domainWhitelist: str.domainWhitelist,
|
||||||
'config',
|
autoEnrollGroups: str.autoEnrollGroups
|
||||||
'selfRegistration',
|
}))
|
||||||
'domainWhitelist',
|
|
||||||
'autoEnrollGroups'
|
|
||||||
])).map(str => ({...str, config: str.config.map(cfg => ({...cfg, value: JSON.stringify({ v: cfg.value.value })}))}))
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
if (_.get(resp, 'data.authentication.updateStrategies.responseResult.succeeded', false)) {
|
||||||
this.$store.commit('showNotification', {
|
this.$store.commit('showNotification', {
|
||||||
message: this.$t('admin:auth.saveSuccess'),
|
message: this.$t('admin:auth.saveSuccess'),
|
||||||
style: 'success',
|
style: 'success',
|
||||||
icon: 'check'
|
icon: 'check'
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
|
throw new Error(_.get(resp, 'data.authentication.updateStrategies.responseResult.message', this.$t('common:error.unexpected')))
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.$store.commit('pushGraphError', err)
|
this.$store.commit('pushGraphError', err)
|
||||||
}
|
}
|
||||||
@ -314,9 +357,67 @@ export default {
|
|||||||
},
|
},
|
||||||
apollo: {
|
apollo: {
|
||||||
strategies: {
|
strategies: {
|
||||||
query: strategiesQuery,
|
query: gql`
|
||||||
|
query {
|
||||||
|
authentication {
|
||||||
|
strategies {
|
||||||
|
key
|
||||||
|
title
|
||||||
|
description
|
||||||
|
isAvailable
|
||||||
|
useForm
|
||||||
|
logo
|
||||||
|
website
|
||||||
|
props {
|
||||||
|
key
|
||||||
|
value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
fetchPolicy: 'network-only',
|
fetchPolicy: 'network-only',
|
||||||
update: (data) => _.cloneDeep(data.authentication.strategies).map(str => ({
|
update: (data) => _.get(data, 'authentication.strategies', []).map(str => ({
|
||||||
|
...str,
|
||||||
|
isDisabled: !str.isAvailable || str.key === `local`,
|
||||||
|
props: _.sortBy(str.props.map(cfg => ({
|
||||||
|
key: cfg.key,
|
||||||
|
...JSON.parse(cfg.value)
|
||||||
|
})), [t => t.order])
|
||||||
|
})),
|
||||||
|
watchLoading (isLoading) {
|
||||||
|
this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'admin-auth-strategies-refresh')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
activeStrategies: {
|
||||||
|
query: gql`
|
||||||
|
query {
|
||||||
|
authentication {
|
||||||
|
activeStrategies {
|
||||||
|
key
|
||||||
|
strategy {
|
||||||
|
key
|
||||||
|
title
|
||||||
|
description
|
||||||
|
useForm
|
||||||
|
logo
|
||||||
|
website
|
||||||
|
}
|
||||||
|
config {
|
||||||
|
key
|
||||||
|
value
|
||||||
|
}
|
||||||
|
order
|
||||||
|
displayName
|
||||||
|
selfRegistration
|
||||||
|
domainWhitelist
|
||||||
|
autoEnrollGroups
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
fetchPolicy: 'network-only',
|
||||||
|
update: (data) => _.get(data, 'authentication.activeStrategies', []).map(str => ({
|
||||||
...str,
|
...str,
|
||||||
config: _.sortBy(str.config.map(cfg => ({
|
config: _.sortBy(str.config.map(cfg => ({
|
||||||
...cfg,
|
...cfg,
|
||||||
@ -324,7 +425,7 @@ export default {
|
|||||||
})), [t => t.value.order])
|
})), [t => t.value.order])
|
||||||
})),
|
})),
|
||||||
watchLoading (isLoading) {
|
watchLoading (isLoading) {
|
||||||
this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'admin-auth-refresh')
|
this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'admin-auth-activestrategies-refresh')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
groups: {
|
groups: {
|
||||||
@ -351,7 +452,7 @@ export default {
|
|||||||
|
|
||||||
.authlogo {
|
.authlogo {
|
||||||
width: 250px;
|
width: 250px;
|
||||||
height: 85px;
|
height: 60px;
|
||||||
float:right;
|
float:right;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
@ -1,23 +1,15 @@
|
|||||||
<template lang="pug">
|
<template lang="pug">
|
||||||
v-card(flat)
|
v-card(flat)
|
||||||
v-card-text
|
v-container.px-3.pb-3.pt-3(fluid, grid-list-md)
|
||||||
v-text-field(
|
v-layout(row, wrap)
|
||||||
outlined
|
v-flex(xs12, v-if='group.isSystem')
|
||||||
v-model='group.name'
|
v-alert.radius-7.mb-0(
|
||||||
label='Group Name'
|
|
||||||
counter='255'
|
|
||||||
prepend-icon='mdi-account-group'
|
|
||||||
)
|
|
||||||
v-alert.radius-7(
|
|
||||||
v-if='group.isSystem'
|
|
||||||
color='orange darken-2'
|
color='orange darken-2'
|
||||||
:class='$vuetify.theme.dark ? "grey darken-4" : "orange lighten-5"'
|
:class='$vuetify.theme.dark ? "grey darken-4" : "orange lighten-5"'
|
||||||
outlined
|
outlined
|
||||||
:value='true'
|
:value='true'
|
||||||
icon='mdi-lock-outline'
|
icon='mdi-lock-outline'
|
||||||
) This is a system group. Some permissions cannot be modified.
|
) This is a system group. Some permissions cannot be modified.
|
||||||
v-container.px-3.pb-3.pt-0(fluid, grid-list-md)
|
|
||||||
v-layout(row, wrap)
|
|
||||||
v-flex(xs12, md6, lg4, v-for='pmGroup in permissions', :key='pmGroup.category')
|
v-flex(xs12, md6, lg4, v-for='pmGroup in permissions', :key='pmGroup.category')
|
||||||
v-card.md2(flat, :class='$vuetify.theme.dark ? "grey darken-3-d5" : "grey lighten-5"')
|
v-card.md2(flat, :class='$vuetify.theme.dark ? "grey darken-3-d5" : "grey lighten-5"')
|
||||||
.overline.px-5.pt-5.pb-3.grey--text.text--darken-2 {{pmGroup.category}}
|
.overline.px-5.pt-5.pb-3.grey--text.text--darken-2 {{pmGroup.category}}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template lang="pug">
|
<template lang="pug">
|
||||||
v-card(flat)
|
v-card(flat)
|
||||||
v-card-text(v-if='group.id === 1')
|
v-card-text(v-if='group.id === 1')
|
||||||
v-alert.radius-7(
|
v-alert.radius-7.mb-0(
|
||||||
:class='$vuetify.theme.dark ? "grey darken-4" : "orange lighten-5"'
|
:class='$vuetify.theme.dark ? "grey darken-4" : "orange lighten-5"'
|
||||||
color='orange darken-2'
|
color='orange darken-2'
|
||||||
outlined
|
outlined
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
v-icon mdi-arrow-left
|
v-icon mdi-arrow-left
|
||||||
v-dialog(v-model='deleteGroupDialog', max-width='500', v-if='!group.isSystem')
|
v-dialog(v-model='deleteGroupDialog', max-width='500', v-if='!group.isSystem')
|
||||||
template(v-slot:activator='{ on }')
|
template(v-slot:activator='{ on }')
|
||||||
v-btn.ml-2(color='red', icon, outlined, v-on='on')
|
v-btn.ml-3(color='red', icon, outlined, v-on='on')
|
||||||
v-icon(color='red') mdi-trash-can-outline
|
v-icon(color='red') mdi-trash-can-outline
|
||||||
v-card
|
v-card
|
||||||
.dialog-header.is-red Delete Group?
|
.dialog-header.is-red Delete Group?
|
||||||
@ -21,11 +21,14 @@
|
|||||||
v-spacer
|
v-spacer
|
||||||
v-btn(text, @click='deleteGroupDialog = false') Cancel
|
v-btn(text, @click='deleteGroupDialog = false') Cancel
|
||||||
v-btn(color='red', dark, @click='deleteGroup') Delete
|
v-btn(color='red', dark, @click='deleteGroup') Delete
|
||||||
v-btn.ml-2(color='success', large, depressed, @click='updateGroup')
|
v-btn.ml-3(color='success', large, depressed, @click='updateGroup')
|
||||||
v-icon(left) mdi-check
|
v-icon(left) mdi-check
|
||||||
span Update Group
|
span Update Group
|
||||||
v-card.mt-3
|
v-card.mt-3
|
||||||
v-tabs.grad-tabs(v-model='tab', :color='$vuetify.theme.dark ? `blue` : `primary`', fixed-tabs, show-arrows, icons-and-text)
|
v-tabs.grad-tabs(v-model='tab', :color='$vuetify.theme.dark ? `blue` : `primary`', fixed-tabs, show-arrows, icons-and-text)
|
||||||
|
v-tab(key='settings')
|
||||||
|
span Settings
|
||||||
|
v-icon mdi-cog-box
|
||||||
v-tab(key='permissions')
|
v-tab(key='permissions')
|
||||||
span Permissions
|
span Permissions
|
||||||
v-icon mdi-lock-pattern
|
v-icon mdi-lock-pattern
|
||||||
@ -36,6 +39,44 @@
|
|||||||
span Users
|
span Users
|
||||||
v-icon mdi-account-group
|
v-icon mdi-account-group
|
||||||
|
|
||||||
|
v-tab-item(key='settings', :transition='false', :reverse-transition='false')
|
||||||
|
v-card(flat)
|
||||||
|
template(v-if='group.id <= 2')
|
||||||
|
v-card-text
|
||||||
|
v-alert.radius-7.mb-0(
|
||||||
|
color='orange darken-2'
|
||||||
|
:class='$vuetify.theme.dark ? "grey darken-4" : "orange lighten-5"'
|
||||||
|
outlined
|
||||||
|
:value='true'
|
||||||
|
icon='mdi-lock-outline'
|
||||||
|
) This is a system group and its settings cannot be modified.
|
||||||
|
v-divider
|
||||||
|
v-card-text
|
||||||
|
v-text-field(
|
||||||
|
outlined
|
||||||
|
v-model='group.name'
|
||||||
|
label='Group Name'
|
||||||
|
hide-details
|
||||||
|
prepend-icon='mdi-account-group'
|
||||||
|
style='max-width: 600px;'
|
||||||
|
:disabled='group.id <= 2'
|
||||||
|
)
|
||||||
|
template(v-if='group.id > 2')
|
||||||
|
v-divider
|
||||||
|
v-card-text
|
||||||
|
v-text-field(
|
||||||
|
outlined
|
||||||
|
v-model='group.redirectOnLogin'
|
||||||
|
label='Redirect on Login'
|
||||||
|
persistent-hint
|
||||||
|
hint='The path / URL where the user will be redirected upon successful login.'
|
||||||
|
prepend-icon='mdi-arrow-top-left-thick'
|
||||||
|
append-icon='mdi-folder-search'
|
||||||
|
@click:append='selectPage'
|
||||||
|
style='max-width: 850px;'
|
||||||
|
:counter='255'
|
||||||
|
)
|
||||||
|
|
||||||
v-tab-item(key='permissions', :transition='false', :reverse-transition='false')
|
v-tab-item(key='permissions', :transition='false', :reverse-transition='false')
|
||||||
group-permissions(v-model='group', @refresh='refresh')
|
group-permissions(v-model='group', @refresh='refresh')
|
||||||
|
|
||||||
@ -44,21 +85,23 @@
|
|||||||
|
|
||||||
v-tab-item(key='users', :transition='false', :reverse-transition='false')
|
v-tab-item(key='users', :transition='false', :reverse-transition='false')
|
||||||
group-users(v-model='group', @refresh='refresh')
|
group-users(v-model='group', @refresh='refresh')
|
||||||
|
|
||||||
v-card-chin
|
v-card-chin
|
||||||
v-spacer
|
v-spacer
|
||||||
.caption.grey--text.pr-2 Group ID #[strong {{group.id}}]
|
.caption.grey--text.pr-2 Group ID #[strong {{group.id}}]
|
||||||
|
|
||||||
|
page-selector(mode='select', v-model='selectPageModal', :open-handler='selectPageHandle', path='home', :locale='currentLang')
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
|
import gql from 'graphql-tag'
|
||||||
|
|
||||||
import GroupPermissions from './admin-groups-edit-permissions.vue'
|
import GroupPermissions from './admin-groups-edit-permissions.vue'
|
||||||
import GroupRules from './admin-groups-edit-rules.vue'
|
import GroupRules from './admin-groups-edit-rules.vue'
|
||||||
import GroupUsers from './admin-groups-edit-users.vue'
|
import GroupUsers from './admin-groups-edit-users.vue'
|
||||||
|
|
||||||
import groupQuery from 'gql/admin/groups/groups-query-single.gql'
|
/* global siteConfig */
|
||||||
import deleteGroupMutation from 'gql/admin/groups/groups-mutation-delete.gql'
|
|
||||||
import updateGroupMutation from 'gql/admin/groups/groups-mutation-update.gql'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@ -74,20 +117,55 @@ export default {
|
|||||||
isSystem: false,
|
isSystem: false,
|
||||||
permissions: [],
|
permissions: [],
|
||||||
pageRules: [],
|
pageRules: [],
|
||||||
users: []
|
users: [],
|
||||||
|
redirectOnLogin: '/'
|
||||||
},
|
},
|
||||||
deleteGroupDialog: false,
|
deleteGroupDialog: false,
|
||||||
tab: null
|
tab: null,
|
||||||
|
selectPageModal: false,
|
||||||
|
currentLang: siteConfig.lang
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
selectPage () {
|
||||||
|
this.selectPageModal = true
|
||||||
|
},
|
||||||
|
selectPageHandle ({ path, locale }) {
|
||||||
|
this.group.redirectOnLogin = `/${locale}/${path}`
|
||||||
|
},
|
||||||
async updateGroup() {
|
async updateGroup() {
|
||||||
try {
|
try {
|
||||||
await this.$apollo.mutate({
|
await this.$apollo.mutate({
|
||||||
mutation: updateGroupMutation,
|
mutation: gql`
|
||||||
|
mutation (
|
||||||
|
$id: Int!
|
||||||
|
$name: String!
|
||||||
|
$redirectOnLogin: String!
|
||||||
|
$permissions: [String]!
|
||||||
|
$pageRules: [PageRuleInput]!
|
||||||
|
) {
|
||||||
|
groups {
|
||||||
|
update(
|
||||||
|
id: $id
|
||||||
|
name: $name
|
||||||
|
redirectOnLogin: $redirectOnLogin
|
||||||
|
permissions: $permissions
|
||||||
|
pageRules: $pageRules
|
||||||
|
) {
|
||||||
|
responseResult {
|
||||||
|
succeeded
|
||||||
|
errorCode
|
||||||
|
slug
|
||||||
|
message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
variables: {
|
variables: {
|
||||||
id: this.group.id,
|
id: this.group.id,
|
||||||
name: this.group.name,
|
name: this.group.name,
|
||||||
|
redirectOnLogin: this.group.redirectOnLogin,
|
||||||
permissions: this.group.permissions,
|
permissions: this.group.permissions,
|
||||||
pageRules: this.group.pageRules
|
pageRules: this.group.pageRules
|
||||||
},
|
},
|
||||||
@ -108,7 +186,20 @@ export default {
|
|||||||
this.deleteGroupDialog = false
|
this.deleteGroupDialog = false
|
||||||
try {
|
try {
|
||||||
await this.$apollo.mutate({
|
await this.$apollo.mutate({
|
||||||
mutation: deleteGroupMutation,
|
mutation: gql`
|
||||||
|
mutation ($id: Int!) {
|
||||||
|
groups {
|
||||||
|
delete(id: $id) {
|
||||||
|
responseResult {
|
||||||
|
succeeded
|
||||||
|
errorCode
|
||||||
|
slug
|
||||||
|
message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
variables: {
|
variables: {
|
||||||
id: this.group.id
|
id: this.group.id
|
||||||
},
|
},
|
||||||
@ -132,7 +223,34 @@ export default {
|
|||||||
},
|
},
|
||||||
apollo: {
|
apollo: {
|
||||||
group: {
|
group: {
|
||||||
query: groupQuery,
|
query: gql`
|
||||||
|
query ($id: Int!) {
|
||||||
|
groups {
|
||||||
|
single(id: $id) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
redirectOnLogin
|
||||||
|
isSystem
|
||||||
|
permissions
|
||||||
|
pageRules {
|
||||||
|
id
|
||||||
|
path
|
||||||
|
roles
|
||||||
|
match
|
||||||
|
deny
|
||||||
|
locales
|
||||||
|
}
|
||||||
|
users {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
email
|
||||||
|
}
|
||||||
|
createdAt
|
||||||
|
updatedAt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
variables() {
|
variables() {
|
||||||
return {
|
return {
|
||||||
id: _.toSafeInteger(this.$route.params.id)
|
id: _.toSafeInteger(this.$route.params.id)
|
||||||
|
@ -8,7 +8,9 @@
|
|||||||
.headline.blue--text.text--darken-2.animated.fadeInLeft Groups
|
.headline.blue--text.text--darken-2.animated.fadeInLeft Groups
|
||||||
.subtitle-1.grey--text.animated.fadeInLeft.wait-p4s Manage groups and their permissions
|
.subtitle-1.grey--text.animated.fadeInLeft.wait-p4s Manage groups and their permissions
|
||||||
v-spacer
|
v-spacer
|
||||||
v-btn.animated.fadeInDown.wait-p2s.mr-3(color='grey', outlined, @click='refresh', icon)
|
v-btn.animated.fadeInDown.wait-p3s(icon, outlined, color='grey', href='https://docs.requarks.io/groups', target='_blank')
|
||||||
|
v-icon mdi-help-circle
|
||||||
|
v-btn.animated.fadeInDown.wait-p2s.mx-3(color='grey', outlined, @click='refresh', icon)
|
||||||
v-icon mdi-refresh
|
v-icon mdi-refresh
|
||||||
v-dialog(v-model='newGroupDialog', max-width='500')
|
v-dialog(v-model='newGroupDialog', max-width='500')
|
||||||
template(v-slot:activator='{ on }')
|
template(v-slot:activator='{ on }')
|
||||||
|
@ -93,25 +93,25 @@
|
|||||||
.caption Defines the duration for which the server should only deliver content through HTTPS.
|
.caption Defines the duration for which the server should only deliver content through HTTPS.
|
||||||
.caption It's a good idea to start with small values and make sure that nothing breaks on your wiki before moving to longer values.
|
.caption It's a good idea to start with small values and make sure that nothing breaks on your wiki before moving to longer values.
|
||||||
|
|
||||||
v-divider.mt-3
|
//- v-divider.mt-3
|
||||||
v-switch(
|
//- v-switch(
|
||||||
inset
|
//- inset
|
||||||
label='Enforce CSP'
|
//- label='Enforce CSP'
|
||||||
color='red darken-2'
|
//- color='red darken-2'
|
||||||
v-model='config.securityCSP'
|
//- v-model='config.securityCSP'
|
||||||
persistent-hint
|
//- persistent-hint
|
||||||
hint='Restricts scripts to pre-approved content sources.'
|
//- hint='Restricts scripts to pre-approved content sources.'
|
||||||
disabled
|
//- disabled
|
||||||
)
|
//- )
|
||||||
v-textarea.mt-5(
|
//- v-textarea.mt-5(
|
||||||
label='CSP Directives'
|
//- label='CSP Directives'
|
||||||
outlined
|
//- outlined
|
||||||
v-model='config.securityCSPDirectives'
|
//- v-model='config.securityCSPDirectives'
|
||||||
prepend-icon='mdi-subdirectory-arrow-right'
|
//- prepend-icon='mdi-subdirectory-arrow-right'
|
||||||
persistent-hint
|
//- persistent-hint
|
||||||
hint='One directive per line.'
|
//- hint='One directive per line.'
|
||||||
disabled
|
//- disabled
|
||||||
)
|
//- )
|
||||||
|
|
||||||
v-flex(lg6 xs12)
|
v-flex(lg6 xs12)
|
||||||
v-card.animated.fadeInUp.wait-p2s
|
v-card.animated.fadeInUp.wait-p2s
|
||||||
@ -142,6 +142,62 @@
|
|||||||
:suffix='$t(`admin:security.maxUploadBatchSuffix`)'
|
:suffix='$t(`admin:security.maxUploadBatchSuffix`)'
|
||||||
style='max-width: 450px;'
|
style='max-width: 450px;'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
v-card.mt-3.animated.fadeInUp.wait-p2s
|
||||||
|
v-toolbar(flat, color='primary', dark, dense)
|
||||||
|
.subtitle-1 {{$t('admin:security.login')}}
|
||||||
|
//- v-card-info(color='blue')
|
||||||
|
//- span {{$t('admin:security.loginInfo')}}
|
||||||
|
.overline.grey--text.pa-4 {{$t('admin:security.loginScreen')}}
|
||||||
|
.px-4.pb-3
|
||||||
|
v-text-field(
|
||||||
|
outlined
|
||||||
|
:label='$t(`admin:security.loginBgUrl`)'
|
||||||
|
v-model='config.authLoginBgUrl'
|
||||||
|
:hint='$t(`admin:security.loginBgUrlHint`)'
|
||||||
|
persistent-hint
|
||||||
|
prepend-icon='mdi-image-area'
|
||||||
|
append-icon='mdi-folder-image'
|
||||||
|
@click:append='browseLoginBg'
|
||||||
|
)
|
||||||
|
v-switch(
|
||||||
|
inset
|
||||||
|
:label='$t(`admin:security.bypassLogin`)'
|
||||||
|
color='red darken-2'
|
||||||
|
v-model='config.authAutoLogin'
|
||||||
|
prepend-icon='mdi-fast-forward'
|
||||||
|
persistent-hint
|
||||||
|
:hint='$t(`admin:security.bypassLoginHint`)'
|
||||||
|
)
|
||||||
|
v-divider.mt-3
|
||||||
|
.overline.grey--text.pa-4 {{$t('admin:security.jwt')}}
|
||||||
|
.px-4.pb-3
|
||||||
|
v-text-field(
|
||||||
|
v-model='config.authJwtAudience'
|
||||||
|
outlined
|
||||||
|
prepend-icon='mdi-account-group-outline'
|
||||||
|
:label='$t(`admin:auth.jwtAudience`)'
|
||||||
|
:hint='$t(`admin:auth.jwtAudienceHint`)'
|
||||||
|
persistent-hint
|
||||||
|
)
|
||||||
|
v-text-field.mt-3(
|
||||||
|
v-model='config.authJwtExpiration'
|
||||||
|
outlined
|
||||||
|
prepend-icon='mdi-clock-outline'
|
||||||
|
:label='$t(`admin:auth.tokenExpiration`)'
|
||||||
|
:hint='$t(`admin:auth.tokenExpirationHint`)'
|
||||||
|
persistent-hint
|
||||||
|
)
|
||||||
|
v-text-field.mt-3(
|
||||||
|
v-model='config.authJwtRenewablePeriod'
|
||||||
|
outlined
|
||||||
|
prepend-icon='mdi-update'
|
||||||
|
:label='$t(`admin:auth.tokenRenewalPeriod`)'
|
||||||
|
:hint='$t(`admin:auth.tokenRenewalPeriodHint`)'
|
||||||
|
persistent-hint
|
||||||
|
)
|
||||||
|
|
||||||
|
component(:is='activeModal')
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@ -149,7 +205,17 @@ import _ from 'lodash'
|
|||||||
import { sync } from 'vuex-pathify'
|
import { sync } from 'vuex-pathify'
|
||||||
import gql from 'graphql-tag'
|
import gql from 'graphql-tag'
|
||||||
|
|
||||||
|
import editorStore from '../../store/editor'
|
||||||
|
|
||||||
|
/* global WIKI */
|
||||||
|
|
||||||
|
WIKI.$store.registerModule('editor', editorStore)
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
i18nOptions: { namespaces: 'editor' },
|
||||||
|
components: {
|
||||||
|
editorModalMedia: () => import(/* webpackChunkName: "editor", webpackMode: "lazy" */ '../editor/editor-modal-media.vue')
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
config: {
|
config: {
|
||||||
@ -163,7 +229,12 @@ export default {
|
|||||||
securityHSTS: false,
|
securityHSTS: false,
|
||||||
securityHSTSDuration: 0,
|
securityHSTSDuration: 0,
|
||||||
securityCSP: false,
|
securityCSP: false,
|
||||||
securityCSPDirectives: ''
|
securityCSPDirectives: '',
|
||||||
|
authAutoLogin: false,
|
||||||
|
authLoginBgUrl: '',
|
||||||
|
authJwtAudience: 'urn:wiki.js',
|
||||||
|
authJwtExpiration: '30m',
|
||||||
|
authJwtRenewablePeriod: '14d'
|
||||||
},
|
},
|
||||||
hstsDurations: [
|
hstsDurations: [
|
||||||
{ value: 300, text: '5 minutes' },
|
{ value: 300, text: '5 minutes' },
|
||||||
@ -184,6 +255,11 @@ export default {
|
|||||||
await this.$apollo.mutate({
|
await this.$apollo.mutate({
|
||||||
mutation: gql`
|
mutation: gql`
|
||||||
mutation (
|
mutation (
|
||||||
|
$authAutoLogin: Boolean
|
||||||
|
$authLoginBgUrl: String
|
||||||
|
$authJwtAudience: String
|
||||||
|
$authJwtExpiration: String
|
||||||
|
$authJwtRenewablePeriod: String
|
||||||
$uploadMaxFileSize: Int
|
$uploadMaxFileSize: Int
|
||||||
$uploadMaxFiles: Int
|
$uploadMaxFiles: Int
|
||||||
$securityOpenRedirect: Boolean
|
$securityOpenRedirect: Boolean
|
||||||
@ -198,6 +274,11 @@ export default {
|
|||||||
) {
|
) {
|
||||||
site {
|
site {
|
||||||
updateConfig(
|
updateConfig(
|
||||||
|
authAutoLogin: $authAutoLogin,
|
||||||
|
authLoginBgUrl: $authLoginBgUrl,
|
||||||
|
authJwtAudience: $authJwtAudience,
|
||||||
|
authJwtExpiration: $authJwtExpiration,
|
||||||
|
authJwtRenewablePeriod: $authJwtRenewablePeriod,
|
||||||
uploadMaxFileSize: $uploadMaxFileSize,
|
uploadMaxFileSize: $uploadMaxFileSize,
|
||||||
uploadMaxFiles: $uploadMaxFiles,
|
uploadMaxFiles: $uploadMaxFiles,
|
||||||
securityOpenRedirect: $securityOpenRedirect,
|
securityOpenRedirect: $securityOpenRedirect,
|
||||||
@ -221,6 +302,11 @@ export default {
|
|||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
variables: {
|
variables: {
|
||||||
|
authAutoLogin: _.get(this.config, 'authAutoLogin', false),
|
||||||
|
authLoginBgUrl: _.get(this.config, 'authLoginBgUrl', ''),
|
||||||
|
authJwtAudience: _.get(this.config, 'authJwtAudience', ''),
|
||||||
|
authJwtExpiration: _.get(this.config, 'authJwtExpiration', ''),
|
||||||
|
authJwtRenewablePeriod: _.get(this.config, 'authJwtRenewablePeriod', ''),
|
||||||
uploadMaxFileSize: _.toSafeInteger(_.get(this.config, 'uploadMaxFileSize', 0)),
|
uploadMaxFileSize: _.toSafeInteger(_.get(this.config, 'uploadMaxFileSize', 0)),
|
||||||
uploadMaxFiles: _.toSafeInteger(_.get(this.config, 'uploadMaxFiles', 0)),
|
uploadMaxFiles: _.toSafeInteger(_.get(this.config, 'uploadMaxFiles', 0)),
|
||||||
securityOpenRedirect: _.get(this.config, 'securityOpenRedirect', false),
|
securityOpenRedirect: _.get(this.config, 'securityOpenRedirect', false),
|
||||||
@ -245,14 +331,31 @@ export default {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.$store.commit('pushGraphError', err)
|
this.$store.commit('pushGraphError', err)
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
browseLoginBg () {
|
||||||
|
this.$store.set('editor/editorKey', 'common')
|
||||||
|
this.activeModal = 'editorModalMedia'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
mounted () {
|
||||||
|
this.$root.$on('editorInsert', opts => {
|
||||||
|
this.config.loginBgUrl = opts.path
|
||||||
|
})
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
this.$root.$off('editorInsert')
|
||||||
|
},
|
||||||
apollo: {
|
apollo: {
|
||||||
config: {
|
config: {
|
||||||
query: gql`
|
query: gql`
|
||||||
{
|
{
|
||||||
site {
|
site {
|
||||||
config {
|
config {
|
||||||
|
authAutoLogin
|
||||||
|
authLoginBgUrl
|
||||||
|
authJwtAudience
|
||||||
|
authJwtExpiration
|
||||||
|
authJwtRenewablePeriod
|
||||||
uploadMaxFileSize
|
uploadMaxFileSize
|
||||||
uploadMaxFiles
|
uploadMaxFiles
|
||||||
securityOpenRedirect
|
securityOpenRedirect
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
v-card-text.pt-5
|
v-card-text.pt-5
|
||||||
v-select(
|
v-select(
|
||||||
:items='providers'
|
:items='providers'
|
||||||
item-text='title'
|
item-text='displayName'
|
||||||
item-value='key'
|
item-value='key'
|
||||||
outlined
|
outlined
|
||||||
prepend-icon='mdi-domain'
|
prepend-icon='mdi-domain'
|
||||||
@ -230,19 +230,15 @@ export default {
|
|||||||
query: gql`
|
query: gql`
|
||||||
query {
|
query {
|
||||||
authentication {
|
authentication {
|
||||||
strategies(
|
activeStrategies {
|
||||||
isEnabled: true
|
|
||||||
) {
|
|
||||||
key
|
key
|
||||||
title
|
displayName
|
||||||
icon
|
|
||||||
color
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
fetchPolicy: 'network-only',
|
fetchPolicy: 'network-only',
|
||||||
update: (data) => data.authentication.strategies,
|
update: (data) => data.authentication.activeStrategies,
|
||||||
watchLoading (isLoading) {
|
watchLoading (isLoading) {
|
||||||
this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'admin-users-strategies-refresh')
|
this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'admin-users-strategies-refresh')
|
||||||
}
|
}
|
||||||
|
@ -33,7 +33,7 @@
|
|||||||
label='Identity Provider'
|
label='Identity Provider'
|
||||||
:items='strategies'
|
:items='strategies'
|
||||||
v-model='filterStrategy'
|
v-model='filterStrategy'
|
||||||
item-text='title'
|
item-text='displayName'
|
||||||
item-value='key'
|
item-value='key'
|
||||||
style='max-width: 300px;'
|
style='max-width: 300px;'
|
||||||
dense
|
dense
|
||||||
@ -162,13 +162,9 @@ export default {
|
|||||||
query: gql`
|
query: gql`
|
||||||
query {
|
query {
|
||||||
authentication {
|
authentication {
|
||||||
strategies(
|
activeStrategies {
|
||||||
isEnabled: true
|
|
||||||
) {
|
|
||||||
key
|
key
|
||||||
title
|
displayName
|
||||||
icon
|
|
||||||
color
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -177,8 +173,8 @@ export default {
|
|||||||
update: (data) => {
|
update: (data) => {
|
||||||
return _.concat({
|
return _.concat({
|
||||||
key: 'all',
|
key: 'all',
|
||||||
title: 'All Providers'
|
displayName: 'All Providers'
|
||||||
}, data.authentication.strategies)
|
}, data.authentication.activeStrategies)
|
||||||
},
|
},
|
||||||
watchLoading (isLoading) {
|
watchLoading (isLoading) {
|
||||||
this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'admin-users-strategies-refresh')
|
this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'admin-users-strategies-refresh')
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
top
|
top
|
||||||
multi-line
|
multi-line
|
||||||
v-model='notificationState'
|
v-model='notificationState'
|
||||||
|
:timeout='6000'
|
||||||
)
|
)
|
||||||
.text-left
|
.text-left
|
||||||
v-icon.mr-3(dark) mdi-{{ notification.icon }}
|
v-icon.mr-3(dark) mdi-{{ notification.icon }}
|
||||||
@ -26,16 +27,15 @@ export default {
|
|||||||
|
|
||||||
<style lang='scss'>
|
<style lang='scss'>
|
||||||
.nav-notify {
|
.nav-notify {
|
||||||
// top: 60px;
|
top: -64px;
|
||||||
|
padding-top: 0;
|
||||||
z-index: 999;
|
z-index: 999;
|
||||||
|
|
||||||
.v-snack__wrapper {
|
.v-snack__wrapper {
|
||||||
border-top-left-radius: 0;
|
border-top-left-radius: 0;
|
||||||
border-top-right-radius: 0;
|
border-top-right-radius: 0;
|
||||||
}
|
|
||||||
|
|
||||||
.v-snack__content {
|
|
||||||
position: relative;
|
position: relative;
|
||||||
|
margin-top: 0;
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
content: '';
|
content: '';
|
||||||
|
@ -245,6 +245,7 @@ import mermaid from 'mermaid'
|
|||||||
|
|
||||||
// Helpers
|
// Helpers
|
||||||
import katexHelper from './common/katex'
|
import katexHelper from './common/katex'
|
||||||
|
import tabsetHelper from './markdown/tabset'
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// INIT
|
// INIT
|
||||||
@ -433,6 +434,7 @@ export default {
|
|||||||
this.$store.set('editor/content', newContent)
|
this.$store.set('editor/content', newContent)
|
||||||
this.previewHTML = DOMPurify.sanitize(md.render(newContent))
|
this.previewHTML = DOMPurify.sanitize(md.render(newContent))
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
|
tabsetHelper.format()
|
||||||
this.renderMermaidDiagrams()
|
this.renderMermaidDiagrams()
|
||||||
Prism.highlightAllUnder(this.$refs.editorPreview)
|
Prism.highlightAllUnder(this.$refs.editorPreview)
|
||||||
Array.from(this.$refs.editorPreview.querySelectorAll('pre.line-numbers')).forEach(pre => pre.classList.add('prismjs'))
|
Array.from(this.$refs.editorPreview.querySelectorAll('pre.line-numbers')).forEach(pre => pre.classList.add('prismjs'))
|
||||||
@ -856,6 +858,44 @@ $editor-height-mobile: calc(100vh - 112px - 16px);
|
|||||||
p.line {
|
p.line {
|
||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tabset {
|
||||||
|
background-color: mc('teal', '700');
|
||||||
|
color: mc('teal', '100') !important;
|
||||||
|
padding: 5px 12px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
border-radius: 5px 0 0 0;
|
||||||
|
font-style: italic;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-header {
|
||||||
|
background-color: mc('teal', '500');
|
||||||
|
color: #FFF !important;
|
||||||
|
padding: 5px 12px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-top: 0 !important;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-content {
|
||||||
|
border-left: 5px solid mc('teal', '500');
|
||||||
|
background-color: mc('teal', '50');
|
||||||
|
padding: 0 15px 15px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
@at-root .theme--dark & {
|
||||||
|
background-color: rgba(mc('teal', '500'), .1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
16
client/components/editor/markdown/tabset.js
Normal file
16
client/components/editor/markdown/tabset.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import cash from 'cash-dom'
|
||||||
|
import _ from 'lodash'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
format () {
|
||||||
|
for (let i = 1; i < 6; i++) {
|
||||||
|
cash(`.editor-markdown-preview-content h${i}.tabset`).each((idx, elm) => {
|
||||||
|
elm.innerHTML = 'Tabset ( rendered upon saving )'
|
||||||
|
cash(elm).nextUntil(_.times(i, t => `h${t + 1}`).join(', '), `h${i + 1}`).each((hidx, hd) => {
|
||||||
|
hd.classList.add('tabset-header')
|
||||||
|
cash(hd).nextUntil(_.times(i + 1, t => `h${t + 1}`).join(', ')).wrapAll('<div class="tabset-content"></div>')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,32 +1,42 @@
|
|||||||
<template lang="pug">
|
<template lang="pug">
|
||||||
v-app
|
v-app
|
||||||
.login
|
.login
|
||||||
v-container(grid-list-lg)
|
.login-sd
|
||||||
v-layout(row, wrap)
|
.d-flex
|
||||||
v-flex(
|
.login-logo
|
||||||
xs12
|
v-avatar(tile, size='34')
|
||||||
offset-sm1, sm10
|
v-img(:src='logoUrl')
|
||||||
offset-md2, md8
|
.login-title
|
||||||
offset-lg3, lg6
|
.text-h6 {{ siteTitle }}
|
||||||
offset-xl4, xl4
|
//-------------------------------------------------
|
||||||
|
//- PROVIDERS LIST
|
||||||
|
//-------------------------------------------------
|
||||||
|
template(v-if='screen === `login` && strategies.length > 1')
|
||||||
|
.login-subtitle.mt-5
|
||||||
|
.text-subtitle-1 Select Authentication Provider
|
||||||
|
.login-list
|
||||||
|
v-list.elevation-1.radius-7(nav)
|
||||||
|
v-list-item-group(v-model='selectedStrategyKey')
|
||||||
|
v-list-item(
|
||||||
|
v-for='(stg, idx) of strategies'
|
||||||
|
:key='stg.key'
|
||||||
|
:value='stg.key'
|
||||||
|
:color='stg.strategy.color'
|
||||||
)
|
)
|
||||||
transition(name='fadeUp')
|
v-avatar.mr-3(tile, size='24', v-html='stg.strategy.icon')
|
||||||
v-card.elevation-5(v-show='isShown', light)
|
span.text-none {{stg.displayName}}
|
||||||
v-toolbar(color='indigo', flat, dense, dark)
|
//-------------------------------------------------
|
||||||
v-spacer
|
//- LOGIN FORM
|
||||||
.subheading(v-if='screen === "tfa"') {{ $t('auth:tfa.subtitle') }}
|
//-------------------------------------------------
|
||||||
.subheading(v-if='screen === "changePwd"') {{ $t('auth:changePwd.subtitle') }}
|
template(v-if='screen === `login` && selectedStrategy.strategy.useForm')
|
||||||
.subheading(v-else-if='selectedStrategy.key !== "local"') {{ $t('auth:loginUsingStrategy', { strategy: selectedStrategy.title, interpolation: { escapeValue: false } }) }}
|
.login-subtitle
|
||||||
.subheading(v-else) {{ $t('auth:loginRequired') }}
|
.text-subtitle-1 Enter your credentials
|
||||||
v-spacer
|
.login-form
|
||||||
v-card-text.text-center
|
v-text-field(
|
||||||
h1.display-1.indigo--text.py-2 {{ siteTitle }}
|
|
||||||
template(v-if='screen === "login"')
|
|
||||||
v-text-field.mt-3(
|
|
||||||
solo
|
solo
|
||||||
flat
|
flat
|
||||||
prepend-icon='mdi-clipboard-account'
|
prepend-inner-icon='mdi-clipboard-account'
|
||||||
background-color='grey lighten-4'
|
background-color='white'
|
||||||
hide-details
|
hide-details
|
||||||
ref='iptEmail'
|
ref='iptEmail'
|
||||||
v-model='username'
|
v-model='username'
|
||||||
@ -35,8 +45,8 @@
|
|||||||
v-text-field.mt-2(
|
v-text-field.mt-2(
|
||||||
solo
|
solo
|
||||||
flat
|
flat
|
||||||
prepend-icon='mdi-textbox-password'
|
prepend-inner-icon='mdi-form-textbox-password'
|
||||||
background-color='grey lighten-4'
|
background-color='white'
|
||||||
hide-details
|
hide-details
|
||||||
ref='iptPassword'
|
ref='iptPassword'
|
||||||
v-model='password'
|
v-model='password'
|
||||||
@ -46,144 +56,185 @@
|
|||||||
:placeholder='$t("auth:fields.password")'
|
:placeholder='$t("auth:fields.password")'
|
||||||
@keyup.enter='login'
|
@keyup.enter='login'
|
||||||
)
|
)
|
||||||
template(v-else-if='screen === "tfa"')
|
v-btn.mt-2.text-none(
|
||||||
.body-2 Enter the security code generated from your trusted device:
|
|
||||||
v-text-field.centered.mt-2(
|
|
||||||
solo
|
|
||||||
flat
|
|
||||||
background-color='grey lighten-4'
|
|
||||||
hide-details
|
|
||||||
ref='iptTFA'
|
|
||||||
v-model='securityCode'
|
|
||||||
:placeholder='$t("auth:tfa.placeholder")'
|
|
||||||
@keyup.enter='verifySecurityCode'
|
|
||||||
)
|
|
||||||
template(v-else-if='screen === "changePwd"')
|
|
||||||
.body-2 {{$t('auth:changePwd.instructions')}}
|
|
||||||
v-text-field.mt-2(
|
|
||||||
type='password'
|
|
||||||
solo
|
|
||||||
flat
|
|
||||||
background-color='grey lighten-4'
|
|
||||||
hide-details
|
|
||||||
ref='iptNewPassword'
|
|
||||||
v-model='newPassword'
|
|
||||||
:placeholder='$t(`auth:changePwd.newPasswordPlaceholder`)'
|
|
||||||
)
|
|
||||||
v-text-field.mt-2(
|
|
||||||
type='password'
|
|
||||||
solo
|
|
||||||
flat
|
|
||||||
background-color='grey lighten-4'
|
|
||||||
hide-details
|
|
||||||
v-model='newPasswordVerify'
|
|
||||||
:placeholder='$t(`auth:changePwd.newPasswordVerifyPlaceholder`)'
|
|
||||||
@keyup.enter='changePassword'
|
|
||||||
)
|
|
||||||
template(v-else-if='screen === "forgot"')
|
|
||||||
.body-2 {{ $t('auth:forgotPasswordSubtitle') }}
|
|
||||||
v-text-field.mt-3(
|
|
||||||
solo
|
|
||||||
flat
|
|
||||||
prepend-icon='mdi-email'
|
|
||||||
background-color='grey lighten-4'
|
|
||||||
hide-details
|
|
||||||
ref='iptEmailForgot'
|
|
||||||
v-model='username'
|
|
||||||
:placeholder='$t("auth:fields.email")'
|
|
||||||
)
|
|
||||||
v-card-actions.pb-4
|
|
||||||
v-spacer
|
|
||||||
v-btn(
|
|
||||||
width='100%'
|
width='100%'
|
||||||
max-width='250px'
|
|
||||||
v-if='screen === "login"'
|
v-if='screen === "login"'
|
||||||
large
|
large
|
||||||
color='primary'
|
color='primary'
|
||||||
dark
|
dark
|
||||||
@click='login'
|
@click='login'
|
||||||
rounded
|
|
||||||
:loading='isLoading'
|
:loading='isLoading'
|
||||||
) {{ $t('auth:actions.login') }}
|
) {{ $t('auth:actions.login') }}
|
||||||
v-btn(
|
.text-center.mt-5(v-if='screen === "login"')
|
||||||
width='100%'
|
v-btn.text-none(
|
||||||
max-width='250px'
|
text
|
||||||
v-else-if='screen === "tfa"'
|
|
||||||
large
|
|
||||||
color='primary'
|
|
||||||
dark
|
|
||||||
@click='verifySecurityCode'
|
|
||||||
rounded
|
rounded
|
||||||
:loading='isLoading'
|
color='grey darken-3'
|
||||||
) {{ $t('auth:tfa.verifyToken') }}
|
@click.stop.prevent='forgotPassword'
|
||||||
v-btn(
|
href='#forgot'
|
||||||
width='100%'
|
): .caption {{ $t('auth:forgotPasswordLink') }}
|
||||||
max-width='250px'
|
v-btn.text-none(
|
||||||
v-else-if='screen === "changePwd"'
|
v-if='screen === "login" && selectedStrategyKey === `local` && selectedStrategy.selfRegistration'
|
||||||
large
|
color='indigo darken-2'
|
||||||
color='primary'
|
text
|
||||||
dark
|
|
||||||
@click='changePassword'
|
|
||||||
rounded
|
rounded
|
||||||
:loading='isLoading'
|
href='/register'
|
||||||
) {{ $t('auth:changePwd.proceed') }}
|
): .caption {{ $t('auth:switchToRegister.link') }}
|
||||||
v-btn(
|
//- .login-main
|
||||||
width='100%'
|
//- v-container(grid-list-lg, fluid)
|
||||||
max-width='250px'
|
//- v-row(no-gutters)
|
||||||
v-else-if='screen === "forgot"'
|
//- v-col(cols='12', xl='4')
|
||||||
large
|
//- transition(name='fadeUp')
|
||||||
color='primary'
|
//- v-card.elevation-5(v-show='isShown', light)
|
||||||
dark
|
//- v-toolbar(color='indigo', flat, dense, dark)
|
||||||
@click='forgotPasswordSubmit'
|
//- v-spacer
|
||||||
rounded
|
//- .subheading(v-if='screen === "tfa"') {{ $t('auth:tfa.subtitle') }}
|
||||||
:loading='isLoading'
|
//- .subheading(v-if='screen === "changePwd"') {{ $t('auth:changePwd.subtitle') }}
|
||||||
) {{ $t('auth:sendResetPassword') }}
|
//- .subheading(v-else-if='selectedStrategy.key !== "local"') {{ $t('auth:loginUsingStrategy', { strategy: selectedStrategy.title, interpolation: { escapeValue: false } }) }}
|
||||||
v-spacer
|
//- .subheading(v-else) {{ $t('auth:loginRequired') }}
|
||||||
v-card-actions.pb-3(v-if='screen === "login" && selectedStrategy.key === "local"')
|
//- v-spacer
|
||||||
v-spacer
|
//- v-card-text.text-center
|
||||||
a.caption(@click.stop.prevent='forgotPassword', href='#forgot') {{ $t('auth:forgotPasswordLink') }}
|
//- h1.display-1.indigo--text.py-2 {{ siteTitle }}
|
||||||
v-spacer
|
//- template(v-if='screen === "login"')
|
||||||
v-card-actions.pb-3(v-else-if='screen === "forgot"')
|
//- v-text-field.mt-3(
|
||||||
v-spacer
|
//- solo
|
||||||
a.caption(@click.stop.prevent='screen = `login`', href='#cancelforgot') {{ $t('auth:forgotPasswordCancel') }}
|
//- flat
|
||||||
v-spacer
|
//- prepend-icon='mdi-clipboard-account'
|
||||||
template(v-if='screen === "login" && isSocialShown')
|
//- background-color='grey lighten-4'
|
||||||
v-divider
|
//- hide-details
|
||||||
v-card-text.grey.lighten-4.text-center
|
//- ref='iptEmail'
|
||||||
.pb-2.body-2.text-xs-center.grey--text.text--darken-2 {{ $t('auth:orLoginUsingStrategy') }}
|
//- v-model='username'
|
||||||
v-btn.mx-1.social-login-btn(
|
//- :placeholder='$t("auth:fields.emailUser")'
|
||||||
v-for='strategy in strategies', :key='strategy.key'
|
//- )
|
||||||
large
|
//- v-text-field.mt-2(
|
||||||
@click='selectStrategy(strategy)'
|
//- solo
|
||||||
dark
|
//- flat
|
||||||
:color='strategy.color'
|
//- prepend-icon='mdi-textbox-password'
|
||||||
:depressed='strategy.key === selectedStrategy.key'
|
//- background-color='grey lighten-4'
|
||||||
)
|
//- hide-details
|
||||||
v-avatar.mr-3(tile, :class='strategy.color', size='24', v-html='strategy.icon')
|
//- ref='iptPassword'
|
||||||
span(style='text-transform: none;') {{ strategy.title }}
|
//- v-model='password'
|
||||||
template(v-if='screen === "login" && selectedStrategy.key === `local` && selectedStrategy.selfRegistration')
|
//- :append-icon='hidePassword ? "mdi-eye-off" : "mdi-eye"'
|
||||||
v-divider
|
//- @click:append='() => (hidePassword = !hidePassword)'
|
||||||
v-card-actions.py-3(:class='isSocialShown ? "" : "grey lighten-4"')
|
//- :type='hidePassword ? "password" : "text"'
|
||||||
v-spacer
|
//- :placeholder='$t("auth:fields.password")'
|
||||||
i18next.caption(path='auth:switchToRegister.text', tag='div')
|
//- @keyup.enter='login'
|
||||||
a.caption(href='/register', place='link') {{ $t('auth:switchToRegister.link') }}
|
//- )
|
||||||
v-spacer
|
//- template(v-else-if='screen === "tfa"')
|
||||||
|
//- .body-2 Enter the security code generated from your trusted device:
|
||||||
|
//- v-text-field.centered.mt-2(
|
||||||
|
//- solo
|
||||||
|
//- flat
|
||||||
|
//- background-color='grey lighten-4'
|
||||||
|
//- hide-details
|
||||||
|
//- ref='iptTFA'
|
||||||
|
//- v-model='securityCode'
|
||||||
|
//- :placeholder='$t("auth:tfa.placeholder")'
|
||||||
|
//- @keyup.enter='verifySecurityCode'
|
||||||
|
//- )
|
||||||
|
//- template(v-else-if='screen === "changePwd"')
|
||||||
|
//- .body-2 {{$t('auth:changePwd.instructions')}}
|
||||||
|
//- v-text-field.mt-2(
|
||||||
|
//- type='password'
|
||||||
|
//- solo
|
||||||
|
//- flat
|
||||||
|
//- background-color='grey lighten-4'
|
||||||
|
//- hide-details
|
||||||
|
//- ref='iptNewPassword'
|
||||||
|
//- v-model='newPassword'
|
||||||
|
//- :placeholder='$t(`auth:changePwd.newPasswordPlaceholder`)'
|
||||||
|
//- )
|
||||||
|
//- v-text-field.mt-2(
|
||||||
|
//- type='password'
|
||||||
|
//- solo
|
||||||
|
//- flat
|
||||||
|
//- background-color='grey lighten-4'
|
||||||
|
//- hide-details
|
||||||
|
//- v-model='newPasswordVerify'
|
||||||
|
//- :placeholder='$t(`auth:changePwd.newPasswordVerifyPlaceholder`)'
|
||||||
|
//- @keyup.enter='changePassword'
|
||||||
|
//- )
|
||||||
|
//- template(v-else-if='screen === "forgot"')
|
||||||
|
//- .body-2 {{ $t('auth:forgotPasswordSubtitle') }}
|
||||||
|
//- v-text-field.mt-3(
|
||||||
|
//- solo
|
||||||
|
//- flat
|
||||||
|
//- prepend-icon='mdi-email'
|
||||||
|
//- background-color='grey lighten-4'
|
||||||
|
//- hide-details
|
||||||
|
//- ref='iptEmailForgot'
|
||||||
|
//- v-model='username'
|
||||||
|
//- :placeholder='$t("auth:fields.email")'
|
||||||
|
//- )
|
||||||
|
//- v-card-actions.pb-4
|
||||||
|
//- v-spacer
|
||||||
|
//- v-btn(
|
||||||
|
//- width='100%'
|
||||||
|
//- max-width='250px'
|
||||||
|
//- v-if='screen === "login"'
|
||||||
|
//- large
|
||||||
|
//- color='primary'
|
||||||
|
//- dark
|
||||||
|
//- @click='login'
|
||||||
|
//- rounded
|
||||||
|
//- :loading='isLoading'
|
||||||
|
//- ) {{ $t('auth:actions.login') }}
|
||||||
|
//- v-btn(
|
||||||
|
//- width='100%'
|
||||||
|
//- max-width='250px'
|
||||||
|
//- v-else-if='screen === "tfa"'
|
||||||
|
//- large
|
||||||
|
//- color='primary'
|
||||||
|
//- dark
|
||||||
|
//- @click='verifySecurityCode'
|
||||||
|
//- rounded
|
||||||
|
//- :loading='isLoading'
|
||||||
|
//- ) {{ $t('auth:tfa.verifyToken') }}
|
||||||
|
//- v-btn(
|
||||||
|
//- width='100%'
|
||||||
|
//- max-width='250px'
|
||||||
|
//- v-else-if='screen === "changePwd"'
|
||||||
|
//- large
|
||||||
|
//- color='primary'
|
||||||
|
//- dark
|
||||||
|
//- @click='changePassword'
|
||||||
|
//- rounded
|
||||||
|
//- :loading='isLoading'
|
||||||
|
//- ) {{ $t('auth:changePwd.proceed') }}
|
||||||
|
//- v-btn(
|
||||||
|
//- width='100%'
|
||||||
|
//- max-width='250px'
|
||||||
|
//- v-else-if='screen === "forgot"'
|
||||||
|
//- large
|
||||||
|
//- color='primary'
|
||||||
|
//- dark
|
||||||
|
//- @click='forgotPasswordSubmit'
|
||||||
|
//- rounded
|
||||||
|
//- :loading='isLoading'
|
||||||
|
//- ) {{ $t('auth:sendResetPassword') }}
|
||||||
|
//- v-spacer
|
||||||
|
//- v-card-actions.pb-3(v-if='screen === "login" && selectedStrategy.key === "local"')
|
||||||
|
//- v-spacer
|
||||||
|
//- a.caption(@click.stop.prevent='forgotPassword', href='#forgot') {{ $t('auth:forgotPasswordLink') }}
|
||||||
|
//- v-spacer
|
||||||
|
//- v-card-actions.pb-3(v-else-if='screen === "forgot"')
|
||||||
|
//- v-spacer
|
||||||
|
//- a.caption(@click.stop.prevent='screen = `login`', href='#cancelforgot') {{ $t('auth:forgotPasswordCancel') }}
|
||||||
|
//- v-spacer
|
||||||
|
|
||||||
loader(v-model='isLoading', :color='loaderColor', :title='loaderTitle', :subtitle='$t(`auth:pleaseWait`)')
|
loader(v-model='isLoading', :color='loaderColor', :title='loaderTitle', :subtitle='$t(`auth:pleaseWait`)')
|
||||||
nav-footer(color='grey darken-5', dark-color='grey darken-5')
|
|
||||||
notify
|
notify
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
/* global siteConfig */
|
/* global siteConfig */
|
||||||
|
|
||||||
|
// <span>Photo by <a href="https://unsplash.com/@isaacquesada?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Isaac Quesada</a> on <a href="/t/textures-patterns?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a></span>
|
||||||
|
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import Cookies from 'js-cookie'
|
import Cookies from 'js-cookie'
|
||||||
|
import gql from 'graphql-tag'
|
||||||
import strategiesQuery from 'gql/login/login-query-strategies.gql'
|
import { sync } from 'vuex-pathify'
|
||||||
import loginMutation from 'gql/login/login-mutation-login.gql'
|
|
||||||
import tfaMutation from 'gql/login/login-mutation-tfa.gql'
|
|
||||||
import changePasswordMutation from 'gql/login/login-mutation-changepassword.gql'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
i18nOptions: { namespaces: 'auth' },
|
i18nOptions: { namespaces: 'auth' },
|
||||||
@ -191,7 +242,8 @@ export default {
|
|||||||
return {
|
return {
|
||||||
error: false,
|
error: false,
|
||||||
strategies: [],
|
strategies: [],
|
||||||
selectedStrategy: { key: 'local' },
|
selectedStrategyKey: 'local',
|
||||||
|
selectedStrategy: { key: 'local', strategy: { useForm: true } },
|
||||||
screen: 'login',
|
screen: 'login',
|
||||||
username: '',
|
username: '',
|
||||||
password: '',
|
password: '',
|
||||||
@ -207,40 +259,39 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
activeModal: sync('editor/activeModal'),
|
||||||
siteTitle () {
|
siteTitle () {
|
||||||
return siteConfig.title
|
return siteConfig.title
|
||||||
},
|
},
|
||||||
isSocialShown () {
|
isSocialShown () {
|
||||||
return this.strategies.length > 1
|
return this.strategies.length > 1
|
||||||
}
|
},
|
||||||
|
logoUrl () { return siteConfig.logoUrl }
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
strategies(newValue, oldValue) {
|
strategies(newValue, oldValue) {
|
||||||
this.selectedStrategy = _.find(newValue, ['key', 'local'])
|
this.selectedStrategy = _.head(newValue)
|
||||||
}
|
|
||||||
},
|
},
|
||||||
mounted () {
|
selectedStrategyKey (newValue, oldValue) {
|
||||||
this.isShown = true
|
this.selectedStrategy = _.find(this.strategies, ['key', newValue])
|
||||||
this.$nextTick(() => {
|
|
||||||
this.$refs.iptEmail.focus()
|
|
||||||
})
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
/**
|
|
||||||
* SELECT STRATEGY
|
|
||||||
*/
|
|
||||||
selectStrategy (strategy) {
|
|
||||||
this.selectedStrategy = strategy
|
|
||||||
this.screen = 'login'
|
this.screen = 'login'
|
||||||
if (!strategy.useForm) {
|
if (!this.selectedStrategy.strategy.useForm) {
|
||||||
this.isLoading = true
|
this.isLoading = true
|
||||||
window.location.assign('/login/' + strategy.key)
|
window.location.assign('/login/' + newValue)
|
||||||
} else {
|
} else {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.$refs.iptEmail.focus()
|
this.$refs.iptEmail.focus()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
mounted () {
|
||||||
|
this.isShown = true
|
||||||
|
this.$nextTick(() => {
|
||||||
|
// this.$refs.iptEmail.focus()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
/**
|
/**
|
||||||
* LOGIN
|
* LOGIN
|
||||||
*/
|
*/
|
||||||
@ -265,7 +316,24 @@ export default {
|
|||||||
this.isLoading = true
|
this.isLoading = true
|
||||||
try {
|
try {
|
||||||
let resp = await this.$apollo.mutate({
|
let resp = await this.$apollo.mutate({
|
||||||
mutation: loginMutation,
|
mutation: gql`
|
||||||
|
mutation($username: String!, $password: String!, $strategy: String!) {
|
||||||
|
authentication {
|
||||||
|
login(username: $username, password: $password, strategy: $strategy) {
|
||||||
|
responseResult {
|
||||||
|
succeeded
|
||||||
|
errorCode
|
||||||
|
slug
|
||||||
|
message
|
||||||
|
}
|
||||||
|
jwt
|
||||||
|
mustChangePwd
|
||||||
|
mustProvideTFA
|
||||||
|
continuationToken
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
variables: {
|
variables: {
|
||||||
username: this.username,
|
username: this.username,
|
||||||
password: this.password,
|
password: this.password,
|
||||||
@ -334,7 +402,11 @@ export default {
|
|||||||
} else {
|
} else {
|
||||||
this.isLoading = true
|
this.isLoading = true
|
||||||
this.$apollo.mutate({
|
this.$apollo.mutate({
|
||||||
mutation: tfaMutation,
|
mutation: gql`
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
`,
|
||||||
variables: {
|
variables: {
|
||||||
continuationToken: this.continuationToken,
|
continuationToken: this.continuationToken,
|
||||||
securityCode: this.securityCode
|
securityCode: this.securityCode
|
||||||
@ -377,7 +449,11 @@ export default {
|
|||||||
this.loaderTitle = this.$t('auth:changePwd.loading')
|
this.loaderTitle = this.$t('auth:changePwd.loading')
|
||||||
this.isLoading = true
|
this.isLoading = true
|
||||||
const resp = await this.$apollo.mutate({
|
const resp = await this.$apollo.mutate({
|
||||||
mutation: changePasswordMutation,
|
mutation: gql`
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
`,
|
||||||
variables: {
|
variables: {
|
||||||
continuationToken: this.continuationToken,
|
continuationToken: this.continuationToken,
|
||||||
newPassword: this.newPassword
|
newPassword: this.newPassword
|
||||||
@ -421,8 +497,26 @@ export default {
|
|||||||
},
|
},
|
||||||
apollo: {
|
apollo: {
|
||||||
strategies: {
|
strategies: {
|
||||||
query: strategiesQuery,
|
query: gql`
|
||||||
update: (data) => data.authentication.strategies,
|
{
|
||||||
|
authentication {
|
||||||
|
activeStrategies {
|
||||||
|
key
|
||||||
|
strategy {
|
||||||
|
key
|
||||||
|
logo
|
||||||
|
color
|
||||||
|
icon
|
||||||
|
useForm
|
||||||
|
}
|
||||||
|
displayName
|
||||||
|
order
|
||||||
|
selfRegistration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
update: (data) => _.sortBy(data.authentication.activeStrategies, ['order']),
|
||||||
watchLoading (isLoading) {
|
watchLoading (isLoading) {
|
||||||
this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'login-strategies-refresh')
|
this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'login-strategies-refresh')
|
||||||
}
|
}
|
||||||
@ -433,61 +527,73 @@ export default {
|
|||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.login {
|
.login {
|
||||||
background-color: mc('indigo', '900');
|
background-image: url('/_assets/img/splash/1.jpg');
|
||||||
background-image: url('../static/svg/motif-blocks.svg');
|
background-size: cover;
|
||||||
background-repeat: repeat;
|
background-position: center center;
|
||||||
background-size: 200px;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
animation: loginBgReveal 20s linear infinite;
|
|
||||||
|
|
||||||
@include keyframes(loginBgReveal) {
|
&-sd {
|
||||||
0% {
|
background-color: rgba(255,255,255,.8);
|
||||||
background-position-y: 0;
|
backdrop-filter: blur(10px);
|
||||||
}
|
-webkit-backdrop-filter: blur(10px);
|
||||||
100% {
|
border-left: 1px solid rgba(255,255,255,.85);
|
||||||
background-position-y: 800px;
|
border-right: 1px solid rgba(255,255,255,.85);
|
||||||
}
|
width: 450px;
|
||||||
}
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
background-image: url('../static/svg/motif-overlay.svg');
|
|
||||||
background-attachment: fixed;
|
|
||||||
background-size: cover;
|
|
||||||
opacity: .5;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100vw;
|
|
||||||
height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .container {
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
align-items: center;
|
margin-left: 5vw;
|
||||||
|
|
||||||
|
@at-root .no-backdropfilter & {
|
||||||
|
background-color: rgba(255,255,255,.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
@include until($tablet) {
|
||||||
|
margin-left: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-logo {
|
||||||
|
padding: 12px 0 0 12px;
|
||||||
|
width: 58px;
|
||||||
|
height: 58px;
|
||||||
|
background-color: #222;
|
||||||
|
margin-left: 12px;
|
||||||
|
border-bottom-left-radius: 7px;
|
||||||
|
border-bottom-right-radius: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-title {
|
||||||
|
height: 58px;
|
||||||
|
padding-left: 12px;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
text-shadow: .5px .5px #FFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
.social-login-btn {
|
&-subtitle {
|
||||||
cursor: pointer;
|
padding: 24px 12px 12px 12px;
|
||||||
transition: opacity .2s ease;
|
color: #111;
|
||||||
&:hover {
|
font-weight: 500;
|
||||||
opacity: .8;
|
text-shadow: 1px 1px rgba(255,255,255,.5);
|
||||||
}
|
background-image: linear-gradient(to bottom, rgba(0,0,0,0), rgba(0,0,0,.15));
|
||||||
margin: .25rem 0;
|
|
||||||
svg {
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
bottom: 0;
|
|
||||||
path {
|
|
||||||
fill: #FFF;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.v-text-field.centered input {
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
border-bottom: 1px solid rgba(0,0,0,.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
&-list {
|
||||||
|
border-top: 1px solid rgba(255,255,255,.85);
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-form {
|
||||||
|
padding: 12px;
|
||||||
|
border-top: 1px solid rgba(255,255,255,.85);
|
||||||
|
}
|
||||||
|
|
||||||
|
&-main {
|
||||||
|
flex: 1 0 100vw;
|
||||||
|
height: 100vh;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
mutation($strategies: [AuthenticationStrategyInput]!, $config: AuthenticationConfigInput) {
|
|
||||||
authentication {
|
|
||||||
updateStrategies(strategies: $strategies, config: $config) {
|
|
||||||
responseResult {
|
|
||||||
succeeded
|
|
||||||
errorCode
|
|
||||||
slug
|
|
||||||
message
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
mutation ($id: Int!) {
|
|
||||||
groups {
|
|
||||||
delete(id: $id) {
|
|
||||||
responseResult {
|
|
||||||
succeeded
|
|
||||||
errorCode
|
|
||||||
slug
|
|
||||||
message
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
mutation ($id: Int!, $name: String!, $permissions: [String]!, $pageRules: [PageRuleInput]!) {
|
|
||||||
groups {
|
|
||||||
update(id: $id, name: $name, permissions: $permissions, pageRules: $pageRules) {
|
|
||||||
responseResult {
|
|
||||||
succeeded
|
|
||||||
errorCode
|
|
||||||
slug
|
|
||||||
message
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
query ($id: Int!) {
|
|
||||||
groups {
|
|
||||||
single(id: $id) {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
isSystem
|
|
||||||
permissions
|
|
||||||
pageRules {
|
|
||||||
id
|
|
||||||
path
|
|
||||||
roles
|
|
||||||
match
|
|
||||||
deny
|
|
||||||
locales
|
|
||||||
}
|
|
||||||
users {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
email
|
|
||||||
}
|
|
||||||
createdAt
|
|
||||||
updatedAt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -14,6 +14,8 @@ switch (window.document.documentElement.lang) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
require('modernizr')
|
||||||
|
|
||||||
require('./scss/app.scss')
|
require('./scss/app.scss')
|
||||||
import(/* webpackChunkName: "theme" */ './themes/' + siteConfig.theme + '/scss/app.scss')
|
import(/* webpackChunkName: "theme" */ './themes/' + siteConfig.theme + '/scss/app.scss')
|
||||||
|
|
||||||
|
3
client/libs/modernizr/modernizr.js
Normal file
3
client/libs/modernizr/modernizr.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
/*! modernizr 3.6.0 (Custom Build) | MIT *
|
||||||
|
* https://modernizr.com/download/?-setclasses !*/
|
||||||
|
!function(n,e,s){function o(n){var e=r.className,s=Modernizr._config.classPrefix||"";if(c&&(e=e.baseVal),Modernizr._config.enableJSClass){var o=new RegExp("(^|\\s)"+s+"no-js(\\s|$)");e=e.replace(o,"$1"+s+"js$2")}Modernizr._config.enableClasses&&(e+=" "+s+n.join(" "+s),c?r.className.baseVal=e:r.className=e)}function a(n,e){return typeof n===e}function i(){var n,e,s,o,i,l,r;for(var c in f)if(f.hasOwnProperty(c)){if(n=[],e=f[c],e.name&&(n.push(e.name.toLowerCase()),e.options&&e.options.aliases&&e.options.aliases.length))for(s=0;s<e.options.aliases.length;s++)n.push(e.options.aliases[s].toLowerCase());for(o=a(e.fn,"function")?e.fn():e.fn,i=0;i<n.length;i++)l=n[i],r=l.split("."),1===r.length?Modernizr[r[0]]=o:(!Modernizr[r[0]]||Modernizr[r[0]]instanceof Boolean||(Modernizr[r[0]]=new Boolean(Modernizr[r[0]])),Modernizr[r[0]][r[1]]=o),t.push((o?"":"no-")+r.join("-"))}}var t=[],f=[],l={_version:"3.6.0",_config:{classPrefix:"",enableClasses:!0,enableJSClass:!0,usePrefixes:!0},_q:[],on:function(n,e){var s=this;setTimeout(function(){e(s[n])},0)},addTest:function(n,e,s){f.push({name:n,fn:e,options:s})},addAsyncTest:function(n){f.push({name:null,fn:n})}},Modernizr=function(){};Modernizr.prototype=l,Modernizr=new Modernizr;var r=e.documentElement,c="svg"===r.nodeName.toLowerCase();i(),o(t),delete l.addTest,delete l.addAsyncTest;for(var u=0;u<Modernizr._q.length;u++)Modernizr._q[u]();n.Modernizr=Modernizr}(window,document);
|
BIN
client/static/img/splash/1.jpg
Normal file
BIN
client/static/img/splash/1.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 602 KiB |
BIN
client/static/img/splash/2.jpg
Normal file
BIN
client/static/img/splash/2.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 512 KiB |
@ -13,7 +13,8 @@ const state = {
|
|||||||
searchIsFocused: false,
|
searchIsFocused: false,
|
||||||
searchIsLoading: false,
|
searchIsLoading: false,
|
||||||
searchRestrictLocale: false,
|
searchRestrictLocale: false,
|
||||||
searchRestrictPath: false
|
searchRestrictPath: false,
|
||||||
|
printView: false
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
<template lang="pug">
|
<template lang="pug">
|
||||||
v-app(v-scroll='upBtnScroll', :dark='$vuetify.theme.dark', :class='$vuetify.rtl ? `is-rtl` : `is-ltr`')
|
v-app(v-scroll='upBtnScroll', :dark='$vuetify.theme.dark', :class='$vuetify.rtl ? `is-rtl` : `is-ltr`')
|
||||||
nav-header
|
nav-header(v-if='!printView')
|
||||||
v-navigation-drawer(
|
v-navigation-drawer(
|
||||||
v-if='navMode !== `NONE`'
|
v-if='navMode !== `NONE` && !printView'
|
||||||
:class='$vuetify.theme.dark ? `grey darken-4-d4` : `primary`'
|
:class='$vuetify.theme.dark ? `grey darken-4-d4` : `primary`'
|
||||||
dark
|
dark
|
||||||
app
|
app
|
||||||
@ -171,7 +171,8 @@
|
|||||||
)
|
)
|
||||||
v-tooltip(bottom)
|
v-tooltip(bottom)
|
||||||
template(v-slot:activator='{ on }')
|
template(v-slot:activator='{ on }')
|
||||||
v-btn(icon, tile, v-on='on', @click='print'): v-icon(color='grey') mdi-printer
|
v-btn(icon, tile, v-on='on', @click='print')
|
||||||
|
v-icon(:color='printView ? `primary` : `grey`') mdi-printer
|
||||||
span {{$t('common:page.printFormat')}}
|
span {{$t('common:page.printFormat')}}
|
||||||
v-spacer
|
v-spacer
|
||||||
|
|
||||||
@ -264,7 +265,7 @@
|
|||||||
.caption {{$t('common:page.unpublishedWarning')}}
|
.caption {{$t('common:page.unpublishedWarning')}}
|
||||||
.contents(ref='container')
|
.contents(ref='container')
|
||||||
slot(name='contents')
|
slot(name='contents')
|
||||||
.comments-container#discussion(v-if='commentsEnabled && commentsPerms.read')
|
.comments-container#discussion(v-if='commentsEnabled && commentsPerms.read && !printView')
|
||||||
.comments-header
|
.comments-header
|
||||||
v-icon.mr-2(dark) mdi-comment-text-outline
|
v-icon.mr-2(dark) mdi-comment-text-outline
|
||||||
span {{$t('common:comments.title')}}
|
span {{$t('common:comments.title')}}
|
||||||
@ -297,7 +298,7 @@ import Tabset from './tabset.vue'
|
|||||||
import NavSidebar from './nav-sidebar.vue'
|
import NavSidebar from './nav-sidebar.vue'
|
||||||
import Prism from 'prismjs'
|
import Prism from 'prismjs'
|
||||||
import mermaid from 'mermaid'
|
import mermaid from 'mermaid'
|
||||||
import { get } from 'vuex-pathify'
|
import { get, sync } from 'vuex-pathify'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import ClipboardJS from 'clipboard'
|
import ClipboardJS from 'clipboard'
|
||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
@ -490,7 +491,8 @@ export default {
|
|||||||
hasAnyPagePermissions () {
|
hasAnyPagePermissions () {
|
||||||
return this.hasAdminPermission || this.hasWritePagesPermission || this.hasManagePagesPermission ||
|
return this.hasAdminPermission || this.hasWritePagesPermission || this.hasManagePagesPermission ||
|
||||||
this.hasDeletePagesPermission || this.hasReadSourcePermission || this.hasReadHistoryPermission
|
this.hasDeletePagesPermission || this.hasReadSourcePermission || this.hasReadHistoryPermission
|
||||||
}
|
},
|
||||||
|
printView: sync('site/printView')
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.$store.set('page/authorId', this.authorId)
|
this.$store.set('page/authorId', this.authorId)
|
||||||
@ -566,7 +568,14 @@ export default {
|
|||||||
this.upBtnShown = scrollOffset > window.innerHeight * 0.33
|
this.upBtnShown = scrollOffset > window.innerHeight * 0.33
|
||||||
},
|
},
|
||||||
print () {
|
print () {
|
||||||
|
if (this.printView) {
|
||||||
|
this.printView = false
|
||||||
|
} else {
|
||||||
|
this.printView = true
|
||||||
|
this.$nextTick(() => {
|
||||||
window.print()
|
window.print()
|
||||||
|
})
|
||||||
|
}
|
||||||
},
|
},
|
||||||
pageEdit () {
|
pageEdit () {
|
||||||
this.$root.$emit('pageEdit')
|
this.$root.$emit('pageEdit')
|
||||||
|
@ -991,4 +991,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.comments-container {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -173,6 +173,10 @@ module.exports = {
|
|||||||
outputPath: 'fonts/'
|
outputPath: 'fonts/'
|
||||||
}
|
}
|
||||||
}]
|
}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loader: 'webpack-modernizr-loader',
|
||||||
|
test: /\.modernizrrc\.js$/
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -253,7 +257,8 @@ module.exports = {
|
|||||||
// Duplicates fixes:
|
// Duplicates fixes:
|
||||||
'apollo-link': path.join(process.cwd(), 'node_modules/apollo-link'),
|
'apollo-link': path.join(process.cwd(), 'node_modules/apollo-link'),
|
||||||
'apollo-utilities': path.join(process.cwd(), 'node_modules/apollo-utilities'),
|
'apollo-utilities': path.join(process.cwd(), 'node_modules/apollo-utilities'),
|
||||||
'uc.micro': path.join(process.cwd(), 'node_modules/uc.micro')
|
'uc.micro': path.join(process.cwd(), 'node_modules/uc.micro'),
|
||||||
|
'modernizr$': path.resolve(process.cwd(), 'client/.modernizrrc.js')
|
||||||
},
|
},
|
||||||
extensions: [
|
extensions: [
|
||||||
'.js',
|
'.js',
|
||||||
|
@ -224,6 +224,7 @@
|
|||||||
"babel-plugin-transform-imports": "2.0.0",
|
"babel-plugin-transform-imports": "2.0.0",
|
||||||
"cache-loader": "4.1.0",
|
"cache-loader": "4.1.0",
|
||||||
"canvas-confetti": "1.2.0",
|
"canvas-confetti": "1.2.0",
|
||||||
|
"cash-dom": "8.0.0",
|
||||||
"chart.js": "2.9.3",
|
"chart.js": "2.9.3",
|
||||||
"clean-webpack-plugin": "3.0.0",
|
"clean-webpack-plugin": "3.0.0",
|
||||||
"clipboard": "2.0.6",
|
"clipboard": "2.0.6",
|
||||||
@ -318,6 +319,7 @@
|
|||||||
"webpack-dev-middleware": "3.7.2",
|
"webpack-dev-middleware": "3.7.2",
|
||||||
"webpack-hot-middleware": "2.25.0",
|
"webpack-hot-middleware": "2.25.0",
|
||||||
"webpack-merge": "4.2.2",
|
"webpack-merge": "4.2.2",
|
||||||
|
"webpack-modernizr-loader": "5.0.0",
|
||||||
"webpack-subresource-integrity": "1.4.1",
|
"webpack-subresource-integrity": "1.4.1",
|
||||||
"webpackbar": "4.0.0",
|
"webpackbar": "4.0.0",
|
||||||
"whatwg-fetch": "3.0.0",
|
"whatwg-fetch": "3.0.0",
|
||||||
|
@ -53,6 +53,12 @@ defaults:
|
|||||||
theme: 'default'
|
theme: 'default'
|
||||||
iconset: 'md'
|
iconset: 'md'
|
||||||
darkMode: false
|
darkMode: false
|
||||||
|
auth:
|
||||||
|
autoLogin: false
|
||||||
|
loginBgUrl: ''
|
||||||
|
audience: 'urn:wiki.js'
|
||||||
|
tokenExpiration: '30m'
|
||||||
|
tokenRenewal: '14d'
|
||||||
features:
|
features:
|
||||||
featurePageRatings: true
|
featurePageRatings: true
|
||||||
featurePageComments: true
|
featurePageComments: true
|
||||||
|
@ -78,9 +78,8 @@ module.exports = {
|
|||||||
const enabledStrategies = await WIKI.models.authentication.getStrategies()
|
const enabledStrategies = await WIKI.models.authentication.getStrategies()
|
||||||
for (let idx in enabledStrategies) {
|
for (let idx in enabledStrategies) {
|
||||||
const stg = enabledStrategies[idx]
|
const stg = enabledStrategies[idx]
|
||||||
if (!stg.isEnabled) { continue }
|
|
||||||
try {
|
try {
|
||||||
const strategy = require(`../modules/authentication/${stg.key}/authentication.js`)
|
const strategy = require(`../modules/authentication/${stg.strategyKey}/authentication.js`)
|
||||||
|
|
||||||
stg.config.callbackURL = `${WIKI.config.host}/login/${stg.key}/callback`
|
stg.config.callbackURL = `${WIKI.config.host}/login/${stg.key}/callback`
|
||||||
strategy.init(passport, stg.config)
|
strategy.init(passport, stg.config)
|
||||||
@ -90,9 +89,9 @@ module.exports = {
|
|||||||
...strategy,
|
...strategy,
|
||||||
...stg
|
...stg
|
||||||
}
|
}
|
||||||
WIKI.logger.info(`Authentication Strategy ${stg.key}: [ OK ]`)
|
WIKI.logger.info(`Authentication Strategy ${stg.displayName}: [ OK ]`)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
WIKI.logger.error(`Authentication Strategy ${stg.key}: [ FAILED ]`)
|
WIKI.logger.error(`Authentication Strategy ${stg.displayName} (${stg.key}): [ FAILED ]`)
|
||||||
WIKI.logger.error(err)
|
WIKI.logger.error(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
23
server/db/migrations-sqlite/2.5.1.js
Normal file
23
server/db/migrations-sqlite/2.5.1.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
exports.up = async knex => {
|
||||||
|
await knex('authentication').where('isEnabled', false).del()
|
||||||
|
|
||||||
|
await knex.schema
|
||||||
|
.alterTable('authentication', table => {
|
||||||
|
table.dropColumn('isEnabled')
|
||||||
|
table.integer('order').unsigned().notNullable().defaultTo(0)
|
||||||
|
table.string('strategyKey').notNullable().defaultTo('')
|
||||||
|
table.string('displayName').notNullable().defaultTo('')
|
||||||
|
})
|
||||||
|
|
||||||
|
// Fix pre-2.5 strategies
|
||||||
|
const strategies = await knex('authentication')
|
||||||
|
let idx = 1
|
||||||
|
for (const strategy of strategies) {
|
||||||
|
await knex('authentication').where('key', strategy.key).update({
|
||||||
|
strategyKey: strategy.key,
|
||||||
|
order: (strategy.key === 'local') ? 0 : idx++
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.down = knex => { }
|
8
server/db/migrations-sqlite/2.5.12.js
Normal file
8
server/db/migrations-sqlite/2.5.12.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
exports.up = async knex => {
|
||||||
|
await knex.schema
|
||||||
|
.alterTable('groups', table => {
|
||||||
|
table.string('redirectOnLogin').notNullable().defaultTo('/')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.down = knex => { }
|
23
server/db/migrations/2.5.1.js
Normal file
23
server/db/migrations/2.5.1.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
exports.up = async knex => {
|
||||||
|
await knex('authentication').where('isEnabled', false).del()
|
||||||
|
|
||||||
|
await knex.schema
|
||||||
|
.alterTable('authentication', table => {
|
||||||
|
table.dropColumn('isEnabled')
|
||||||
|
table.integer('order').unsigned().notNullable().defaultTo(0)
|
||||||
|
table.string('strategyKey').notNullable().defaultTo('')
|
||||||
|
table.string('displayName').notNullable().defaultTo('')
|
||||||
|
})
|
||||||
|
|
||||||
|
// Fix pre-2.5 strategies
|
||||||
|
const strategies = await knex('authentication')
|
||||||
|
let idx = 1
|
||||||
|
for (const strategy of strategies) {
|
||||||
|
await knex('authentication').where('key', strategy.key).update({
|
||||||
|
strategyKey: strategy.key,
|
||||||
|
order: (strategy.key === 'local') ? 0 : idx++
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.down = knex => { }
|
8
server/db/migrations/2.5.12.js
Normal file
8
server/db/migrations/2.5.12.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
exports.up = async knex => {
|
||||||
|
await knex.schema
|
||||||
|
.alterTable('groups', table => {
|
||||||
|
table.string('redirectOnLogin').notNullable().defaultTo('/')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.down = knex => { }
|
@ -34,16 +34,28 @@ module.exports = {
|
|||||||
apiState () {
|
apiState () {
|
||||||
return WIKI.config.api.isEnabled
|
return WIKI.config.api.isEnabled
|
||||||
},
|
},
|
||||||
|
async strategies () {
|
||||||
|
return WIKI.data.authentication.map(stg => ({
|
||||||
|
...stg,
|
||||||
|
isAvailable: stg.isAvailable === true,
|
||||||
|
props: _.sortBy(_.transform(stg.props, (res, value, key) => {
|
||||||
|
res.push({
|
||||||
|
key,
|
||||||
|
value: JSON.stringify(value)
|
||||||
|
})
|
||||||
|
}, []), 'key')
|
||||||
|
}))
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* Fetch active authentication strategies
|
* Fetch active authentication strategies
|
||||||
*/
|
*/
|
||||||
async strategies (obj, args, context, info) {
|
async activeStrategies (obj, args, context, info) {
|
||||||
let strategies = await WIKI.models.authentication.getStrategies(args.isEnabled)
|
let strategies = await WIKI.models.authentication.getStrategies()
|
||||||
strategies = strategies.map(stg => {
|
strategies = strategies.map(stg => {
|
||||||
const strategyInfo = _.find(WIKI.data.authentication, ['key', stg.key]) || {}
|
const strategyInfo = _.find(WIKI.data.authentication, ['key', stg.strategyKey]) || {}
|
||||||
return {
|
return {
|
||||||
...strategyInfo,
|
|
||||||
...stg,
|
...stg,
|
||||||
|
strategy: strategyInfo,
|
||||||
config: _.sortBy(_.transform(stg.config, (res, value, key) => {
|
config: _.sortBy(_.transform(stg.config, (res, value, key) => {
|
||||||
const configData = _.get(strategyInfo.props, key, false)
|
const configData = _.get(strategyInfo.props, key, false)
|
||||||
if (configData) {
|
if (configData) {
|
||||||
@ -174,16 +186,18 @@ module.exports = {
|
|||||||
*/
|
*/
|
||||||
async updateStrategies (obj, args, context) {
|
async updateStrategies (obj, args, context) {
|
||||||
try {
|
try {
|
||||||
WIKI.config.auth = {
|
// WIKI.config.auth = {
|
||||||
audience: _.get(args, 'config.audience', WIKI.config.auth.audience),
|
// audience: _.get(args, 'config.audience', WIKI.config.auth.audience),
|
||||||
tokenExpiration: _.get(args, 'config.tokenExpiration', WIKI.config.auth.tokenExpiration),
|
// tokenExpiration: _.get(args, 'config.tokenExpiration', WIKI.config.auth.tokenExpiration),
|
||||||
tokenRenewal: _.get(args, 'config.tokenRenewal', WIKI.config.auth.tokenRenewal)
|
// tokenRenewal: _.get(args, 'config.tokenRenewal', WIKI.config.auth.tokenRenewal)
|
||||||
}
|
// }
|
||||||
await WIKI.configSvc.saveToDb(['auth'])
|
// await WIKI.configSvc.saveToDb(['auth'])
|
||||||
|
|
||||||
for (let str of args.strategies) {
|
const previousStrategies = await WIKI.models.authentication.getStrategies()
|
||||||
await WIKI.models.authentication.query().patch({
|
for (const str of args.strategies) {
|
||||||
isEnabled: str.isEnabled,
|
const newStr = {
|
||||||
|
displayName: str.displayName,
|
||||||
|
order: str.order,
|
||||||
config: _.reduce(str.config, (result, value, key) => {
|
config: _.reduce(str.config, (result, value, key) => {
|
||||||
_.set(result, `${value.key}`, _.get(JSON.parse(value.value), 'v', null))
|
_.set(result, `${value.key}`, _.get(JSON.parse(value.value), 'v', null))
|
||||||
return result
|
return result
|
||||||
@ -191,8 +205,32 @@ module.exports = {
|
|||||||
selfRegistration: str.selfRegistration,
|
selfRegistration: str.selfRegistration,
|
||||||
domainWhitelist: { v: str.domainWhitelist },
|
domainWhitelist: { v: str.domainWhitelist },
|
||||||
autoEnrollGroups: { v: str.autoEnrollGroups }
|
autoEnrollGroups: { v: str.autoEnrollGroups }
|
||||||
}).where('key', str.key)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_.some(previousStrategies, ['key', str.key])) {
|
||||||
|
await WIKI.models.authentication.query().patch({
|
||||||
|
key: str.key,
|
||||||
|
strategyKey: str.strategyKey,
|
||||||
|
...newStr
|
||||||
|
}).where('key', str.key)
|
||||||
|
} else {
|
||||||
|
await WIKI.models.authentication.query().insert({
|
||||||
|
key: str.key,
|
||||||
|
strategyKey: str.strategyKey,
|
||||||
|
...newStr
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const str of _.differenceBy(previousStrategies, args.strategies, 'key')) {
|
||||||
|
const hasUsers = await WIKI.models.users.query().count('* as total').where({ providerKey: str.key }).first()
|
||||||
|
if (_.toSafeInteger(hasUsers.total) > 0) {
|
||||||
|
throw new Error(`Cannot delete ${str.displayName} as 1 or more users are still using it.`)
|
||||||
|
} else {
|
||||||
|
await WIKI.models.authentication.query().delete().where('key', str.key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await WIKI.auth.activateStrategies()
|
await WIKI.auth.activateStrategies()
|
||||||
WIKI.events.outbound.emit('reloadAuthStrategies')
|
WIKI.events.outbound.emit('reloadAuthStrategies')
|
||||||
return {
|
return {
|
||||||
|
@ -102,8 +102,13 @@ module.exports = {
|
|||||||
throw new gql.GraphQLError('Some Page Rules contains unsafe or exponential time regex.')
|
throw new gql.GraphQLError('Some Page Rules contains unsafe or exponential time regex.')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_.isEmpty(args.redirectOnLogin)) {
|
||||||
|
args.redirectOnLogin = '/'
|
||||||
|
}
|
||||||
|
|
||||||
await WIKI.models.groups.query().patch({
|
await WIKI.models.groups.query().patch({
|
||||||
name: args.name,
|
name: args.name,
|
||||||
|
redirectOnLogin: args.redirectOnLogin,
|
||||||
permissions: JSON.stringify(args.permissions),
|
permissions: JSON.stringify(args.permissions),
|
||||||
pageRules: JSON.stringify(args.pageRules)
|
pageRules: JSON.stringify(args.pageRules)
|
||||||
}).where('id', args.id)
|
}).where('id', args.id)
|
||||||
|
@ -171,9 +171,10 @@ module.exports = {
|
|||||||
* FETCH TAGS
|
* FETCH TAGS
|
||||||
*/
|
*/
|
||||||
async tags (obj, args, context, info) {
|
async tags (obj, args, context, info) {
|
||||||
const pages = await WIKI.models.pages.query().column([
|
const pages = await WIKI.models.pages.query()
|
||||||
|
.column([
|
||||||
'path',
|
'path',
|
||||||
{ locale: 'localeCode' },
|
{ locale: 'localeCode' }
|
||||||
])
|
])
|
||||||
.withGraphJoined('tags')
|
.withGraphJoined('tags')
|
||||||
const allTags = _.filter(pages, r => {
|
const allTags = _.filter(pages, r => {
|
||||||
@ -181,8 +182,7 @@ module.exports = {
|
|||||||
path: r.path,
|
path: r.path,
|
||||||
locale: r.locale
|
locale: r.locale
|
||||||
})
|
})
|
||||||
})
|
}).flatMap(r => r.tags)
|
||||||
.flatMap(r => r.tags)
|
|
||||||
return _.orderBy(_.uniqBy(allTags, 'id'), ['tag'], ['asc'])
|
return _.orderBy(_.uniqBy(allTags, 'id'), ['tag'], ['asc'])
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
@ -190,9 +190,10 @@ module.exports = {
|
|||||||
*/
|
*/
|
||||||
async searchTags (obj, args, context, info) {
|
async searchTags (obj, args, context, info) {
|
||||||
const query = _.trim(args.query)
|
const query = _.trim(args.query)
|
||||||
const pages = await WIKI.models.pages.query().column([
|
const pages = await WIKI.models.pages.query()
|
||||||
|
.column([
|
||||||
'path',
|
'path',
|
||||||
{ locale: 'localeCode' },
|
{ locale: 'localeCode' }
|
||||||
])
|
])
|
||||||
.withGraphJoined('tags')
|
.withGraphJoined('tags')
|
||||||
.modifyGraph('tags', builder => {
|
.modifyGraph('tags', builder => {
|
||||||
@ -212,9 +213,7 @@ module.exports = {
|
|||||||
path: r.path,
|
path: r.path,
|
||||||
locale: r.locale
|
locale: r.locale
|
||||||
})
|
})
|
||||||
})
|
}).flatMap(r => r.tags).map(t => t.tag)
|
||||||
.flatMap(r => r.tags)
|
|
||||||
.map(t => t.tag)
|
|
||||||
return _.uniq(allTags).slice(0, 5)
|
return _.uniq(allTags).slice(0, 5)
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
@ -271,7 +270,7 @@ module.exports = {
|
|||||||
* FETCH PAGE LINKS
|
* FETCH PAGE LINKS
|
||||||
*/
|
*/
|
||||||
async links (obj, args, context, info) {
|
async links (obj, args, context, info) {
|
||||||
let results;
|
let results
|
||||||
|
|
||||||
if (WIKI.config.db.type === 'mysql' || WIKI.config.db.type === 'mariadb' || WIKI.config.db.type === 'sqlite') {
|
if (WIKI.config.db.type === 'mysql' || WIKI.config.db.type === 'mariadb' || WIKI.config.db.type === 'sqlite') {
|
||||||
results = await WIKI.models.knex('pages')
|
results = await WIKI.models.knex('pages')
|
||||||
|
@ -21,6 +21,11 @@ module.exports = {
|
|||||||
...WIKI.config.seo,
|
...WIKI.config.seo,
|
||||||
...WIKI.config.features,
|
...WIKI.config.features,
|
||||||
...WIKI.config.security,
|
...WIKI.config.security,
|
||||||
|
authAutoLogin: WIKI.config.auth.autoLogin,
|
||||||
|
authLoginBgUrl: WIKI.config.auth.loginBgUrl,
|
||||||
|
authJwtAudience: WIKI.config.auth.audience,
|
||||||
|
authJwtExpiration: WIKI.config.auth.tokenExpiration,
|
||||||
|
authJwtRenewablePeriod: WIKI.config.auth.tokenRenewal,
|
||||||
uploadMaxFileSize: WIKI.config.uploads.maxFileSize,
|
uploadMaxFileSize: WIKI.config.uploads.maxFileSize,
|
||||||
uploadMaxFiles: WIKI.config.uploads.maxFiles
|
uploadMaxFiles: WIKI.config.uploads.maxFiles
|
||||||
}
|
}
|
||||||
@ -60,6 +65,14 @@ module.exports = {
|
|||||||
analyticsId: _.get(args, 'analyticsId', WIKI.config.seo.analyticsId)
|
analyticsId: _.get(args, 'analyticsId', WIKI.config.seo.analyticsId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WIKI.config.auth = {
|
||||||
|
autoLogin: _.get(args, 'authAutoLogin', WIKI.config.auth.autoLogin),
|
||||||
|
loginBgUrl: _.get(args, 'authLoginBgUrl', WIKI.config.auth.loginBgUrl),
|
||||||
|
audience: _.get(args, 'authJwtAudience', WIKI.config.auth.audience),
|
||||||
|
tokenExpiration: _.get(args, 'authJwtExpiration', WIKI.config.auth.tokenExpiration),
|
||||||
|
tokenRenewal: _.get(args, 'authJwtRenewablePeriod', WIKI.config.auth.tokenRenewal)
|
||||||
|
}
|
||||||
|
|
||||||
WIKI.config.features = {
|
WIKI.config.features = {
|
||||||
featurePageRatings: _.get(args, 'featurePageRatings', WIKI.config.features.featurePageRatings),
|
featurePageRatings: _.get(args, 'featurePageRatings', WIKI.config.features.featurePageRatings),
|
||||||
featurePageComments: _.get(args, 'featurePageComments', WIKI.config.features.featurePageComments),
|
featurePageComments: _.get(args, 'featurePageComments', WIKI.config.features.featurePageComments),
|
||||||
@ -83,7 +96,7 @@ module.exports = {
|
|||||||
maxFiles: _.get(args, 'uploadMaxFiles', WIKI.config.uploads.maxFiles)
|
maxFiles: _.get(args, 'uploadMaxFiles', WIKI.config.uploads.maxFiles)
|
||||||
}
|
}
|
||||||
|
|
||||||
await WIKI.configSvc.saveToDb(['host', 'title', 'company', 'contentLicense', 'seo', 'logoUrl', 'features', 'security', 'uploads'])
|
await WIKI.configSvc.saveToDb(['host', 'title', 'company', 'contentLicense', 'seo', 'logoUrl', 'auth', 'features', 'security', 'uploads'])
|
||||||
|
|
||||||
if (WIKI.config.security.securityTrustProxy) {
|
if (WIKI.config.security.securityTrustProxy) {
|
||||||
WIKI.app.enable('trust proxy')
|
WIKI.app.enable('trust proxy')
|
||||||
|
@ -19,9 +19,8 @@ type AuthenticationQuery {
|
|||||||
|
|
||||||
apiState: Boolean! @auth(requires: ["manage:system", "manage:api"])
|
apiState: Boolean! @auth(requires: ["manage:system", "manage:api"])
|
||||||
|
|
||||||
strategies(
|
strategies: [AuthenticationStrategy] @auth(requires: ["manage:system"])
|
||||||
isEnabled: Boolean
|
activeStrategies: [AuthenticationActiveStrategy]
|
||||||
): [AuthenticationStrategy]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# -----------------------------------------------
|
# -----------------------------------------------
|
||||||
@ -68,7 +67,6 @@ type AuthenticationMutation {
|
|||||||
|
|
||||||
updateStrategies(
|
updateStrategies(
|
||||||
strategies: [AuthenticationStrategyInput]!
|
strategies: [AuthenticationStrategyInput]!
|
||||||
config: AuthenticationConfigInput
|
|
||||||
): DefaultResponse @auth(requires: ["manage:system"])
|
): DefaultResponse @auth(requires: ["manage:system"])
|
||||||
|
|
||||||
regenerateCertificates: DefaultResponse @auth(requires: ["manage:system"])
|
regenerateCertificates: DefaultResponse @auth(requires: ["manage:system"])
|
||||||
@ -81,9 +79,8 @@ type AuthenticationMutation {
|
|||||||
# -----------------------------------------------
|
# -----------------------------------------------
|
||||||
|
|
||||||
type AuthenticationStrategy {
|
type AuthenticationStrategy {
|
||||||
isEnabled: Boolean!
|
|
||||||
key: String!
|
key: String!
|
||||||
props: [String]
|
props: [KeyValuePair] @auth(requires: ["manage:system"])
|
||||||
title: String!
|
title: String!
|
||||||
description: String
|
description: String
|
||||||
isAvailable: Boolean
|
isAvailable: Boolean
|
||||||
@ -92,6 +89,13 @@ type AuthenticationStrategy {
|
|||||||
color: String
|
color: String
|
||||||
website: String
|
website: String
|
||||||
icon: String
|
icon: String
|
||||||
|
}
|
||||||
|
|
||||||
|
type AuthenticationActiveStrategy {
|
||||||
|
key: String!
|
||||||
|
strategy: AuthenticationStrategy!
|
||||||
|
displayName: String!
|
||||||
|
order: Int!
|
||||||
config: [KeyValuePair] @auth(requires: ["manage:system"])
|
config: [KeyValuePair] @auth(requires: ["manage:system"])
|
||||||
selfRegistration: Boolean!
|
selfRegistration: Boolean!
|
||||||
domainWhitelist: [String]! @auth(requires: ["manage:system"])
|
domainWhitelist: [String]! @auth(requires: ["manage:system"])
|
||||||
@ -112,20 +116,16 @@ type AuthenticationRegisterResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
input AuthenticationStrategyInput {
|
input AuthenticationStrategyInput {
|
||||||
isEnabled: Boolean!
|
|
||||||
key: String!
|
key: String!
|
||||||
|
strategyKey: String!
|
||||||
config: [KeyValuePairInput]
|
config: [KeyValuePairInput]
|
||||||
|
displayName: String!
|
||||||
|
order: Int!
|
||||||
selfRegistration: Boolean!
|
selfRegistration: Boolean!
|
||||||
domainWhitelist: [String]!
|
domainWhitelist: [String]!
|
||||||
autoEnrollGroups: [Int]!
|
autoEnrollGroups: [Int]!
|
||||||
}
|
}
|
||||||
|
|
||||||
input AuthenticationConfigInput {
|
|
||||||
audience: String!
|
|
||||||
tokenExpiration: String!
|
|
||||||
tokenRenewal: String!
|
|
||||||
}
|
|
||||||
|
|
||||||
type AuthenticationApiKey {
|
type AuthenticationApiKey {
|
||||||
id: Int!
|
id: Int!
|
||||||
name: String!
|
name: String!
|
||||||
|
@ -37,6 +37,7 @@ type GroupMutation {
|
|||||||
update(
|
update(
|
||||||
id: Int!
|
id: Int!
|
||||||
name: String!
|
name: String!
|
||||||
|
redirectOnLogin: String!
|
||||||
permissions: [String]!
|
permissions: [String]!
|
||||||
pageRules: [PageRuleInput]!
|
pageRules: [PageRuleInput]!
|
||||||
): DefaultResponse @auth(requires: ["write:groups", "manage:groups", "manage:system"])
|
): DefaultResponse @auth(requires: ["write:groups", "manage:groups", "manage:system"])
|
||||||
@ -78,6 +79,7 @@ type Group {
|
|||||||
id: Int!
|
id: Int!
|
||||||
name: String!
|
name: String!
|
||||||
isSystem: Boolean!
|
isSystem: Boolean!
|
||||||
|
redirectOnLogin: String
|
||||||
permissions: [String]!
|
permissions: [String]!
|
||||||
pageRules: [PageRule]
|
pageRules: [PageRule]
|
||||||
users: [UserMinimal]
|
users: [UserMinimal]
|
||||||
|
@ -33,6 +33,11 @@ type SiteMutation {
|
|||||||
company: String
|
company: String
|
||||||
contentLicense: String
|
contentLicense: String
|
||||||
logoUrl: String
|
logoUrl: String
|
||||||
|
authAutoLogin: Boolean
|
||||||
|
authLoginBgUrl: String
|
||||||
|
authJwtAudience: String
|
||||||
|
authJwtExpiration: String
|
||||||
|
authJwtRenewablePeriod: String
|
||||||
featurePageRatings: Boolean
|
featurePageRatings: Boolean
|
||||||
featurePageComments: Boolean
|
featurePageComments: Boolean
|
||||||
featurePersonalWikis: Boolean
|
featurePersonalWikis: Boolean
|
||||||
@ -65,6 +70,11 @@ type SiteConfig {
|
|||||||
company: String!
|
company: String!
|
||||||
contentLicense: String!
|
contentLicense: String!
|
||||||
logoUrl: String!
|
logoUrl: String!
|
||||||
|
authAutoLogin: Boolean
|
||||||
|
authLoginBgUrl: String
|
||||||
|
authJwtAudience: String
|
||||||
|
authJwtExpiration: String
|
||||||
|
authJwtRenewablePeriod: String
|
||||||
featurePageRatings: Boolean!
|
featurePageRatings: Boolean!
|
||||||
featurePageComments: Boolean!
|
featurePageComments: Boolean!
|
||||||
featurePersonalWikis: Boolean!
|
featurePersonalWikis: Boolean!
|
||||||
|
@ -17,11 +17,10 @@ module.exports = class Authentication extends Model {
|
|||||||
static get jsonSchema () {
|
static get jsonSchema () {
|
||||||
return {
|
return {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
required: ['key', 'isEnabled'],
|
required: ['key'],
|
||||||
|
|
||||||
properties: {
|
properties: {
|
||||||
key: {type: 'string'},
|
key: {type: 'string'},
|
||||||
isEnabled: {type: 'boolean'},
|
|
||||||
selfRegistration: {type: 'boolean'}
|
selfRegistration: {type: 'boolean'}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -35,8 +34,8 @@ module.exports = class Authentication extends Model {
|
|||||||
return WIKI.models.authentication.query().findOne({ key })
|
return WIKI.models.authentication.query().findOne({ key })
|
||||||
}
|
}
|
||||||
|
|
||||||
static async getStrategies(isEnabled) {
|
static async getStrategies() {
|
||||||
const strategies = await WIKI.models.authentication.query().where(_.isBoolean(isEnabled) ? { isEnabled } : {})
|
const strategies = await WIKI.models.authentication.query().orderBy('order')
|
||||||
return _.sortBy(strategies.map(str => ({
|
return _.sortBy(strategies.map(str => ({
|
||||||
...str,
|
...str,
|
||||||
domainWhitelist: _.get(str.domainWhitelist, 'v', []),
|
domainWhitelist: _.get(str.domainWhitelist, 'v', []),
|
||||||
@ -45,7 +44,7 @@ module.exports = class Authentication extends Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static async getStrategiesForLegacyClient() {
|
static async getStrategiesForLegacyClient() {
|
||||||
const strategies = await WIKI.models.authentication.query().select('key', 'selfRegistration').where({ isEnabled: true })
|
const strategies = await WIKI.models.authentication.query().select('key', 'selfRegistration')
|
||||||
let formStrategies = []
|
let formStrategies = []
|
||||||
let socialStrategies = []
|
let socialStrategies = []
|
||||||
|
|
||||||
@ -77,64 +76,42 @@ module.exports = class Authentication extends Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static async refreshStrategiesFromDisk() {
|
static async refreshStrategiesFromDisk() {
|
||||||
let trx
|
|
||||||
try {
|
try {
|
||||||
const dbStrategies = await WIKI.models.authentication.query()
|
const dbStrategies = await WIKI.models.authentication.query()
|
||||||
|
|
||||||
// -> Fetch definitions from disk
|
// -> Fetch definitions from disk
|
||||||
const authDirs = await fs.readdir(path.join(WIKI.SERVERPATH, 'modules/authentication'))
|
const authDirs = await fs.readdir(path.join(WIKI.SERVERPATH, 'modules/authentication'))
|
||||||
let diskStrategies = []
|
WIKI.data.authentication = []
|
||||||
for (let dir of authDirs) {
|
for (let dir of authDirs) {
|
||||||
const def = await fs.readFile(path.join(WIKI.SERVERPATH, 'modules/authentication', dir, 'definition.yml'), 'utf8')
|
const defRaw = await fs.readFile(path.join(WIKI.SERVERPATH, 'modules/authentication', dir, 'definition.yml'), 'utf8')
|
||||||
diskStrategies.push(yaml.safeLoad(def))
|
const def = yaml.safeLoad(defRaw)
|
||||||
}
|
WIKI.data.authentication.push({
|
||||||
WIKI.data.authentication = diskStrategies.map(strategy => ({
|
...def,
|
||||||
...strategy,
|
props: commonHelper.parseModuleProps(def.props)
|
||||||
props: commonHelper.parseModuleProps(strategy.props)
|
|
||||||
}))
|
|
||||||
|
|
||||||
let newStrategies = []
|
|
||||||
for (let strategy of WIKI.data.authentication) {
|
|
||||||
if (!_.some(dbStrategies, ['key', strategy.key])) {
|
|
||||||
newStrategies.push({
|
|
||||||
key: strategy.key,
|
|
||||||
isEnabled: false,
|
|
||||||
config: _.transform(strategy.props, (result, value, key) => {
|
|
||||||
_.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({
|
for (const strategy of dbStrategies) {
|
||||||
config: _.transform(strategy.props, (result, value, key) => {
|
const strategyDef = _.find(WIKI.data.authentication, ['key', strategy.strategyKey])
|
||||||
|
strategy.config = _.transform(strategyDef.props, (result, value, key) => {
|
||||||
if (!_.has(result, key)) {
|
if (!_.has(result, key)) {
|
||||||
_.set(result, key, value.default)
|
_.set(result, key, value.default)
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}, strategyConfig)
|
}, strategy.config)
|
||||||
|
|
||||||
|
// Fix pre-2.5 strategies displayName
|
||||||
|
if (!strategy.displayName) {
|
||||||
|
await WIKI.models.authentication.query().patch({
|
||||||
|
displayName: strategyDef.title
|
||||||
}).where('key', strategy.key)
|
}).where('key', strategy.key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (newStrategies.length > 0) {
|
|
||||||
trx = await WIKI.models.Objection.transaction.start(WIKI.models.knex)
|
WIKI.logger.info(`Loaded ${WIKI.data.authentication.length} authentication strategies: [ OK ]`)
|
||||||
for (let strategy of newStrategies) {
|
|
||||||
await WIKI.models.authentication.query(trx).insert(strategy)
|
|
||||||
}
|
|
||||||
await trx.commit()
|
|
||||||
WIKI.logger.info(`Loaded ${newStrategies.length} new authentication strategies: [ OK ]`)
|
|
||||||
} else {
|
|
||||||
WIKI.logger.info(`No new authentication strategies found: [ SKIPPED ]`)
|
|
||||||
}
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
WIKI.logger.error(`Failed to scan or load new authentication providers: [ FAILED ]`)
|
WIKI.logger.error(`Failed to scan or load new authentication providers: [ FAILED ]`)
|
||||||
WIKI.logger.error(err)
|
WIKI.logger.error(err)
|
||||||
if (trx) {
|
|
||||||
trx.rollback()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,8 @@ module.exports = class Group extends Model {
|
|||||||
properties: {
|
properties: {
|
||||||
id: {type: 'integer'},
|
id: {type: 'integer'},
|
||||||
name: {type: 'string'},
|
name: {type: 'string'},
|
||||||
|
isSystem: {type: 'boolean'},
|
||||||
|
redirectOnLogin: {type: 'string'},
|
||||||
createdAt: {type: 'string'},
|
createdAt: {type: 'string'},
|
||||||
updatedAt: {type: 'string'}
|
updatedAt: {type: 'string'}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
key: local
|
key: local
|
||||||
title: Local
|
title: Local Database
|
||||||
description: Built-in authentication for Wiki.js
|
description: Built-in authentication for Wiki.js
|
||||||
author: requarks.io
|
author: requarks.io
|
||||||
logo: https://static.requarks.io/logo/wikijs.svg
|
logo: https://static.requarks.io/logo/wikijs.svg
|
||||||
|
@ -5,9 +5,54 @@ author: requarks.io
|
|||||||
logo: https://static.requarks.io/logo/oauth2.svg
|
logo: https://static.requarks.io/logo/oauth2.svg
|
||||||
color: grey darken-4
|
color: grey darken-4
|
||||||
website: https://oauth.net/2/
|
website: https://oauth.net/2/
|
||||||
|
isAvailable: true
|
||||||
useForm: false
|
useForm: false
|
||||||
props:
|
props:
|
||||||
clientId: String
|
clientId:
|
||||||
clientSecret: String
|
type: String
|
||||||
authorizationURL: String
|
title: Client ID
|
||||||
tokenURL: String
|
hint: Application Client ID
|
||||||
|
order: 1
|
||||||
|
clientSecret:
|
||||||
|
type: String
|
||||||
|
title: Client Secret
|
||||||
|
hint: Application Client Secret
|
||||||
|
order: 2
|
||||||
|
authorizationURL:
|
||||||
|
type: String
|
||||||
|
title: Authorization Endpoint URL
|
||||||
|
hint: The full URL to the authorization endpoint, used to get an authorization code.
|
||||||
|
order: 3
|
||||||
|
tokenURL:
|
||||||
|
type: String
|
||||||
|
title: Token Endpoint URL
|
||||||
|
hint: The full URL to the token endpoint, used to get an access token.
|
||||||
|
order: 4
|
||||||
|
mappingUID:
|
||||||
|
title: Unique ID Field Mapping
|
||||||
|
type: String
|
||||||
|
default: 'id'
|
||||||
|
hint: The field storing the user unique identifier, e.g. "id" or "_id".
|
||||||
|
maxWidth: 500
|
||||||
|
order: 20
|
||||||
|
mappingEmail:
|
||||||
|
title: Email Field Mapping
|
||||||
|
type: String
|
||||||
|
default: 'email'
|
||||||
|
hint: The field storing the user email, e.g. "email" or "mail".
|
||||||
|
maxWidth: 500
|
||||||
|
order: 21
|
||||||
|
mappingDisplayName:
|
||||||
|
title: Display Name Field Mapping
|
||||||
|
type: String
|
||||||
|
default: 'name'
|
||||||
|
hint: The field storing the user display name, e.g. "name", "displayName" or "username".
|
||||||
|
maxWidth: 500
|
||||||
|
order: 22
|
||||||
|
mappingPicture:
|
||||||
|
title: Avatar Picture Field Mapping
|
||||||
|
type: String
|
||||||
|
default: 'pictureUrl'
|
||||||
|
hint: The field storing the user avatar picture, e.g. "pictureUrl" or "avatarUrl".
|
||||||
|
maxWidth: 500
|
||||||
|
order: 23
|
||||||
|
@ -253,9 +253,17 @@ module.exports = () => {
|
|||||||
throw new Error('Incorrect groups auto-increment configuration! Should start at 0 and increment by 1. Contact your database administrator.')
|
throw new Error('Incorrect groups auto-increment configuration! Should start at 0 and increment by 1. Contact your database administrator.')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load authentication strategies + enable local
|
// Load local authentication strategy
|
||||||
await WIKI.models.authentication.refreshStrategiesFromDisk()
|
await WIKI.models.authentication.query().insert({
|
||||||
await WIKI.models.authentication.query().patch({ isEnabled: true }).where('key', 'local')
|
key: 'local',
|
||||||
|
config: {},
|
||||||
|
selfRegistration: false,
|
||||||
|
domainWhitelist: {v: []},
|
||||||
|
autoEnrollGroups: {v: []},
|
||||||
|
order: 0,
|
||||||
|
strategyKey: 'local',
|
||||||
|
displayName: 'Local'
|
||||||
|
})
|
||||||
|
|
||||||
// Load editors + enable default
|
// Load editors + enable default
|
||||||
await WIKI.models.editors.refreshEditorsFromDisk()
|
await WIKI.models.editors.refreshEditorsFromDisk()
|
||||||
|
63
yarn.lock
63
yarn.lock
@ -5681,6 +5681,11 @@ caseless@~0.12.0:
|
|||||||
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
|
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
|
||||||
integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=
|
integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=
|
||||||
|
|
||||||
|
cash-dom@8.0.0:
|
||||||
|
version "8.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/cash-dom/-/cash-dom-8.0.0.tgz#2069238ba1fc2caa7ef4b9066343e020afab8e87"
|
||||||
|
integrity sha512-uUv7JX91CQeACoovHur8eGV8Z3rnd3jjvU1t2EQzW0kOd96VzOnjZCDjbC0FP3LgMpWLcweQjK2UZ1rQ6MbljA==
|
||||||
|
|
||||||
chalk@2.3.x:
|
chalk@2.3.x:
|
||||||
version "2.3.2"
|
version "2.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.3.2.tgz#250dc96b07491bfd601e648d66ddf5f60c7a5c65"
|
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.3.2.tgz#250dc96b07491bfd601e648d66ddf5f60c7a5c65"
|
||||||
@ -8602,6 +8607,11 @@ file-type@14.6.2:
|
|||||||
token-types "^2.0.0"
|
token-types "^2.0.0"
|
||||||
typedarray-to-buffer "^3.1.5"
|
typedarray-to-buffer "^3.1.5"
|
||||||
|
|
||||||
|
file@^0.2.2:
|
||||||
|
version "0.2.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/file/-/file-0.2.2.tgz#c3dfd8f8cf3535ae455c2b423c2e52635d76b4d3"
|
||||||
|
integrity sha1-w9/Y+M81Na5FXCtCPC5SY112tNM=
|
||||||
|
|
||||||
filepond-plugin-file-validate-type@1.2.5:
|
filepond-plugin-file-validate-type@1.2.5:
|
||||||
version "1.2.5"
|
version "1.2.5"
|
||||||
resolved "https://registry.yarnpkg.com/filepond-plugin-file-validate-type/-/filepond-plugin-file-validate-type-1.2.5.tgz#b3588088d2b0d5dbdf706d068c0ecdf28140a8e3"
|
resolved "https://registry.yarnpkg.com/filepond-plugin-file-validate-type/-/filepond-plugin-file-validate-type-1.2.5.tgz#b3588088d2b0d5dbdf706d068c0ecdf28140a8e3"
|
||||||
@ -11266,7 +11276,7 @@ loader-utils@1.2.3, loader-utils@^1.0.2, loader-utils@^1.0.4, loader-utils@^1.1.
|
|||||||
emojis-list "^2.0.0"
|
emojis-list "^2.0.0"
|
||||||
json5 "^1.0.1"
|
json5 "^1.0.1"
|
||||||
|
|
||||||
loader-utils@^1.4.0:
|
loader-utils@^1.0.0, loader-utils@^1.4.0:
|
||||||
version "1.4.0"
|
version "1.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613"
|
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613"
|
||||||
integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==
|
integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==
|
||||||
@ -11647,6 +11657,17 @@ markdown-it@^8.4.2:
|
|||||||
mdurl "^1.0.1"
|
mdurl "^1.0.1"
|
||||||
uc.micro "^1.0.5"
|
uc.micro "^1.0.5"
|
||||||
|
|
||||||
|
markdown-it@^10.0.0:
|
||||||
|
version "10.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-10.0.0.tgz#abfc64f141b1722d663402044e43927f1f50a8dc"
|
||||||
|
integrity sha512-YWOP1j7UbDNz+TumYP1kpwnP0aEa711cJjrAQrzd0UXlbJfc5aAq0F/PZHjiioqDC1NKgvIMX+o+9Bk7yuM2dg==
|
||||||
|
dependencies:
|
||||||
|
argparse "^1.0.7"
|
||||||
|
entities "~2.0.0"
|
||||||
|
linkify-it "^2.0.0"
|
||||||
|
mdurl "^1.0.1"
|
||||||
|
uc.micro "^1.0.5"
|
||||||
|
|
||||||
math-expression-evaluator@^1.2.14:
|
math-expression-evaluator@^1.2.14:
|
||||||
version "1.2.17"
|
version "1.2.17"
|
||||||
resolved "https://registry.yarnpkg.com/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz#de819fdbcd84dccd8fae59c6aeb79615b9d266ac"
|
resolved "https://registry.yarnpkg.com/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz#de819fdbcd84dccd8fae59c6aeb79615b9d266ac"
|
||||||
@ -12001,6 +12022,13 @@ mkdirp@0.3.0:
|
|||||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.3.0.tgz#1bbf5ab1ba827af23575143490426455f481fe1e"
|
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.3.0.tgz#1bbf5ab1ba827af23575143490426455f481fe1e"
|
||||||
integrity sha1-G79asbqCevI1dRQ0kEJkVfSB/h4=
|
integrity sha1-G79asbqCevI1dRQ0kEJkVfSB/h4=
|
||||||
|
|
||||||
|
mkdirp@0.5.5, mkdirp@^0.5.3, mkdirp@^0.5.4:
|
||||||
|
version "0.5.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def"
|
||||||
|
integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==
|
||||||
|
dependencies:
|
||||||
|
minimist "^1.2.5"
|
||||||
|
|
||||||
mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.1:
|
mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.1:
|
||||||
version "0.5.1"
|
version "0.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
|
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
|
||||||
@ -12008,18 +12036,24 @@ mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
minimist "0.0.8"
|
minimist "0.0.8"
|
||||||
|
|
||||||
mkdirp@^0.5.3, mkdirp@^0.5.4:
|
|
||||||
version "0.5.5"
|
|
||||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def"
|
|
||||||
integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==
|
|
||||||
dependencies:
|
|
||||||
minimist "^1.2.5"
|
|
||||||
|
|
||||||
mkdirp@^1.0.3, mkdirp@^1.0.4, mkdirp@~1.0.3:
|
mkdirp@^1.0.3, mkdirp@^1.0.4, mkdirp@~1.0.3:
|
||||||
version "1.0.4"
|
version "1.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
|
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
|
||||||
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
|
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
|
||||||
|
|
||||||
|
modernizr@^3.7.1:
|
||||||
|
version "3.11.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/modernizr/-/modernizr-3.11.2.tgz#12410de335b79c4c02a19edfefd1bfca6896429b"
|
||||||
|
integrity sha512-sx262pafYJP1YPrPlMA4a+9WX5cQjvwp39213YNAqv1LZ4rHASFeNIEyLN+a5/F8/1yJR4ic/4wRFVsbK64TUQ==
|
||||||
|
dependencies:
|
||||||
|
doctrine "^3.0.0"
|
||||||
|
file "^0.2.2"
|
||||||
|
lodash "^4.17.15"
|
||||||
|
markdown-it "^10.0.0"
|
||||||
|
mkdirp "0.5.5"
|
||||||
|
requirejs "^2.3.6"
|
||||||
|
yargs "^15.3.1"
|
||||||
|
|
||||||
moment-duration-format@2.3.2:
|
moment-duration-format@2.3.2:
|
||||||
version "2.3.2"
|
version "2.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/moment-duration-format/-/moment-duration-format-2.3.2.tgz#5fa2b19b941b8d277122ff3f87a12895ec0d6212"
|
resolved "https://registry.yarnpkg.com/moment-duration-format/-/moment-duration-format-2.3.2.tgz#5fa2b19b941b8d277122ff3f87a12895ec0d6212"
|
||||||
@ -15530,6 +15564,11 @@ require_optional@^1.0.1:
|
|||||||
resolve-from "^2.0.0"
|
resolve-from "^2.0.0"
|
||||||
semver "^5.1.0"
|
semver "^5.1.0"
|
||||||
|
|
||||||
|
requirejs@^2.3.6:
|
||||||
|
version "2.3.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/requirejs/-/requirejs-2.3.6.tgz#e5093d9601c2829251258c0b9445d4d19fa9e7c9"
|
||||||
|
integrity sha512-ipEzlWQe6RK3jkzikgCupiTbTvm4S0/CAU5GlgptkN5SO6F3u0UD0K18wy6ErDqiCyP4J4YYe1HuAShvsxePLg==
|
||||||
|
|
||||||
resolve-cwd@^2.0.0:
|
resolve-cwd@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a"
|
resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a"
|
||||||
@ -17984,6 +18023,14 @@ webpack-merge@4.2.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
lodash "^4.17.15"
|
lodash "^4.17.15"
|
||||||
|
|
||||||
|
webpack-modernizr-loader@5.0.0:
|
||||||
|
version "5.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/webpack-modernizr-loader/-/webpack-modernizr-loader-5.0.0.tgz#6a7c07d3fac4b6e02964ee3be61819cf9ab811cc"
|
||||||
|
integrity sha512-D+FIZ03QtWNV536+cGp046qbJIcxPYEF0kGqP6YrM8Y1g6PqzXGdx8VYnK7VkfRuBdtqLL5rCerWYk1ncOSjJQ==
|
||||||
|
dependencies:
|
||||||
|
loader-utils "^1.0.0"
|
||||||
|
modernizr "^3.7.1"
|
||||||
|
|
||||||
webpack-sources@^1.1.0, webpack-sources@^1.3.0, webpack-sources@^1.4.0, webpack-sources@^1.4.1, webpack-sources@^1.4.3:
|
webpack-sources@^1.1.0, webpack-sources@^1.3.0, webpack-sources@^1.4.0, webpack-sources@^1.4.1, webpack-sources@^1.4.3:
|
||||||
version "1.4.3"
|
version "1.4.3"
|
||||||
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933"
|
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933"
|
||||||
|
Loading…
Reference in New Issue
Block a user