feat: mandatory password change on login + UI fixes

This commit is contained in:
Nick
2019-08-24 22:19:35 -04:00
parent 38008f0460
commit d3e693ab46
40 changed files with 1468 additions and 1064 deletions

View File

@@ -11,17 +11,18 @@
offset-xl4, xl4
)
transition(name='fadeUp')
v-card.elevation-5.md2(v-show='isShown')
v-card.elevation-5(v-show='isShown')
v-toolbar(color='primary', flat, dense, dark)
v-spacer
.subheading(v-if='screen === "tfa"') {{ $t('auth:tfa.subtitle') }}
.subheading(v-if='screen === "changePwd"') {{ $t('auth:changePwd.subtitle') }}
.subheading(v-else-if='selectedStrategy.key !== "local"') {{ $t('auth:loginUsingStrategy', { strategy: selectedStrategy.title, interpolation: { escapeValue: false } }) }}
.subheading(v-else) {{ $t('auth:loginRequired') }}
v-spacer
v-card-text.text-center
h1.display-1.primary--text.py-2 {{ siteTitle }}
template(v-if='screen === "login"')
v-text-field.md2.mt-3(
v-text-field.mt-3(
solo
flat
prepend-icon='mdi-clipboard-account'
@@ -31,7 +32,7 @@
v-model='username'
:placeholder='$t("auth:fields.emailUser")'
)
v-text-field.md2.mt-2(
v-text-field.mt-2(
solo
flat
prepend-icon='mdi-textbox-password'
@@ -47,7 +48,7 @@
)
template(v-else-if='screen === "tfa"')
.body-2 Enter the security code generated from your trusted device:
v-text-field.md2.centered.mt-2(
v-text-field.centered.mt-2(
solo
flat
background-color='grey lighten-4'
@@ -57,12 +58,34 @@
:placeholder='$t("auth:tfa.placeholder")'
@keyup.enter='verifySecurityCode'
)
template(v-else-if='screen === "forgot"')
.body-2 {{ $t('auth:forgotPasswordSubtitle') }}
v-text-field.md2.mt-3(
template(v-else-if='screen === "changePwd"')
.body-2 {{$t('auth:changePwd.instructions')}}
v-text-field.mt-2(
type='password'
solo
flat
prepend-icon='email'
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'
@@ -71,31 +94,48 @@
)
v-card-actions.pb-4
v-spacer
v-btn.md2(
v-btn(
width='100%'
max-width='250px'
v-if='screen === "login"'
block
large
color='primary'
color='teal'
dark
@click='login'
round
rounded
:loading='isLoading'
) {{ $t('auth:actions.login') }}
v-btn.md2(
v-btn(
width='100%'
max-width='250px'
v-else-if='screen === "tfa"'
block
large
color='primary'
color='teal'
dark
@click='verifySecurityCode'
round
rounded
:loading='isLoading'
) {{ $t('auth:tfa.verifyToken') }}
v-btn.md2(
v-else-if='screen === "forgot"'
block
v-btn(
width='100%'
max-width='250px'
v-else-if='screen === "changePwd"'
large
color='primary'
color='teal'
dark
@click='changePassword'
rounded
:loading='isLoading'
) {{ $t('auth:changePwd.proceed') }}
v-btn(
width='100%'
max-width='250px'
v-else-if='screen === "forgot"'
large
color='teal'
dark
@click='forgotPasswordSubmit'
round
rounded
:loading='isLoading'
) {{ $t('auth:sendResetPassword') }}
v-spacer
@@ -111,15 +151,16 @@
v-divider
v-card-text.grey.lighten-4.text-center
.pb-2.body-2.text-xs-center.grey--text.text--darken-2 {{ $t('auth:orLoginUsingStrategy') }}
v-tooltip(top, v-for='strategy in strategies', :key='strategy.key')
.social-login-btn.mr-2(
slot='activator'
v-ripple
v-html='strategy.icon'
:class='strategy.color + " elevation-" + (strategy.key === selectedStrategy.key ? "0" : "4")'
@click='selectStrategy(strategy)'
)
span {{ strategy.title }}
v-btn.mx-1.social-login-btn(
v-for='strategy in strategies', :key='strategy.key'
large
@click='selectStrategy(strategy)'
dark
:color='strategy.color'
:depressed='strategy.key === selectedStrategy.key'
)
v-avatar.mr-3(tile, :class='strategy.color', size='24', v-html='strategy.icon')
span(style='text-transform: none;') {{ strategy.title }}
template(v-if='screen === "login" && selectedStrategy.selfRegistration')
v-divider
v-card-actions.py-3(:class='isSocialShown ? "" : "grey lighten-4"')
@@ -142,6 +183,7 @@ import Cookies from 'js-cookie'
import strategiesQuery from 'gql/login/login-query-strategies.gql'
import loginMutation from 'gql/login/login-mutation-login.gql'
import tfaMutation from 'gql/login/login-mutation-tfa.gql'
import changePasswordMutation from 'gql/login/login-mutation-changepassword.gql'
export default {
i18nOptions: { namespaces: 'auth' },
@@ -155,11 +197,13 @@ export default {
password: '',
hidePassword: true,
securityCode: '',
loginToken: '',
continuationToken: '',
isLoading: false,
loaderColor: 'grey darken-4',
loaderTitle: 'Working...',
isShown: false
isShown: false,
newPassword: '',
newPasswordVerify: ''
}
},
computed: {
@@ -205,14 +249,14 @@ export default {
this.$store.commit('showNotification', {
style: 'red',
message: this.$t('auth:invalidEmailUsername'),
icon: 'warning'
icon: 'alert'
})
this.$refs.iptEmail.focus()
} else if (this.password.length < 2) {
this.$store.commit('showNotification', {
style: 'red',
message: this.$t('auth:invalidPassword'),
icon: 'warning'
icon: 'alert'
})
this.$refs.iptPassword.focus()
} else {
@@ -231,10 +275,16 @@ export default {
if (_.has(resp, 'data.authentication.login')) {
let respObj = _.get(resp, 'data.authentication.login', {})
if (respObj.responseResult.succeeded === true) {
if (respObj.tfaRequired === 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.loginToken = respObj.tfaLoginToken
this.$nextTick(() => {
this.$refs.iptTFA.focus()
})
@@ -258,7 +308,7 @@ export default {
this.$store.commit('showNotification', {
style: 'red',
message: err.message,
icon: 'warning'
icon: 'alert'
})
this.isLoading = false
}
@@ -280,7 +330,7 @@ export default {
this.$apollo.mutate({
mutation: tfaMutation,
variables: {
loginToken: this.loginToken,
continuationToken: this.continuationToken,
securityCode: this.securityCode
}
}).then(resp => {
@@ -307,23 +357,59 @@ export default {
this.$store.commit('showNotification', {
style: 'red',
message: err.message,
icon: 'warning'
icon: 'alert'
})
this.isLoading = false
})
}
},
forgotPassword() {
/**
* CHANGE PASSWORD
*/
async changePassword () {
this.loaderColor = 'grey darken-4'
this.loaderTitle = this.$t('auth:changePwd.loading')
this.isLoading = true
const resp = await this.$apollo.mutate({
mutation: changePasswordMutation,
variables: {
continuationToken: this.continuationToken,
newPassword: this.newPassword
}
})
if (_.get(resp, 'data.authentication.loginChangePassword.responseResult.succeeded', false) === true) {
this.loaderColor = 'green darken-1'
this.loaderTitle = this.$t('auth:loginSuccess')
Cookies.set('jwt', _.get(resp, 'data.authentication.loginChangePassword.jwt', ''), { expires: 365 })
_.delay(() => {
window.location.replace('/') // TEMPORARY - USE RETURNURL
}, 1000)
} else {
this.$store.commit('showNotification', {
style: 'red',
message: _.get(resp, 'data.authentication.loginChangePassword.responseResult.message', false),
icon: 'alert'
})
this.isLoading = false
}
},
/**
* SWITCH TO FORGOT PASSWORD SCREEN
*/
forgotPassword () {
this.screen = 'forgot'
this.$nextTick(() => {
this.$refs.iptEmailForgot.focus()
})
},
async forgotPasswordSubmit() {
/**
* FORGOT PASSWORD SUBMIT
*/
async forgotPasswordSubmit () {
this.$store.commit('showNotification', {
style: 'pink',
message: 'Coming soon!',
icon: 'free_breakfast'
icon: 'ferry'
})
}
},
@@ -378,18 +464,12 @@ export default {
}
.social-login-btn {
display: inline-flex;
justify-content: center;
align-items: center;
border-radius: 50%;
width: 54px;
height: 54px;
cursor: pointer;
transition: opacity .2s ease;
&:hover {
opacity: .8;
}
margin: .5rem 0;
margin: .25rem 0;
svg {
width: 24px;
height: 24px;