refactor: deps update + 2FA setup + verify
This commit is contained in:
1 parent
29afc61abf
commit
f72530bf84
58 files changed
+481
-391
No files matched your search
@@ -64,7 +64,7 @@ const graphQLLink = ApolloLink.from([
|
||||
})
|
||||
store.commit('showNotification', {
|
||||
style: 'red',
|
||||
message: isAuthError ? `You are not authorized to access this resource.` : `An unexpected error occured.`,
|
||||
message: isAuthError ? `You are not authorized to access this resource.` : `An unexpected error occurred.`,
|
||||
icon: 'alert'
|
||||
})
|
||||
}
|
||||
|
||||
@@ -309,6 +309,21 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
.admin-providerlogo {
|
||||
width: 250px;
|
||||
height: 50px;
|
||||
float: right;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
margin-left: 16px;
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
max-height: 50px;
|
||||
}
|
||||
}
|
||||
|
||||
.v-application.admin {
|
||||
code {
|
||||
box-shadow: none;
|
||||
|
||||
@@ -46,14 +46,16 @@
|
||||
hide-details
|
||||
inset
|
||||
)
|
||||
v-card-info(color='blue')
|
||||
div
|
||||
div {{provider.description}}
|
||||
span.caption: a(:href='provider.website') {{provider.website}}
|
||||
v-spacer
|
||||
.admin-providerlogo
|
||||
img(:src='provider.logo', :alt='provider.title')
|
||||
v-card-text
|
||||
v-form
|
||||
.analytic-provider-logo
|
||||
img(:src='provider.logo', :alt='provider.title')
|
||||
.body-2.pt-3 {{provider.description}}
|
||||
.body-2.pt-3: a(:href='provider.website') {{provider.website}}
|
||||
v-divider.mt-5
|
||||
.overline.py-5 {{$t('admin:analytics.providerConfiguration')}}
|
||||
.overline.pb-5 {{$t('admin:analytics.providerConfiguration')}}
|
||||
.body-1.ml-3(v-if='!provider.config || provider.config.length < 1'): em {{$t('admin:analytics.providerNoConfiguration')}}
|
||||
template(v-else, v-for='cfg in provider.config')
|
||||
v-select(
|
||||
@@ -177,21 +179,3 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
|
||||
.analytic-provider-logo {
|
||||
width: 250px;
|
||||
height: 85px;
|
||||
float:right;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
max-height: 50px;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -211,7 +211,7 @@ export default {
|
||||
} else {
|
||||
this.$store.commit('showNotification', {
|
||||
style: 'red',
|
||||
message: _.get(resp, 'data.authentication.createApiKey.responseResult.message', 'An unexpected error occured.'),
|
||||
message: _.get(resp, 'data.authentication.createApiKey.responseResult.message', 'An unexpected error occurred.'),
|
||||
icon: 'alert'
|
||||
})
|
||||
}
|
||||
|
||||
@@ -131,7 +131,7 @@ export default {
|
||||
} else {
|
||||
this.$store.commit('showNotification', {
|
||||
style: 'red',
|
||||
message: _.get(resp, 'data.authentication.setApiState.responseResult.message', 'An unexpected error occured.'),
|
||||
message: _.get(resp, 'data.authentication.setApiState.responseResult.message', 'An unexpected error occurred.'),
|
||||
icon: 'alert'
|
||||
})
|
||||
}
|
||||
@@ -182,7 +182,7 @@ export default {
|
||||
} else {
|
||||
this.$store.commit('showNotification', {
|
||||
style: 'red',
|
||||
message: _.get(resp, 'data.authentication.revokeApiKey.responseResult.message', 'An unexpected error occured.'),
|
||||
message: _.get(resp, 'data.authentication.revokeApiKey.responseResult.message', 'An unexpected error occurred.'),
|
||||
icon: 'alert'
|
||||
})
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
span {{strategy.strategy.description}}
|
||||
.caption: a(:href='strategy.strategy.website') {{strategy.strategy.website}}
|
||||
v-spacer
|
||||
.authlogo
|
||||
.admin-providerlogo
|
||||
img(:src='strategy.strategy.logo', :alt='strategy.strategy.title')
|
||||
v-card-text
|
||||
.overline.mb-5 {{$t('admin:auth.strategyConfiguration')}}
|
||||
@@ -423,22 +423,3 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
|
||||
.authlogo {
|
||||
width: 250px;
|
||||
height: 60px;
|
||||
float:right;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
margin-left: 16px;
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
max-height: 50px;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -38,12 +38,14 @@
|
||||
v-card.animated.fadeInUp.wait-p2s
|
||||
v-toolbar(color='primary', dense, flat, dark)
|
||||
.subtitle-1 {{provider.title}}
|
||||
v-card-text
|
||||
.providerlogo
|
||||
v-card-info(color='blue')
|
||||
div
|
||||
div {{provider.description}}
|
||||
span.caption: a(:href='provider.website') {{provider.website}}
|
||||
v-spacer
|
||||
.admin-providerlogo
|
||||
img(:src='provider.logo', :alt='provider.title')
|
||||
.caption.pt-3 {{provider.description}}
|
||||
.caption.pb-3: a(:href='provider.website') {{provider.website}}
|
||||
v-divider.mt-3
|
||||
v-card-text
|
||||
.overline.my-5 {{$t('admin:comments.providerConfig')}}
|
||||
.body-2.ml-3(v-if='!provider.config || provider.config.length < 1'): em {{$t('admin:comments.providerNoConfig')}}
|
||||
template(v-else, v-for='cfg in provider.config')
|
||||
@@ -202,21 +204,3 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
|
||||
.providerlogo {
|
||||
width: 250px;
|
||||
height: 85px;
|
||||
float:right;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
max-height: 50px;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -226,7 +226,7 @@ export default {
|
||||
}
|
||||
})
|
||||
if (!_.get(resp, 'data.mail.sendTest.responseResult.succeeded', false)) {
|
||||
throw new Error(_.get(resp, 'data.mail.sendTest.responseResult.message', 'An unexpected error occured.'))
|
||||
throw new Error(_.get(resp, 'data.mail.sendTest.responseResult.message', 'An unexpected error occurred.'))
|
||||
}
|
||||
|
||||
this.testEmail = ''
|
||||
|
||||
@@ -8,7 +8,9 @@
|
||||
.headline.primary--text.animated.fadeInLeft {{$t('navigation.title')}}
|
||||
.subtitle-1.grey--text.animated.fadeInLeft.wait-p4s {{$t('navigation.subtitle')}}
|
||||
v-spacer
|
||||
v-btn.animated.fadeInDown.wait-p2s.mr-3(icon, outlined, color='grey', @click='refresh')
|
||||
v-btn.animated.fadeInDown.wait-p3s(icon, outlined, color='grey', href='https://docs.requarks.io/navigation', target='_blank')
|
||||
v-icon mdi-help-circle
|
||||
v-btn.mx-3.animated.fadeInDown.wait-p2s.mr-3(icon, outlined, color='grey', @click='refresh')
|
||||
v-icon mdi-refresh
|
||||
v-btn.animated.fadeInDown(color='success', depressed, @click='save', large)
|
||||
v-icon(left) mdi-check
|
||||
@@ -30,15 +32,6 @@
|
||||
v-list-item-avatar
|
||||
v-icon(v-if='$vuetify.theme.dark', :color='config.mode === `TREE` ? `teal lighten-3` : `grey darken-2`') mdi-check-circle
|
||||
v-icon(v-else, :color='config.mode === `TREE` ? `teal` : `grey lighten-3`') mdi-check-circle
|
||||
v-list-item(value='MIXED')
|
||||
v-list-item-avatar
|
||||
img(src='/_assets/svg/icon-user-menu-male-dotted.svg', alt='Custom Navigation')
|
||||
v-list-item-content
|
||||
v-list-item-title {{$t('admin:navigation.modeCustom.title')}}
|
||||
v-list-item-subtitle {{$t('admin:navigation.modeCustom.description')}}
|
||||
v-list-item-avatar
|
||||
v-icon(v-if='$vuetify.theme.dark', :color='config.mode === `MIXED` ? `teal lighten-3` : `grey darken-2`') mdi-check-circle
|
||||
v-icon(v-else, :color='config.mode === `MIXED` ? `teal` : `grey lighten-3`') mdi-check-circle
|
||||
v-list-item(value='STATIC')
|
||||
v-list-item-avatar
|
||||
img(src='/_assets/svg/icon-features-list.svg', alt='Static Navigation')
|
||||
@@ -48,6 +41,15 @@
|
||||
v-list-item-avatar
|
||||
v-icon(v-if='$vuetify.theme.dark', :color='config.mode === `STATIC` ? `teal lighten-3` : `grey darken-2`') mdi-check-circle
|
||||
v-icon(v-else, :color='config.mode === `STATIC` ? `teal` : `grey lighten-3`') mdi-check-circle
|
||||
v-list-item(value='MIXED')
|
||||
v-list-item-avatar
|
||||
img(src='/_assets/svg/icon-user-menu-male-dotted.svg', alt='Custom Navigation')
|
||||
v-list-item-content
|
||||
v-list-item-title {{$t('admin:navigation.modeCustom.title')}}
|
||||
v-list-item-subtitle {{$t('admin:navigation.modeCustom.description')}}
|
||||
v-list-item-avatar
|
||||
v-icon(v-if='$vuetify.theme.dark', :color='config.mode === `MIXED` ? `teal lighten-3` : `grey darken-2`') mdi-check-circle
|
||||
v-icon(v-else, :color='config.mode === `MIXED` ? `teal` : `grey lighten-3`') mdi-check-circle
|
||||
v-list-item(value='NONE')
|
||||
v-list-item-avatar
|
||||
img(src='/_assets/svg/icon-cancel-dotted.svg', alt='None')
|
||||
@@ -421,7 +423,7 @@ export default {
|
||||
icon: 'check'
|
||||
})
|
||||
} else {
|
||||
throw new Error(_.get(resp, 'data.navigation.updateTree.responseResult.message', 'An unexpected error occured.'))
|
||||
throw new Error(_.get(resp, 'data.navigation.updateTree.responseResult.message', 'An unexpected error occurred.'))
|
||||
}
|
||||
} catch (err) {
|
||||
this.$store.commit('pushGraphError', err)
|
||||
|
||||
@@ -85,18 +85,12 @@
|
||||
hide-details
|
||||
inset
|
||||
)
|
||||
v-card-text.py-2.pl-4
|
||||
.body-2.pt-3 {{currentRenderer.description}}
|
||||
.body-2.pt-1.pb-5: a(href='https://docs.requarks.io/en/rendering', target='_blank') Documentation
|
||||
i18next.body-2(path='admin:auth.strategyState', tag='div', v-if='currentRenderer.isEnabled')
|
||||
v-chip(color='green', small, dark, label, place='state') {{$t('admin:auth.strategyStateActive')}}
|
||||
span(v-if='selectedCore === `local`', place='locked') {{$t('admin:auth.strategyStateLocked')}}
|
||||
span(v-else, place='locked', v-text='')
|
||||
i18next.body-2(path='admin:auth.strategyState', tag='div', v-else)
|
||||
v-chip(color='red', small, dark, label, place='state') {{$t('admin:auth.strategyStateInactive')}}
|
||||
v-divider.mt-3
|
||||
v-card-text.pb-4.pt-2.pl-4
|
||||
.overline.my-5 Rendering Module Configuration
|
||||
v-card-info(color='blue')
|
||||
div
|
||||
div {{currentRenderer.description}}
|
||||
span.caption: a(href='https://docs.requarks.io/en/rendering', target='_blank') Documentation
|
||||
v-card-text.pb-4.pl-4
|
||||
.overline.mb-5 Rendering Module Configuration
|
||||
.body-2.ml-3(v-if='!currentRenderer.config || currentRenderer.config.length < 1'): em This rendering module has no configuration options you can modify.
|
||||
template(v-else, v-for='(cfg, idx) in currentRenderer.config')
|
||||
v-select(
|
||||
|
||||
@@ -41,13 +41,15 @@
|
||||
v-card.animated.fadeInUp.wait-p2s
|
||||
v-toolbar(color='primary', dense, flat, dark)
|
||||
.subtitle-1 {{engine.title}}
|
||||
v-card-text
|
||||
.enginelogo
|
||||
v-card-info(color='blue')
|
||||
div
|
||||
div {{engine.description}}
|
||||
span.caption: a(:href='engine.website') {{engine.website}}
|
||||
v-spacer
|
||||
.admin-providerlogo
|
||||
img(:src='engine.logo', :alt='engine.title')
|
||||
.caption.pt-3 {{engine.description}}
|
||||
.caption.pb-3: a(:href='engine.website') {{engine.website}}
|
||||
v-divider.mt-3
|
||||
.overline.my-5 {{$t('admin:search.engineConfig')}}
|
||||
v-card-text
|
||||
.overline.mb-5 {{$t('admin:search.engineConfig')}}
|
||||
.body-2.ml-3(v-if='!engine.config || engine.config.length < 1'): em {{$t('admin:search.engineNoConfig')}}
|
||||
template(v-else, v-for='cfg in engine.config')
|
||||
v-select(
|
||||
|
||||
@@ -92,12 +92,15 @@
|
||||
hide-details
|
||||
inset
|
||||
)
|
||||
v-card-info(color='blue')
|
||||
div
|
||||
div {{target.description}}
|
||||
span.caption: a(:href='target.website') {{target.website}}
|
||||
v-spacer
|
||||
.admin-providerlogo
|
||||
img(:src='target.logo', :alt='target.title')
|
||||
v-card-text
|
||||
v-form
|
||||
.targetlogo
|
||||
img(:src='target.logo', :alt='target.title')
|
||||
.body-2.pt-3 {{target.description}}
|
||||
.body-2.pt-3.pb-5: a(:href='target.website') {{target.website}}
|
||||
i18next.body-2(path='admin:storage.targetState', tag='div', v-if='target.isEnabled')
|
||||
v-chip(color='green', small, dark, label, place='state') {{$t('admin:storage.targetStateActive')}}
|
||||
i18next.body-2(path='admin:storage.targetState', tag='div', v-else)
|
||||
|
||||
@@ -153,7 +153,7 @@ export default {
|
||||
})
|
||||
this.refresh()
|
||||
} else {
|
||||
throw new Error(_.get(resp, 'data.pages.deleteTag.responseResult.message', 'An unexpected error occured.'))
|
||||
throw new Error(_.get(resp, 'data.pages.deleteTag.responseResult.message', 'An unexpected error occurred.'))
|
||||
}
|
||||
} catch (err) {
|
||||
this.$store.commit('pushGraphError', err)
|
||||
@@ -193,7 +193,7 @@ export default {
|
||||
})
|
||||
this.current.updatedAt = new Date()
|
||||
} else {
|
||||
throw new Error(_.get(resp, 'data.pages.updateTag.responseResult.message', 'An unexpected error occured.'))
|
||||
throw new Error(_.get(resp, 'data.pages.updateTag.responseResult.message', 'An unexpected error occurred.'))
|
||||
}
|
||||
} catch (err) {
|
||||
this.$store.commit('pushGraphError', err)
|
||||
|
||||
@@ -212,7 +212,7 @@ export default {
|
||||
} else {
|
||||
this.$store.commit('showNotification', {
|
||||
style: 'red',
|
||||
message: _.get(resp, 'data.users.create.responseResult.message', 'An unexpected error occured.'),
|
||||
message: _.get(resp, 'data.users.create.responseResult.message', 'An unexpected error occurred.'),
|
||||
icon: 'alert'
|
||||
})
|
||||
}
|
||||
|
||||
@@ -174,7 +174,8 @@
|
||||
v-icon mdi-two-factor-authentication
|
||||
v-list-item-content
|
||||
v-list-item-title {{$t('admin:users.tfa')}}
|
||||
v-list-item-subtitle.red--text Inactive
|
||||
v-list-item-subtitle.green--text(v-if='user.tfaIsActive') Active
|
||||
v-list-item-subtitle.red--text(v-else) Inactive
|
||||
v-list-item-action
|
||||
v-tooltip(top)
|
||||
template(v-slot:activator='{ on }')
|
||||
@@ -709,7 +710,7 @@ export default {
|
||||
} else {
|
||||
this.$store.commit('showNotification', {
|
||||
style: 'red',
|
||||
message: _.get(resp, 'data.users.activate.responseResult.message', 'An unexpected error occured.'),
|
||||
message: _.get(resp, 'data.users.activate.responseResult.message', 'An unexpected error occurred.'),
|
||||
icon: 'warning'
|
||||
})
|
||||
}
|
||||
@@ -749,7 +750,7 @@ export default {
|
||||
} else {
|
||||
this.$store.commit('showNotification', {
|
||||
style: 'red',
|
||||
message: _.get(resp, 'data.users.deactivate.responseResult.message', 'An unexpected error occured.'),
|
||||
message: _.get(resp, 'data.users.deactivate.responseResult.message', 'An unexpected error occurred.'),
|
||||
icon: 'warning'
|
||||
})
|
||||
}
|
||||
@@ -798,7 +799,7 @@ export default {
|
||||
} else {
|
||||
this.$store.commit('showNotification', {
|
||||
style: 'red',
|
||||
message: _.get(resp, 'data.users.delete.responseResult.message', 'An unexpected error occured.'),
|
||||
message: _.get(resp, 'data.users.delete.responseResult.message', 'An unexpected error occurred.'),
|
||||
icon: 'warning'
|
||||
})
|
||||
}
|
||||
@@ -864,7 +865,7 @@ export default {
|
||||
} else {
|
||||
this.$store.commit('showNotification', {
|
||||
style: 'red',
|
||||
message: _.get(resp, 'data.users.update.responseResult.message', 'An unexpected error occured.'),
|
||||
message: _.get(resp, 'data.users.update.responseResult.message', 'An unexpected error occurred.'),
|
||||
icon: 'warning'
|
||||
})
|
||||
}
|
||||
@@ -935,7 +936,7 @@ export default {
|
||||
} else {
|
||||
this.$store.commit('showNotification', {
|
||||
style: 'red',
|
||||
message: _.get(resp, 'data.users.verify.responseResult.message', 'An unexpected error occured.'),
|
||||
message: _.get(resp, 'data.users.verify.responseResult.message', 'An unexpected error occurred.'),
|
||||
icon: 'warning'
|
||||
})
|
||||
}
|
||||
@@ -962,6 +963,7 @@ export default {
|
||||
createdAt
|
||||
updatedAt
|
||||
lastLoginAt
|
||||
tfaIsActive
|
||||
groups {
|
||||
id
|
||||
name
|
||||
|
||||
@@ -345,7 +345,7 @@ export default {
|
||||
})
|
||||
const respObj = _.get(resp, 'data.system.importUsersFromV1', {})
|
||||
if (!_.get(respObj, 'responseResult.succeeded', false)) {
|
||||
throw new Error(_.get(respObj, 'responseResult.message', 'An unexpected error occured'))
|
||||
throw new Error(_.get(respObj, 'responseResult.message', 'An unexpected error occurred'))
|
||||
}
|
||||
this.successUsers = _.get(respObj, 'usersCount', 0)
|
||||
this.successGroups = _.get(respObj, 'groupsCount', 0)
|
||||
@@ -429,7 +429,7 @@ export default {
|
||||
})
|
||||
const respObj = _.get(respSv, 'data.storage.updateTargets', {})
|
||||
if (!_.get(respObj, 'responseResult.succeeded', false)) {
|
||||
throw new Error(_.get(respObj, 'responseResult.message', 'An unexpected error occured'))
|
||||
throw new Error(_.get(respObj, 'responseResult.message', 'An unexpected error occurred'))
|
||||
}
|
||||
|
||||
this.progress += 10
|
||||
@@ -480,7 +480,7 @@ export default {
|
||||
|
||||
const respImportObj = _.get(respImport, 'data.storage.executeAction', {})
|
||||
if (!_.get(respImportObj, 'responseResult.succeeded', false)) {
|
||||
throw new Error(_.get(respImportObj, 'responseResult.message', 'An unexpected error occured'))
|
||||
throw new Error(_.get(respImportObj, 'responseResult.message', 'An unexpected error occurred'))
|
||||
}
|
||||
|
||||
this.progress += 15
|
||||
|
||||
@@ -314,7 +314,7 @@ export default {
|
||||
this.$vuetify.goTo(`#comment-post-id-${_.get(resp, 'data.comments.create.id', 0)}`, this.scrollOpts)
|
||||
})
|
||||
} else {
|
||||
throw new Error(_.get(resp, 'data.comments.create.responseResult.message', 'An unexpected error occured.'))
|
||||
throw new Error(_.get(resp, 'data.comments.create.responseResult.message', 'An unexpected error occurred.'))
|
||||
}
|
||||
} catch (err) {
|
||||
this.$store.commit('showNotification', {
|
||||
@@ -420,7 +420,7 @@ export default {
|
||||
|
||||
this.editCommentCancel()
|
||||
} else {
|
||||
throw new Error(_.get(resp, 'data.comments.delete.responseResult.message', 'An unexpected error occured.'))
|
||||
throw new Error(_.get(resp, 'data.comments.delete.responseResult.message', 'An unexpected error occurred.'))
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn(err)
|
||||
@@ -482,7 +482,7 @@ export default {
|
||||
|
||||
this.comments = _.reject(this.comments, ['id', this.commentToDelete.id])
|
||||
} else {
|
||||
throw new Error(_.get(resp, 'data.comments.delete.responseResult.message', 'An unexpected error occured.'))
|
||||
throw new Error(_.get(resp, 'data.comments.delete.responseResult.message', 'An unexpected error occurred.'))
|
||||
}
|
||||
} catch (err) {
|
||||
this.$store.commit('showNotification', {
|
||||
|
||||
@@ -172,6 +172,19 @@
|
||||
span {{$t('common:header.newPage')}}
|
||||
v-divider(vertical)
|
||||
|
||||
//- ADMIN
|
||||
|
||||
template(v-if='isAuthenticated && isAdmin')
|
||||
v-tooltip(bottom, v-if='mode !== `admin`')
|
||||
template(v-slot:activator='{ on }')
|
||||
v-btn(icon, tile, height='64', v-on='on', href='/a', :aria-label='$t(`common:header.admin`)')
|
||||
v-icon(color='grey') mdi-cog
|
||||
span {{$t('common:header.admin')}}
|
||||
v-btn(v-else, text, tile, height='64', href='/', :aria-label='$t(`common:actions.exit`)')
|
||||
v-icon(left, color='grey') mdi-exit-to-app
|
||||
span {{$t('common:actions.exit')}}
|
||||
v-divider(vertical)
|
||||
|
||||
//- ACCOUNT
|
||||
|
||||
v-menu(v-if='isAuthenticated', offset-y, bottom, min-width='300', transition='slide-y-transition', left)
|
||||
@@ -210,9 +223,6 @@
|
||||
v-list-item-action: v-icon(color='blue-grey') mdi-face-profile
|
||||
v-list-item-content
|
||||
v-list-item-title(:class='$vuetify.theme.dark ? `blue-grey--text text--lighten-3` : `blue-grey--text`') {{$t('common:header.profile')}}
|
||||
v-list-item(href='/a', v-if='isAuthenticated && isAdmin')
|
||||
v-list-item-action.btn-animate-rotate: v-icon(:color='$vuetify.theme.dark ? `blue-grey lighten-3` : `blue-grey`') mdi-cog
|
||||
v-list-item-title(:class='$vuetify.theme.dark ? `blue-grey--text text--lighten-3` : `blue-grey--text`') {{$t('common:header.admin')}}
|
||||
v-list-item(@click='logout')
|
||||
v-list-item-action: v-icon(color='red') mdi-logout
|
||||
v-list-item-title.red--text {{$t('common:header.logout')}}
|
||||
|
||||
@@ -418,7 +418,7 @@ export default {
|
||||
window.location.assign(`/${this.locale}/${this.path}`)
|
||||
}, 1000)
|
||||
} else {
|
||||
throw new Error(_.get(resp, 'data.pages.restore.responseResult.message', 'An unexpected error occured'))
|
||||
throw new Error(_.get(resp, 'data.pages.restore.responseResult.message', 'An unexpected error occurred'))
|
||||
}
|
||||
} catch (err) {
|
||||
this.$store.commit('showNotification', {
|
||||
|
||||
+157
-82
@@ -2,17 +2,27 @@
|
||||
v-app
|
||||
.login(:style='`background-image: url(` + bgUrl + `);`')
|
||||
.login-sd
|
||||
.d-flex
|
||||
.d-flex.mb-5
|
||||
.login-logo
|
||||
v-avatar(tile, size='34')
|
||||
v-img(:src='logoUrl')
|
||||
.login-title
|
||||
.text-h6 {{ siteTitle }}
|
||||
v-alert.mb-0(
|
||||
v-model='errorShown'
|
||||
transition='slide-y-reverse-transition'
|
||||
color='red darken-2'
|
||||
tile
|
||||
dark
|
||||
dense
|
||||
icon='mdi-alert'
|
||||
)
|
||||
.body-2 {{errorMessage}}
|
||||
//-------------------------------------------------
|
||||
//- PROVIDERS LIST
|
||||
//-------------------------------------------------
|
||||
template(v-if='screen === `login` && strategies.length > 1')
|
||||
.login-subtitle.mt-5
|
||||
.login-subtitle
|
||||
.text-subtitle-1 Select Authentication Provider
|
||||
.login-list
|
||||
v-list.elevation-1.radius-7(nav)
|
||||
@@ -176,19 +186,51 @@
|
||||
v-model='securityCode'
|
||||
:placeholder='$t("auth:tfa.placeholder")'
|
||||
autocomplete='one-time-code'
|
||||
@keyup.enter='verifySecurityCode'
|
||||
@keyup.enter='verifySecurityCode(false)'
|
||||
)
|
||||
v-btn.mt-2.text-none(
|
||||
width='100%'
|
||||
large
|
||||
color='primary'
|
||||
dark
|
||||
@click='verifySecurityCode'
|
||||
@click='verifySecurityCode(false)'
|
||||
:loading='isLoading'
|
||||
) {{ $t('auth:tfa.verifyToken') }}
|
||||
|
||||
//-------------------------------------------------
|
||||
//- SETUP TFA FORM
|
||||
//-------------------------------------------------
|
||||
v-dialog(v-model='isTFASetupShown', max-width='600', persistent)
|
||||
v-card
|
||||
.login-tfa.text-center.pa-5
|
||||
.subtitle-1.primary--text Your administrator has required Two-Factor Authentication (2FA) to be enabled on your account.
|
||||
v-divider.my-5
|
||||
.subtitle-2 1) Scan the QR code below from your mobile 2FA application:
|
||||
.caption (e.g. #[a(href='https://authy.com/', target='_blank', noopener) Authy], #[a(href='https://support.google.com/accounts/answer/1066447', target='_blank', noopener) Google Authenticator], #[a(href='https://www.microsoft.com/en-us/account/authenticator', target='_blank', noopener) Microsoft Authenticator], etc.)
|
||||
.login-tfa-qr.mt-5(v-if='isTFASetupShown', v-html='tfaQRImage')
|
||||
.subtitle-2.mt-5 2) Enter the security code generated from your trusted device:
|
||||
v-text-field.login-tfa-field.mt-2(
|
||||
solo
|
||||
flat
|
||||
background-color='white'
|
||||
hide-details
|
||||
ref='iptTFASetup'
|
||||
v-model='securityCode'
|
||||
:placeholder='$t("auth:tfa.placeholder")'
|
||||
autocomplete='one-time-code'
|
||||
@keyup.enter='verifySecurityCode(true)'
|
||||
)
|
||||
v-btn.mt-2.text-none(
|
||||
width='100%'
|
||||
large
|
||||
color='primary'
|
||||
dark
|
||||
@click='verifySecurityCode(true)'
|
||||
:loading='isLoading'
|
||||
) {{ $t('auth:tfa.verifyToken') }}
|
||||
|
||||
loader(v-model='isLoading', :color='loaderColor', :title='loaderTitle', :subtitle='$t(`auth:pleaseWait`)')
|
||||
notify
|
||||
notify(style='padding-top: 64px;')
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -231,7 +273,11 @@ export default {
|
||||
isShown: false,
|
||||
newPassword: '',
|
||||
newPasswordVerify: '',
|
||||
isTFAShown: false
|
||||
isTFAShown: false,
|
||||
isTFASetupShown: false,
|
||||
tfaQRImage: '',
|
||||
errorShown: false,
|
||||
errorMessage: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -282,26 +328,21 @@ export default {
|
||||
* LOGIN
|
||||
*/
|
||||
async login () {
|
||||
this.errorShown = false
|
||||
if (this.username.length < 2) {
|
||||
this.$store.commit('showNotification', {
|
||||
style: 'red',
|
||||
message: this.$t('auth:invalidEmailUsername'),
|
||||
icon: 'alert'
|
||||
})
|
||||
this.errorMessage = this.$t('auth:invalidEmailUsername')
|
||||
this.errorShown = true
|
||||
this.$refs.iptEmail.focus()
|
||||
} else if (this.password.length < 2) {
|
||||
this.$store.commit('showNotification', {
|
||||
style: 'red',
|
||||
message: this.$t('auth:invalidPassword'),
|
||||
icon: 'alert'
|
||||
})
|
||||
this.errorMessage = this.$t('auth:invalidPassword')
|
||||
this.errorShown = true
|
||||
this.$refs.iptPassword.focus()
|
||||
} else {
|
||||
this.loaderColor = 'grey darken-4'
|
||||
this.loaderTitle = this.$t('auth:signingIn')
|
||||
this.isLoading = true
|
||||
try {
|
||||
let resp = await this.$apollo.mutate({
|
||||
const resp = await this.$apollo.mutate({
|
||||
mutation: gql`
|
||||
mutation($username: String!, $password: String!, $strategy: String!) {
|
||||
authentication {
|
||||
@@ -315,8 +356,10 @@ export default {
|
||||
jwt
|
||||
mustChangePwd
|
||||
mustProvideTFA
|
||||
mustSetupTFA
|
||||
continuationToken
|
||||
redirect
|
||||
tfaQRImage
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -328,38 +371,9 @@ export default {
|
||||
}
|
||||
})
|
||||
if (_.has(resp, 'data.authentication.login')) {
|
||||
let respObj = _.get(resp, 'data.authentication.login', {})
|
||||
const respObj = _.get(resp, 'data.authentication.login', {})
|
||||
if (respObj.responseResult.succeeded === true) {
|
||||
this.continuationToken = respObj.continuationToken
|
||||
if (respObj.mustChangePwd === true) {
|
||||
this.screen = 'changePwd'
|
||||
this.$nextTick(() => {
|
||||
this.$refs.iptNewPassword.focus()
|
||||
})
|
||||
this.isLoading = false
|
||||
} else if (respObj.mustProvideTFA === true) {
|
||||
this.screen = 'tfa'
|
||||
this.securityCode = ''
|
||||
this.$nextTick(() => {
|
||||
this.$refs.iptTFA.focus()
|
||||
})
|
||||
this.isLoading = false
|
||||
} else {
|
||||
this.loaderColor = 'green darken-1'
|
||||
this.loaderTitle = this.$t('auth:loginSuccess')
|
||||
Cookies.set('jwt', respObj.jwt, { expires: 365 })
|
||||
_.delay(() => {
|
||||
const loginRedirect = Cookies.get('loginRedirect')
|
||||
if (loginRedirect) {
|
||||
Cookies.remove('loginRedirect')
|
||||
window.location.replace(loginRedirect)
|
||||
} else if (respObj.redirect) {
|
||||
window.location.replace(respObj.redirect)
|
||||
} else {
|
||||
window.location.replace('/')
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
this.handleLoginResponse(respObj)
|
||||
} else {
|
||||
throw new Error(respObj.responseResult.message)
|
||||
}
|
||||
@@ -380,58 +394,70 @@ export default {
|
||||
/**
|
||||
* VERIFY TFA CODE
|
||||
*/
|
||||
verifySecurityCode () {
|
||||
async verifySecurityCode (setup = false) {
|
||||
if (this.securityCode.length !== 6) {
|
||||
this.$store.commit('showNotification', {
|
||||
style: 'red',
|
||||
message: 'Enter a valid security code.',
|
||||
icon: 'warning'
|
||||
icon: 'alert'
|
||||
})
|
||||
this.$refs.iptTFA.focus()
|
||||
if (setup) {
|
||||
this.$refs.iptTFASetup.focus()
|
||||
} else {
|
||||
this.$refs.iptTFA.focus()
|
||||
}
|
||||
} else {
|
||||
this.loaderColor = 'grey darken-4'
|
||||
this.loaderTitle = this.$t('auth:signingIn')
|
||||
this.isLoading = true
|
||||
this.$apollo.mutate({
|
||||
mutation: gql`
|
||||
{
|
||||
authentication {
|
||||
activeStrategies {
|
||||
key
|
||||
try {
|
||||
const resp = await this.$apollo.mutate({
|
||||
mutation: gql`
|
||||
mutation(
|
||||
$continuationToken: String!
|
||||
$securityCode: String!
|
||||
$setup: Boolean
|
||||
) {
|
||||
authentication {
|
||||
loginTFA(
|
||||
continuationToken: $continuationToken
|
||||
securityCode: $securityCode
|
||||
setup: $setup
|
||||
) {
|
||||
responseResult {
|
||||
succeeded
|
||||
errorCode
|
||||
slug
|
||||
message
|
||||
}
|
||||
jwt
|
||||
mustChangePwd
|
||||
continuationToken
|
||||
redirect
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
continuationToken: this.continuationToken,
|
||||
securityCode: this.securityCode,
|
||||
setup
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
continuationToken: this.continuationToken,
|
||||
securityCode: this.securityCode
|
||||
}
|
||||
}).then(resp => {
|
||||
})
|
||||
if (_.has(resp, 'data.authentication.loginTFA')) {
|
||||
let respObj = _.get(resp, 'data.authentication.loginTFA', {})
|
||||
if (respObj.responseResult.succeeded === true) {
|
||||
this.$store.commit('showNotification', {
|
||||
message: 'Login successful!',
|
||||
style: 'success',
|
||||
icon: 'check'
|
||||
})
|
||||
_.delay(() => {
|
||||
const loginRedirect = Cookies.get('loginRedirect')
|
||||
if (loginRedirect) {
|
||||
Cookies.remove('loginRedirect')
|
||||
window.location.replace(loginRedirect)
|
||||
} else if (respObj.redirect) {
|
||||
window.location.replace(respObj.redirect)
|
||||
} else {
|
||||
window.location.replace('/')
|
||||
}
|
||||
}, 1000)
|
||||
this.isLoading = false
|
||||
this.handleLoginResponse(respObj)
|
||||
} else {
|
||||
if (!setup) {
|
||||
this.isTFAShown = false
|
||||
}
|
||||
throw new Error(respObj.responseResult.message)
|
||||
}
|
||||
} else {
|
||||
throw new Error(this.$t('auth:genericError'))
|
||||
}
|
||||
}).catch(err => {
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
this.$store.commit('showNotification', {
|
||||
style: 'red',
|
||||
@@ -439,7 +465,7 @@ export default {
|
||||
icon: 'alert'
|
||||
})
|
||||
this.isLoading = false
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
@@ -498,6 +524,46 @@ export default {
|
||||
message: 'Coming soon!',
|
||||
icon: 'ferry'
|
||||
})
|
||||
},
|
||||
handleLoginResponse (respObj) {
|
||||
this.continuationToken = respObj.continuationToken
|
||||
if (respObj.mustChangePwd === true) {
|
||||
this.screen = 'changePwd'
|
||||
this.$nextTick(() => {
|
||||
this.$refs.iptNewPassword.focus()
|
||||
})
|
||||
this.isLoading = false
|
||||
} else if (respObj.mustProvideTFA === true) {
|
||||
this.securityCode = ''
|
||||
this.isTFAShown = true
|
||||
setTimeout(() => {
|
||||
this.$refs.iptTFA.focus()
|
||||
}, 500)
|
||||
this.isLoading = false
|
||||
} else if (respObj.mustSetupTFA === true) {
|
||||
this.securityCode = ''
|
||||
this.isTFASetupShown = true
|
||||
this.tfaQRImage = respObj.tfaQRImage
|
||||
setTimeout(() => {
|
||||
this.$refs.iptTFASetup.focus()
|
||||
}, 500)
|
||||
this.isLoading = false
|
||||
} else {
|
||||
this.loaderColor = 'green darken-1'
|
||||
this.loaderTitle = this.$t('auth:loginSuccess')
|
||||
Cookies.set('jwt', respObj.jwt, { expires: 365 })
|
||||
_.delay(() => {
|
||||
const loginRedirect = Cookies.get('loginRedirect')
|
||||
if (loginRedirect) {
|
||||
Cookies.remove('loginRedirect')
|
||||
window.location.replace(loginRedirect)
|
||||
} else if (respObj.redirect) {
|
||||
window.location.replace(respObj.redirect)
|
||||
} else {
|
||||
window.location.replace('/')
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
}
|
||||
},
|
||||
apollo: {
|
||||
@@ -619,6 +685,15 @@ export default {
|
||||
&-field input {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&-qr {
|
||||
background-color: #FFF;
|
||||
padding: 5px;
|
||||
border-radius: 5px;
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -33,7 +33,7 @@
|
||||
v-text-field.md2.mt-2(
|
||||
solo
|
||||
flat
|
||||
prepend-icon='mdi-textbox-password'
|
||||
prepend-icon='mdi-form-textbox-password'
|
||||
:background-color='$vuetify.theme.dark ? `grey darken-3` : `grey lighten-4`'
|
||||
ref='iptPassword'
|
||||
v-model='password'
|
||||
@@ -49,7 +49,7 @@
|
||||
v-text-field.md2.mt-2(
|
||||
solo
|
||||
flat
|
||||
prepend-icon='mdi-textbox-password'
|
||||
prepend-icon='mdi-form-textbox-password'
|
||||
:background-color='$vuetify.theme.dark ? `grey darken-3` : `grey lighten-4`'
|
||||
hide-details
|
||||
ref='iptVerifyPassword'
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
v-card.elevation-20.radius-7.animated.fadeInUp
|
||||
v-alert(v-if='isDevMode', tile, dark, color='red darken-3', icon='mdi-alert', prominent)
|
||||
.body-2 You are running an unstable, unreleased development version. This code base is #[strong NOT] for production use!
|
||||
.body-2.mt-3 Cloning the master branch directly from GitHub is #[strong NOT] the proper way to install Wiki.js!
|
||||
.body-2.mt-3 Cloning the dev branch directly from GitHub is #[strong NOT] the proper way to install Wiki.js!
|
||||
.body-2 Read the #[a(href='https://docs.requarks.io/install', style='color: #FFF;') documentation] on correctly installing the latest stable version.
|
||||
.text-center
|
||||
img.setup-logo.animated.fadeInUp.wait-p2s(src='/_assets/svg/logo-wikijs-full.svg', alt='Wiki.js Logo')
|
||||
@@ -249,7 +249,7 @@ export default {
|
||||
height: 100vh;
|
||||
z-index: 0;
|
||||
background-color: transparent;
|
||||
background-image: url(/_assets/svg/motif-grid.svg) !important;
|
||||
background-image: url(../static/svg/motif-grid.svg) !important;
|
||||
background-size: 100px;
|
||||
background-repeat: repeat;
|
||||
animation: bg-anim 100s linear infinite;
|
||||
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -1,3 +1,4 @@
|
||||
require('./scss/legacy.scss')
|
||||
require('./scss/fonts/default.scss')
|
||||
|
||||
window.WIKI = null
|
||||
@@ -1,39 +1,39 @@
|
||||
@font-face {
|
||||
font-family: 'Tajawal';
|
||||
src: url('/_assets/fonts/arabic/Tajawal-Bold.woff2') format('woff2'),
|
||||
url('/_assets/fonts/arabic/Tajawal-Bold.woff') format('woff');
|
||||
src: url('../../fonts/arabic/Tajawal-Bold.woff2') format('woff2'),
|
||||
url('../../fonts/arabic/Tajawal-Bold.woff') format('woff');
|
||||
font-weight: bold;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Tajawal';
|
||||
src: url('/_assets/fonts/arabic/Tajawal-Regular.woff2') format('woff2'),
|
||||
url('/_assets/fonts/arabic/Tajawal-Regular.woff') format('woff');
|
||||
src: url('../../fonts/arabic/Tajawal-Regular.woff2') format('woff2'),
|
||||
url('../../fonts/arabic/Tajawal-Regular.woff') format('woff');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Tajawal';
|
||||
src: url('/_assets/fonts/arabic/Tajawal-Medium.woff2') format('woff2'),
|
||||
url('/_assets/fonts/arabic/Tajawal-Medium.woff') format('woff');
|
||||
src: url('../../fonts/arabic/Tajawal-Medium.woff2') format('woff2'),
|
||||
url('../../fonts/arabic/Tajawal-Medium.woff') format('woff');
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'BalooBhaijaan';
|
||||
src: url('/_assets/fonts/arabic/BalooBhaijaan-Regular.woff2') format('woff2'),
|
||||
url('/_assets/fonts/arabic/BalooBhaijaan-Regular.woff') format('woff');
|
||||
src: url('../../fonts/arabic/BalooBhaijaan-Regular.woff2') format('woff2'),
|
||||
url('../../fonts/arabic/BalooBhaijaan-Regular.woff') format('woff');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Roboto Mono';
|
||||
src: url('/_assets/fonts/default/RobotoMono-Regular.woff2') format('woff2'),
|
||||
url('/_assets/fonts/default/RobotoMono-Regular.woff') format('woff');
|
||||
src: url('../../fonts/default/RobotoMono-Regular.woff2') format('woff2'),
|
||||
url('../../fonts/default/RobotoMono-Regular.woff') format('woff');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@@ -1,55 +1,55 @@
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
src: url('/_assets/fonts/default/Roboto-MediumItalic.woff2') format('woff2'),
|
||||
url('/_assets/fonts/default/Roboto-MediumItalic.woff') format('woff');
|
||||
src: url('../../fonts/default/Roboto-MediumItalic.woff2') format('woff2'),
|
||||
url('../../fonts/default/Roboto-MediumItalic.woff') format('woff');
|
||||
font-weight: 500;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
src: url('/_assets/fonts/default/Roboto-Italic.woff2') format('woff2'),
|
||||
url('/_assets/fonts/default/Roboto-Italic.woff') format('woff');
|
||||
src: url('../../fonts/default/Roboto-Italic.woff2') format('woff2'),
|
||||
url('../../fonts/default/Roboto-Italic.woff') format('woff');
|
||||
font-weight: normal;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
src: url('/_assets/fonts/default/Roboto-Bold.woff2') format('woff2'),
|
||||
url('/_assets/fonts/default/Roboto-Bold.woff') format('woff');
|
||||
src: url('../../fonts/default/Roboto-Bold.woff2') format('woff2'),
|
||||
url('../../fonts/default/Roboto-Bold.woff') format('woff');
|
||||
font-weight: bold;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
src: url('/_assets/fonts/default/Roboto-Regular.woff2') format('woff2'),
|
||||
url('/_assets/fonts/default/Roboto-Regular.woff') format('woff');
|
||||
src: url('../../fonts/default/Roboto-Regular.woff2') format('woff2'),
|
||||
url('../../fonts/default/Roboto-Regular.woff') format('woff');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
src: url('/_assets/fonts/default/Roboto-BoldItalic.woff2') format('woff2'),
|
||||
url('/_assets/fonts/default/Roboto-BoldItalic.woff') format('woff');
|
||||
src: url('../../fonts/default/Roboto-BoldItalic.woff2') format('woff2'),
|
||||
url('../../fonts/default/Roboto-BoldItalic.woff') format('woff');
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
src: url('/_assets/fonts/default/Roboto-Medium.woff2') format('woff2'),
|
||||
url('/_assets/fonts/default/Roboto-Medium.woff') format('woff');
|
||||
src: url('../../fonts/default/Roboto-Medium.woff2') format('woff2'),
|
||||
url('../../fonts/default/Roboto-Medium.woff') format('woff');
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Roboto Mono';
|
||||
src: url('/_assets/fonts/default/RobotoMono-Regular.woff2') format('woff2'),
|
||||
url('/_assets/fonts/default/RobotoMono-Regular.woff') format('woff');
|
||||
src: url('../../fonts/default/RobotoMono-Regular.woff2') format('woff2'),
|
||||
url('../../fonts/default/RobotoMono-Regular.woff') format('woff');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
@import "global";
|
||||
|
||||
@import "./base/icons.scss";
|
||||
@import "./fonts/default.scss";
|
||||
@import '~katex/dist/katex.min.css';
|
||||
@import '~@mdi/font/css/materialdesignicons.css';
|
||||
|
||||
|
||||
@@ -162,10 +162,7 @@ module.exports = {
|
||||
]
|
||||
},
|
||||
{
|
||||
test: /\.(woff(2)?|ttf|eot|svg)(\?v=\d+\.\d+\.\d+)?$/,
|
||||
exclude: [
|
||||
path.join(process.cwd(), 'client')
|
||||
],
|
||||
test: /\.(woff2|woff|ttf|eot)(\?v=\d+\.\d+\.\d+)?$/,
|
||||
use: [{
|
||||
loader: 'file-loader',
|
||||
options: {
|
||||
|
||||
@@ -168,10 +168,7 @@ module.exports = {
|
||||
]
|
||||
},
|
||||
{
|
||||
test: /\.(woff(2)?|ttf|eot|svg)(\?v=\d+\.\d+\.\d+)?$/,
|
||||
exclude: [
|
||||
path.join(process.cwd(), 'client')
|
||||
],
|
||||
test: /\.(woff2|woff|ttf|eot)(\?v=\d+\.\d+\.\d+)?$/,
|
||||
use: [{
|
||||
loader: 'file-loader',
|
||||
options: {
|
||||
|
||||
+66
-66
@@ -38,26 +38,26 @@
|
||||
"dependencies": {
|
||||
"@aoberoi/passport-slack": "1.0.5",
|
||||
"@azure/storage-blob": "12.1.2",
|
||||
"@bugsnag/js": "7.2.0",
|
||||
"@bugsnag/js": "7.3.0",
|
||||
"@exlinc/keycloak-passport": "1.0.2",
|
||||
"@root/csr": "0.8.1",
|
||||
"@root/keypairs": "0.9.0",
|
||||
"@root/keypairs": "0.10.1",
|
||||
"@root/pem": "1.0.4",
|
||||
"acme": "3.0.3",
|
||||
"akismet-api": "5.0.0",
|
||||
"algoliasearch": "4.3.0",
|
||||
"algoliasearch": "4.4.0",
|
||||
"apollo-fetch": "0.7.0",
|
||||
"apollo-server": "2.15.1",
|
||||
"apollo-server-express": "2.15.1",
|
||||
"apollo-server": "2.16.1",
|
||||
"apollo-server-express": "2.16.1",
|
||||
"auto-load": "3.0.4",
|
||||
"aws-sdk": "2.713.0",
|
||||
"aws-sdk": "2.738.0",
|
||||
"azure-search-client": "3.1.5",
|
||||
"bcryptjs-then": "1.0.1",
|
||||
"bluebird": "3.7.2",
|
||||
"body-parser": "1.19.0",
|
||||
"chalk": "4.1.0",
|
||||
"cheerio": "1.0.0-rc.3",
|
||||
"chokidar": "3.4.0",
|
||||
"chokidar": "3.4.2",
|
||||
"chromium-pickle-js": "0.2.0",
|
||||
"clean-css": "4.2.3",
|
||||
"command-exists": "1.2.9",
|
||||
@@ -69,7 +69,7 @@
|
||||
"custom-error-instance": "2.1.2",
|
||||
"dependency-graph": "0.9.0",
|
||||
"diff": "4.0.2",
|
||||
"diff2html": "3.1.9",
|
||||
"diff2html": "3.1.12",
|
||||
"dompurify": "2.0.12",
|
||||
"dotize": "0.3.0",
|
||||
"elasticsearch6": "npm:@elastic/elasticsearch@6",
|
||||
@@ -79,7 +79,7 @@
|
||||
"express": "4.17.1",
|
||||
"express-brute": "1.0.1",
|
||||
"express-session": "1.17.1",
|
||||
"file-type": "14.6.2",
|
||||
"file-type": "14.7.1",
|
||||
"filesize": "6.1.0",
|
||||
"fs-extra": "9.0.1",
|
||||
"getos": "3.2.1",
|
||||
@@ -87,22 +87,22 @@
|
||||
"graphql-list-fields": "2.0.2",
|
||||
"graphql-rate-limit-directive": "1.2.1",
|
||||
"graphql-subscriptions": "1.1.0",
|
||||
"graphql-tools": "6.0.12",
|
||||
"graphql-tools": "6.0.18",
|
||||
"he": "1.2.0",
|
||||
"highlight.js": "10.1.1",
|
||||
"i18next": "19.6.0",
|
||||
"highlight.js": "10.1.2",
|
||||
"i18next": "19.7.0",
|
||||
"i18next-express-middleware": "2.0.0",
|
||||
"i18next-node-fs-backend": "2.1.3",
|
||||
"image-size": "0.8.3",
|
||||
"js-base64": "2.6.3",
|
||||
"js-base64": "3.4.5",
|
||||
"js-binary": "1.2.0",
|
||||
"js-yaml": "3.14.0",
|
||||
"jsdom": "16.3.0",
|
||||
"jsdom": "16.4.0",
|
||||
"jsonwebtoken": "8.5.1",
|
||||
"katex": "0.11.1",
|
||||
"katex": "0.12.0",
|
||||
"klaw": "3.0.0",
|
||||
"knex": "0.21.2",
|
||||
"lodash": "4.17.19",
|
||||
"knex": "0.21.5",
|
||||
"lodash": "4.17.20",
|
||||
"luxon": "1.24.1",
|
||||
"markdown-it": "11.0.0",
|
||||
"markdown-it-abbr": "1.0.4",
|
||||
@@ -122,16 +122,16 @@
|
||||
"mime-types": "2.1.27",
|
||||
"moment": "2.27.0",
|
||||
"moment-timezone": "0.5.31",
|
||||
"mongodb": "3.5.9",
|
||||
"mongodb": "3.6.0",
|
||||
"ms": "2.1.2",
|
||||
"mssql": "6.2.0",
|
||||
"mssql": "6.2.1",
|
||||
"multer": "1.4.2",
|
||||
"mysql2": "2.1.0",
|
||||
"nanoid": "3.1.10",
|
||||
"nanoid": "3.1.12",
|
||||
"node-2fa": "1.1.2",
|
||||
"node-cache": "5.1.2",
|
||||
"nodemailer": "6.4.10",
|
||||
"objection": "2.2.1",
|
||||
"nodemailer": "6.4.11",
|
||||
"objection": "2.2.3",
|
||||
"passport": "0.4.1",
|
||||
"passport-auth0": "1.3.3",
|
||||
"passport-azure-ad": "4.2.1",
|
||||
@@ -149,13 +149,13 @@
|
||||
"passport-oauth2": "1.5.0",
|
||||
"passport-okta-oauth": "0.0.1",
|
||||
"passport-openidconnect": "0.0.2",
|
||||
"passport-saml": "1.3.3",
|
||||
"passport-saml": "1.3.4",
|
||||
"passport-twitch-oauth": "1.0.0",
|
||||
"pem-jwk": "2.0.0",
|
||||
"pg": "8.3.0",
|
||||
"pg": "8.3.2",
|
||||
"pg-hstore": "2.3.3",
|
||||
"pg-pubsub": "0.5.0",
|
||||
"pg-query-stream": "3.2.0",
|
||||
"pg-query-stream": "3.2.2",
|
||||
"pg-tsquery": "8.1.0",
|
||||
"pug": "3.0.0",
|
||||
"punycode": "2.1.1",
|
||||
@@ -163,33 +163,33 @@
|
||||
"raven": "2.6.4",
|
||||
"remove-markdown": "0.3.0",
|
||||
"request": "2.88.2",
|
||||
"request-promise": "4.2.5",
|
||||
"request-promise": "4.2.6",
|
||||
"safe-regex": "2.1.1",
|
||||
"sanitize-filename": "1.6.3",
|
||||
"scim-query-filter-parser": "2.0.4",
|
||||
"semver": "7.3.2",
|
||||
"serve-favicon": "2.5.0",
|
||||
"simple-git": "2.12.0",
|
||||
"simple-git": "2.19.0",
|
||||
"solr-node": "1.2.1",
|
||||
"sqlite3": "5.0.0",
|
||||
"ssh2": "0.8.9",
|
||||
"ssh2-promise": "0.1.7",
|
||||
"striptags": "3.1.1",
|
||||
"subscriptions-transport-ws": "0.9.17",
|
||||
"subscriptions-transport-ws": "0.9.18",
|
||||
"tar-fs": "2.1.0",
|
||||
"twemoji": "13.0.0",
|
||||
"twemoji": "13.0.1",
|
||||
"uslug": "1.0.4",
|
||||
"uuid": "8.2.0",
|
||||
"uuid": "8.3.0",
|
||||
"validate.js": "0.13.1",
|
||||
"winston": "3.3.3",
|
||||
"xss": "1.0.7",
|
||||
"xss": "1.0.8",
|
||||
"yargs": "15.4.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.10.4",
|
||||
"@babel/core": "^7.10.4",
|
||||
"@babel/cli": "^7.10.5",
|
||||
"@babel/core": "^7.11.4",
|
||||
"@babel/plugin-proposal-class-properties": "^7.10.4",
|
||||
"@babel/plugin-proposal-decorators": "^7.10.4",
|
||||
"@babel/plugin-proposal-decorators": "^7.10.5",
|
||||
"@babel/plugin-proposal-export-namespace-from": "^7.10.4",
|
||||
"@babel/plugin-proposal-function-sent": "^7.10.4",
|
||||
"@babel/plugin-proposal-json-strings": "^7.10.4",
|
||||
@@ -198,11 +198,11 @@
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
||||
"@babel/plugin-syntax-import-meta": "^7.10.4",
|
||||
"@babel/polyfill": "^7.10.4",
|
||||
"@babel/preset-env": "^7.10.4",
|
||||
"@mdi/font": "5.3.45",
|
||||
"@babel/preset-env": "^7.11.0",
|
||||
"@mdi/font": "5.5.55",
|
||||
"@panter/vue-i18next": "0.15.2",
|
||||
"@requarks/ckeditor5": "19.0.1-wiki.2",
|
||||
"@vue/babel-preset-app": "4.4.6",
|
||||
"@vue/babel-preset-app": "4.5.4",
|
||||
"animate-sass": "0.8.2",
|
||||
"animated-number-vue": "1.0.0",
|
||||
"apollo-cache-inmemory": "1.6.6",
|
||||
@@ -214,11 +214,11 @@
|
||||
"apollo-link-persisted-queries": "0.2.2",
|
||||
"apollo-link-ws": "1.0.20",
|
||||
"apollo-utilities": "1.3.4",
|
||||
"autoprefixer": "9.8.5",
|
||||
"autoprefixer": "9.8.6",
|
||||
"babel-eslint": "10.1.0",
|
||||
"babel-jest": "26.1.0",
|
||||
"babel-jest": "26.3.0",
|
||||
"babel-loader": "^8.1.0",
|
||||
"babel-plugin-graphql-tag": "2.5.0",
|
||||
"babel-plugin-graphql-tag": "3.0.0",
|
||||
"babel-plugin-lodash": "3.3.4",
|
||||
"babel-plugin-prismjs": "2.0.1",
|
||||
"babel-plugin-transform-imports": "2.0.0",
|
||||
@@ -228,16 +228,16 @@
|
||||
"chart.js": "2.9.3",
|
||||
"clean-webpack-plugin": "3.0.0",
|
||||
"clipboard": "2.0.6",
|
||||
"codemirror": "5.55.0",
|
||||
"codemirror": "5.57.0",
|
||||
"copy-webpack-plugin": "6.0.3",
|
||||
"core-js": "3.6.5",
|
||||
"css-loader": "3.6.0",
|
||||
"css-loader": "4.2.1",
|
||||
"cssnano": "4.1.10",
|
||||
"cypress": "4.10.0",
|
||||
"cypress": "5.0.0",
|
||||
"d3": "5.16.0",
|
||||
"duplicate-package-checker-webpack-plugin": "3.0.0",
|
||||
"epic-spinners": "1.1.0",
|
||||
"eslint": "7.4.0",
|
||||
"eslint": "7.7.0",
|
||||
"eslint-config-requarks": "1.0.7",
|
||||
"eslint-config-standard": "14.1.1",
|
||||
"eslint-plugin-cypress": "2.11.1",
|
||||
@@ -248,11 +248,11 @@
|
||||
"eslint-plugin-vue": "6.2.2",
|
||||
"fibers": "5.0.0",
|
||||
"file-loader": "6.0.0",
|
||||
"filepond": "4.18.0",
|
||||
"filepond": "4.19.2",
|
||||
"filepond-plugin-file-validate-type": "1.2.5",
|
||||
"filesize.js": "2.0.0",
|
||||
"graphql-persisted-document-loader": "2.0.0",
|
||||
"graphql-tag": "^2.10.4",
|
||||
"graphql-tag": "^2.11.0",
|
||||
"hammerjs": "2.0.8",
|
||||
"html-webpack-plugin": "4.3.0",
|
||||
"html-webpack-pug-plugin": "2.0.0",
|
||||
@@ -260,11 +260,11 @@
|
||||
"i18next-localstorage-backend": "3.1.1",
|
||||
"i18next-xhr-backend": "3.2.2",
|
||||
"ignore-loader": "0.1.2",
|
||||
"jest": "26.1.0",
|
||||
"js-beautify": "1.11.0",
|
||||
"jest": "26.4.2",
|
||||
"js-beautify": "1.13.0",
|
||||
"js-cookie": "2.2.1",
|
||||
"mermaid": "8.5.2",
|
||||
"mini-css-extract-plugin": "0.9.0",
|
||||
"mermaid": "8.7.0",
|
||||
"mini-css-extract-plugin": "0.10.0",
|
||||
"moment-duration-format": "2.3.2",
|
||||
"moment-timezone-data-webpack-plugin": "1.3.0",
|
||||
"offline-plugin": "5.0.7",
|
||||
@@ -277,52 +277,52 @@
|
||||
"postcss-loader": "3.0.0",
|
||||
"postcss-preset-env": "6.7.0",
|
||||
"postcss-selector-parser": "6.0.2",
|
||||
"prismjs": "1.20.0",
|
||||
"prismjs": "1.21.0",
|
||||
"pug-lint": "2.6.0",
|
||||
"pug-loader": "2.4.0",
|
||||
"pug-plain-loader": "1.0.0",
|
||||
"raw-loader": "4.0.1",
|
||||
"resolve-url-loader": "3.1.1",
|
||||
"sass": "1.26.10",
|
||||
"sass-loader": "9.0.2",
|
||||
"sass-resources-loader": "2.0.3",
|
||||
"sass-loader": "9.0.3",
|
||||
"sass-resources-loader": "2.1.0",
|
||||
"script-ext-html-webpack-plugin": "2.1.4",
|
||||
"simple-progress-webpack-plugin": "1.1.2",
|
||||
"style-loader": "1.2.1",
|
||||
"terser": "4.8.0",
|
||||
"terser": "5.2.1",
|
||||
"twemoji-awesome": "1.0.6",
|
||||
"url-loader": "4.1.0",
|
||||
"velocity-animate": "1.5.2",
|
||||
"viz.js": "2.1.2",
|
||||
"vue": "2.6.11",
|
||||
"vue-apollo": "3.0.3",
|
||||
"vue-chartjs": "3.5.0",
|
||||
"vue": "2.6.12",
|
||||
"vue-apollo": "3.0.4",
|
||||
"vue-chartjs": "3.5.1",
|
||||
"vue-clipboards": "1.3.0",
|
||||
"vue-filepond": "6.0.2",
|
||||
"vue-hot-reload-api": "2.3.4",
|
||||
"vue-loader": "15.9.3",
|
||||
"vue-moment": "4.1.0",
|
||||
"vue-router": "3.3.4",
|
||||
"vue-router": "3.4.3",
|
||||
"vue-status-indicator": "1.2.1",
|
||||
"vue-template-compiler": "2.6.11",
|
||||
"vue-template-compiler": "2.6.12",
|
||||
"vue2-animate": "2.1.3",
|
||||
"vuedraggable": "2.24.0",
|
||||
"vuescroll": "4.16.0",
|
||||
"vuetify": "2.3.4",
|
||||
"vuedraggable": "2.24.1",
|
||||
"vuescroll": "4.16.1",
|
||||
"vuetify": "2.3.9",
|
||||
"vuetify-loader": "1.6.0",
|
||||
"vuex": "3.5.1",
|
||||
"vuex-pathify": "1.4.1",
|
||||
"vuex-persistedstate": "3.0.1",
|
||||
"webpack": "4.43.0",
|
||||
"vuex-persistedstate": "3.1.0",
|
||||
"webpack": "4.44.1",
|
||||
"webpack-bundle-analyzer": "3.8.0",
|
||||
"webpack-cli": "3.3.12",
|
||||
"webpack-dev-middleware": "3.7.2",
|
||||
"webpack-hot-middleware": "2.25.0",
|
||||
"webpack-merge": "5.0.9",
|
||||
"webpack-merge": "5.1.2",
|
||||
"webpack-modernizr-loader": "5.0.0",
|
||||
"webpack-subresource-integrity": "1.4.1",
|
||||
"webpackbar": "4.0.0",
|
||||
"whatwg-fetch": "3.2.0",
|
||||
"whatwg-fetch": "3.4.0",
|
||||
"write-file-webpack-plugin": "4.5.1",
|
||||
"xterm": "4.8.1",
|
||||
"zxcvbn": "4.4.2"
|
||||
|
||||
@@ -44,6 +44,7 @@ type AuthenticationMutation {
|
||||
loginTFA(
|
||||
continuationToken: String!
|
||||
securityCode: String!
|
||||
setup: Boolean
|
||||
): AuthenticationLoginResponse @rateLimit(limit: 5, duration: 60)
|
||||
|
||||
loginChangePassword(
|
||||
@@ -108,8 +109,10 @@ type AuthenticationLoginResponse {
|
||||
jwt: String
|
||||
mustChangePwd: Boolean
|
||||
mustProvideTFA: Boolean
|
||||
mustSetupTFA: Boolean
|
||||
continuationToken: String
|
||||
redirect: String
|
||||
tfaQRImage: String
|
||||
}
|
||||
|
||||
type AuthenticationRegisterResponse {
|
||||
|
||||
@@ -140,6 +140,7 @@ type User {
|
||||
createdAt: Date!
|
||||
updatedAt: Date!
|
||||
lastLoginAt: Date
|
||||
tfaIsActive: Boolean!
|
||||
groups: [Group]!
|
||||
}
|
||||
|
||||
|
||||
@@ -185,7 +185,7 @@ module.exports = class Asset extends Model {
|
||||
static async getAssetFromStorage(assetPath, res) {
|
||||
const localLocations = await WIKI.models.storage.getLocalLocations({
|
||||
asset: {
|
||||
path: assetPath,
|
||||
path: assetPath
|
||||
}
|
||||
})
|
||||
for (let location of _.filter(localLocations, location => Boolean(location.path))) {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/* global WIKI */
|
||||
|
||||
const Model = require('objection').Model
|
||||
const moment = require('moment')
|
||||
const nanoid = require('nanoid').nanoid
|
||||
const { DateTime } = require('luxon')
|
||||
const { nanoid } = require('nanoid')
|
||||
|
||||
/**
|
||||
* Users model
|
||||
@@ -41,25 +41,27 @@ module.exports = class UserKey extends Model {
|
||||
async $beforeInsert(context) {
|
||||
await super.$beforeInsert(context)
|
||||
|
||||
this.createdAt = moment.utc().toISOString()
|
||||
this.createdAt = DateTime.utc().toISO()
|
||||
}
|
||||
|
||||
static async generateToken ({ userId, kind }, context) {
|
||||
const token = nanoid()
|
||||
const token = await nanoid()
|
||||
await WIKI.models.userKeys.query().insert({
|
||||
kind,
|
||||
token,
|
||||
validUntil: moment.utc().add(1, 'days').toISOString(),
|
||||
validUntil: DateTime.utc().plus({ days: 1 }).toISO(),
|
||||
userId
|
||||
})
|
||||
return token
|
||||
}
|
||||
|
||||
static async validateToken ({ kind, token }, context) {
|
||||
static async validateToken ({ kind, token, skipDelete }, context) {
|
||||
const res = await WIKI.models.userKeys.query().findOne({ kind, token }).withGraphJoined('user')
|
||||
if (res) {
|
||||
await WIKI.models.userKeys.query().deleteById(res.id)
|
||||
if (moment.utc().isAfter(moment.utc(res.validUntil))) {
|
||||
if (skipDelete !== true) {
|
||||
await WIKI.models.userKeys.query().deleteById(res.id)
|
||||
}
|
||||
if (DateTime.utc() > DateTime.fromISO(res.validUntil)) {
|
||||
throw new WIKI.Error.AuthValidationTokenInvalid()
|
||||
}
|
||||
return res.user
|
||||
@@ -67,4 +69,8 @@ module.exports = class UserKey extends Model {
|
||||
throw new WIKI.Error.AuthValidationTokenInvalid()
|
||||
}
|
||||
}
|
||||
|
||||
static async destroyToken ({ token }) {
|
||||
return WIKI.models.userKeys.query().findOne({ token }).delete()
|
||||
}
|
||||
}
|
||||
+106
-72
@@ -6,6 +6,7 @@ const tfa = require('node-2fa')
|
||||
const jwt = require('jsonwebtoken')
|
||||
const Model = require('objection').Model
|
||||
const validate = require('validate.js')
|
||||
const qr = require('qr-image')
|
||||
|
||||
const bcryptRegexp = /^\$2[ayb]\$[0-9]{2}\$[A-Za-z0-9./]{53}$/
|
||||
|
||||
@@ -118,14 +119,22 @@ module.exports = class User extends Model {
|
||||
}
|
||||
}
|
||||
|
||||
async enableTFA() {
|
||||
async generateTFA() {
|
||||
let tfaInfo = tfa.generateSecret({
|
||||
name: WIKI.config.site.title
|
||||
name: WIKI.config.title,
|
||||
account: this.email
|
||||
})
|
||||
return this.$query.patch({
|
||||
tfaIsActive: true,
|
||||
await WIKI.models.users.query().findById(this.id).patch({
|
||||
tfaIsActive: false,
|
||||
tfaSecret: tfaInfo.secret
|
||||
})
|
||||
return qr.imageSync(`otpauth://totp/${WIKI.config.title}:${this.email}?secret=${tfaInfo.secret}`, { type: 'svg' })
|
||||
}
|
||||
|
||||
async enableTFA() {
|
||||
return WIKI.models.users.query().findById(this.id).patch({
|
||||
tfaIsActive: true
|
||||
})
|
||||
}
|
||||
|
||||
async disableTFA() {
|
||||
@@ -135,7 +144,7 @@ module.exports = class User extends Model {
|
||||
})
|
||||
}
|
||||
|
||||
async verifyTFA(code) {
|
||||
verifyTFA(code) {
|
||||
let result = tfa.verifyToken(this.tfaSecret, code)
|
||||
return (result && _.has(result, 'delta') && result.delta === 0)
|
||||
}
|
||||
@@ -281,55 +290,12 @@ module.exports = class User extends Model {
|
||||
if (err) { return reject(err) }
|
||||
if (!user) { return reject(new WIKI.Error.AuthLoginFailed()) }
|
||||
|
||||
// Get redirect target
|
||||
user.groups = await user.$relatedQuery('groups').select('groups.id', 'permissions', 'redirectOnLogin')
|
||||
let redirect = '/'
|
||||
if (user.groups && user.groups.length > 0) {
|
||||
redirect = user.groups[0].redirectOnLogin
|
||||
try {
|
||||
const resp = await WIKI.models.users.afterLoginChecks(user, context)
|
||||
resolve(resp)
|
||||
} catch (err) {
|
||||
reject(err)
|
||||
}
|
||||
|
||||
// Must Change Password?
|
||||
if (user.mustChangePwd) {
|
||||
try {
|
||||
const pwdChangeToken = await WIKI.models.userKeys.generateToken({
|
||||
kind: 'changePwd',
|
||||
userId: user.id
|
||||
})
|
||||
|
||||
return resolve({
|
||||
mustChangePwd: true,
|
||||
continuationToken: pwdChangeToken,
|
||||
redirect
|
||||
})
|
||||
} catch (errc) {
|
||||
WIKI.logger.warn(errc)
|
||||
return reject(new WIKI.Error.AuthGenericError())
|
||||
}
|
||||
}
|
||||
|
||||
// Is 2FA required?
|
||||
if (user.tfaIsActive) {
|
||||
try {
|
||||
const tfaToken = await WIKI.models.userKeys.generateToken({
|
||||
kind: 'tfa',
|
||||
userId: user.id
|
||||
})
|
||||
return resolve({
|
||||
tfaRequired: true,
|
||||
continuationToken: tfaToken,
|
||||
redirect
|
||||
})
|
||||
} catch (errc) {
|
||||
WIKI.logger.warn(errc)
|
||||
return reject(new WIKI.Error.AuthGenericError())
|
||||
}
|
||||
}
|
||||
|
||||
context.req.logIn(user, { session: !strInfo.useForm }, async errc => {
|
||||
if (errc) { return reject(errc) }
|
||||
const jwtToken = await WIKI.models.users.refreshToken(user)
|
||||
resolve({ jwt: jwtToken.token, redirect })
|
||||
})
|
||||
})(context.req, context.res, () => {})
|
||||
})
|
||||
} else {
|
||||
@@ -337,6 +303,79 @@ module.exports = class User extends Model {
|
||||
}
|
||||
}
|
||||
|
||||
static async afterLoginChecks (user, context, { skipTFA, skipChangePwd } = { skipTFA: false, skipChangePwd: false }) {
|
||||
// Get redirect target
|
||||
user.groups = await user.$relatedQuery('groups').select('groups.id', 'permissions', 'redirectOnLogin')
|
||||
let redirect = '/'
|
||||
if (user.groups && user.groups.length > 0) {
|
||||
redirect = user.groups[0].redirectOnLogin
|
||||
}
|
||||
|
||||
// Is 2FA required?
|
||||
if (!skipTFA) {
|
||||
if (user.tfaIsActive && user.tfaSecret) {
|
||||
try {
|
||||
const tfaToken = await WIKI.models.userKeys.generateToken({
|
||||
kind: 'tfa',
|
||||
userId: user.id
|
||||
})
|
||||
return {
|
||||
mustProvideTFA: true,
|
||||
continuationToken: tfaToken,
|
||||
redirect
|
||||
}
|
||||
} catch (errc) {
|
||||
WIKI.logger.warn(errc)
|
||||
throw new WIKI.Error.AuthGenericError()
|
||||
}
|
||||
} else if (WIKI.config.auth.enforce2FA || (user.tfaIsActive && !user.tfaSecret)) {
|
||||
try {
|
||||
const tfaQRImage = await user.generateTFA()
|
||||
const tfaToken = await WIKI.models.userKeys.generateToken({
|
||||
kind: 'tfaSetup',
|
||||
userId: user.id
|
||||
})
|
||||
return {
|
||||
mustSetupTFA: true,
|
||||
continuationToken: tfaToken,
|
||||
tfaQRImage,
|
||||
redirect
|
||||
}
|
||||
} catch (errc) {
|
||||
WIKI.logger.warn(errc)
|
||||
throw new WIKI.Error.AuthGenericError()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Must Change Password?
|
||||
if (!skipChangePwd && user.mustChangePwd) {
|
||||
try {
|
||||
const pwdChangeToken = await WIKI.models.userKeys.generateToken({
|
||||
kind: 'changePwd',
|
||||
userId: user.id
|
||||
})
|
||||
|
||||
return {
|
||||
mustChangePwd: true,
|
||||
continuationToken: pwdChangeToken,
|
||||
redirect
|
||||
}
|
||||
} catch (errc) {
|
||||
WIKI.logger.warn(errc)
|
||||
throw new WIKI.Error.AuthGenericError()
|
||||
}
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
context.req.login(user, { session: false }, async errc => {
|
||||
if (errc) { return reject(errc) }
|
||||
const jwtToken = await WIKI.models.users.refreshToken(user)
|
||||
resolve({ jwt: jwtToken.token, redirect })
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
static async refreshToken(user) {
|
||||
if (_.isSafeInteger(user)) {
|
||||
user = await WIKI.models.users.query().findById(user).withGraphFetched('groups').modifyGraph('groups', builder => {
|
||||
@@ -384,26 +423,21 @@ module.exports = class User extends Model {
|
||||
}
|
||||
}
|
||||
|
||||
static async loginTFA (opts, context) {
|
||||
if (opts.securityCode.length === 6 && opts.loginToken.length === 64) {
|
||||
let result = await WIKI.redis.get(`tfa:${opts.loginToken}`)
|
||||
if (result) {
|
||||
let userId = _.toSafeInteger(result)
|
||||
if (userId && userId > 0) {
|
||||
let user = await WIKI.models.users.query().findById(userId)
|
||||
if (user && user.verifyTFA(opts.securityCode)) {
|
||||
return Promise.fromCallback(clb => {
|
||||
context.req.logIn(user, clb)
|
||||
}).return({
|
||||
succeeded: true,
|
||||
message: 'Login Successful'
|
||||
}).catch(err => {
|
||||
WIKI.logger.warn(err)
|
||||
throw new WIKI.Error.AuthGenericError()
|
||||
})
|
||||
} else {
|
||||
throw new WIKI.Error.AuthTFAFailed()
|
||||
static async loginTFA ({ securityCode, continuationToken, setup }, context) {
|
||||
if (securityCode.length === 6 && continuationToken.length > 1) {
|
||||
const user = await WIKI.models.userKeys.validateToken({
|
||||
kind: setup ? 'tfaSetup' : 'tfa',
|
||||
token: continuationToken,
|
||||
skipDelete: setup
|
||||
})
|
||||
if (user) {
|
||||
if (user.verifyTFA(securityCode)) {
|
||||
if (setup) {
|
||||
await user.enableTFA()
|
||||
}
|
||||
return WIKI.models.users.afterLoginChecks(user, context, { skipTFA: true })
|
||||
} else {
|
||||
throw new WIKI.Error.AuthTFAFailed()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -442,7 +442,7 @@ module.exports = () => {
|
||||
WIKI.logger.info('HTTP Server: [ RUNNING ]')
|
||||
WIKI.logger.info('🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻')
|
||||
WIKI.logger.info('')
|
||||
WIKI.logger.info(`Browse to http://localhost:${WIKI.config.port}/ to complete setup!`)
|
||||
WIKI.logger.info(`Browse to http://YOUR-SERVER-IP:${WIKI.config.port}/ to complete setup!`)
|
||||
WIKI.logger.info('')
|
||||
WIKI.logger.info('🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺')
|
||||
})
|
||||
|
||||
Reference in new issue
Block a user