123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793 |
- <template lang="pug">
- v-app
- .login(:style='`background-image: url(` + bgUrl + `);`')
- .login-sd
- .d-flex.mb-5
- .login-logo
- v-avatar(tile, size='34')
- v-img(:src='logoUrl')
- .login-title
- .text-h6.grey--text.text--darken-4 {{ 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
- .text-subtitle-1 {{$t('auth:selectAuthProvider')}}
- .login-list
- v-list.elevation-1.radius-7(nav, light)
- v-list-item-group(v-model='selectedStrategyKey')
- v-list-item(
- v-for='(stg, idx) of filteredStrategies'
- :key='stg.key'
- :value='stg.key'
- :color='stg.strategy.color'
- )
- v-avatar.mr-3(tile, size='24', v-html='stg.strategy.icon')
- span.text-none {{stg.displayName}}
- //-------------------------------------------------
- //- LOGIN FORM
- //-------------------------------------------------
- template(v-if='screen === `login` && selectedStrategy.strategy.useForm')
- .login-subtitle
- .text-subtitle-1 {{$t('auth:enterCredentials')}}
- .login-form
- v-text-field(
- solo
- flat
- prepend-inner-icon='mdi-clipboard-account'
- background-color='white'
- color='blue darken-2'
- hide-details
- ref='iptEmail'
- v-model='username'
- :placeholder='isUsernameEmail ? $t(`auth:fields.email`) : $t(`auth:fields.username`)'
- :type='isUsernameEmail ? `email` : `text`'
- :autocomplete='isUsernameEmail ? `email` : `username`'
- light
- )
- v-text-field.mt-2(
- solo
- flat
- prepend-inner-icon='mdi-form-textbox-password'
- background-color='white'
- color='blue darken-2'
- hide-details
- ref='iptPassword'
- v-model='password'
- :append-icon='hidePassword ? "mdi-eye-off" : "mdi-eye"'
- @click:append='() => (hidePassword = !hidePassword)'
- :type='hidePassword ? "password" : "text"'
- :placeholder='$t("auth:fields.password")'
- autocomplete='current-password'
- @keyup.enter='login'
- light
- )
- v-btn.mt-2.text-none(
- width='100%'
- large
- color='blue darken-2'
- dark
- @click='login'
- :loading='isLoading'
- ) {{ $t('auth:actions.login') }}
- .text-center.mt-5
- v-btn.text-none(
- text
- rounded
- color='grey darken-3'
- @click.stop.prevent='forgotPassword'
- href='#forgot'
- ): .caption {{ $t('auth:forgotPasswordLink') }}
- v-btn.text-none(
- v-if='selectedStrategyKey === `local` && selectedStrategy.selfRegistration'
- color='indigo darken-2'
- text
- rounded
- href='/register'
- ): .caption {{ $t('auth:switchToRegister.link') }}
- //-------------------------------------------------
- //- FORGOT PASSWORD FORM
- //-------------------------------------------------
- template(v-if='screen === `forgot`')
- .login-subtitle
- .text-subtitle-1 {{$t('auth:forgotPasswordTitle')}}
- .login-info {{ $t('auth:forgotPasswordSubtitle') }}
- .login-form
- v-text-field(
- solo
- flat
- prepend-inner-icon='mdi-clipboard-account'
- background-color='white'
- color='blue darken-2'
- hide-details
- ref='iptForgotPwdEmail'
- v-model='username'
- :placeholder='$t(`auth:fields.email`)'
- type='email'
- autocomplete='email'
- light
- )
- v-btn.mt-2.text-none(
- width='100%'
- large
- color='blue darken-2'
- dark
- @click='forgotPasswordSubmit'
- :loading='isLoading'
- ) {{ $t('auth:sendResetPassword') }}
- .text-center.mt-5
- v-btn.text-none(
- text
- rounded
- color='grey darken-3'
- @click.stop.prevent='screen = `login`'
- href='#forgot'
- ): .caption {{ $t('auth:forgotPasswordCancel') }}
- //-------------------------------------------------
- //- CHANGE PASSWORD FORM
- //-------------------------------------------------
- template(v-if='screen === `changePwd`')
- .login-subtitle
- .text-subtitle-1 {{ $t('auth:changePwd.subtitle') }}
- .login-form
- v-text-field.mt-2(
- type='password'
- solo
- flat
- prepend-inner-icon='mdi-form-textbox-password'
- background-color='white'
- color='blue darken-2'
- hide-details
- ref='iptNewPassword'
- v-model='newPassword'
- :placeholder='$t(`auth:changePwd.newPasswordPlaceholder`)'
- autocomplete='new-password'
- light
- )
- password-strength(slot='progress', v-model='newPassword')
- v-text-field.mt-2(
- type='password'
- solo
- flat
- prepend-inner-icon='mdi-form-textbox-password'
- background-color='white'
- color='blue darken-2'
- hide-details
- v-model='newPasswordVerify'
- :placeholder='$t(`auth:changePwd.newPasswordVerifyPlaceholder`)'
- autocomplete='new-password'
- @keyup.enter='changePassword'
- light
- )
- v-btn.mt-2.text-none(
- width='100%'
- large
- color='blue darken-2'
- dark
- @click='changePassword'
- :loading='isLoading'
- ) {{ $t('auth:changePwd.proceed') }}
- //-------------------------------------------------
- //- TFA FORM
- //-------------------------------------------------
- v-dialog(v-model='isTFAShown', max-width='500', persistent)
- v-card
- .login-tfa.text-center.pa-5.grey--text.text--darken-3
- img(src='_assets/svg/icon-pin-pad.svg')
- .subtitle-2 {{$t('auth:tfaFormTitle')}}
- v-text-field.login-tfa-field.mt-2(
- solo
- flat
- background-color='white'
- color='blue darken-2'
- hide-details
- ref='iptTFA'
- v-model='securityCode'
- :placeholder='$t("auth:tfa.placeholder")'
- autocomplete='one-time-code'
- @keyup.enter='verifySecurityCode(false)'
- light
- )
- v-btn.mt-2.text-none(
- width='100%'
- large
- color='blue darken-2'
- dark
- @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.grey--text.text--darken-3
- .subtitle-1.primary--text {{$t('auth:tfaSetupTitle')}}
- v-divider.my-5
- .subtitle-2 {{$t('auth:tfaSetupInstrFirst')}}
- .caption (#[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 {{$t('auth:tfaSetupInstrSecond')}}
- v-text-field.login-tfa-field.mt-2(
- solo
- flat
- background-color='white'
- color='blue darken-2'
- hide-details
- ref='iptTFASetup'
- v-model='securityCode'
- :placeholder='$t("auth:tfa.placeholder")'
- autocomplete='one-time-code'
- @keyup.enter='verifySecurityCode(true)'
- light
- )
- v-btn.mt-2.text-none(
- width='100%'
- large
- color='blue darken-2'
- dark
- @click='verifySecurityCode(true)'
- :loading='isLoading'
- ) {{ $t('auth:tfa.verifyToken') }}
- loader(v-model='isLoading', :color='loaderColor', :title='loaderTitle', :subtitle='$t(`auth:pleaseWait`)')
- notify(style='padding-top: 64px;')
- </template>
- <script>
- /* 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 Cookies from 'js-cookie'
- import gql from 'graphql-tag'
- import { sync } from 'vuex-pathify'
- export default {
- i18nOptions: { namespaces: 'auth' },
- props: {
- bgUrl: {
- type: String,
- default: ''
- },
- hideLocal: {
- type: Boolean,
- default: false
- },
- changePwdContinuationToken: {
- type: String,
- default: null
- }
- },
- data () {
- return {
- error: false,
- strategies: [],
- selectedStrategyKey: 'unselected',
- selectedStrategy: { key: 'unselected', strategy: { useForm: false, usernameType: 'email' } },
- screen: 'login',
- username: '',
- password: '',
- hidePassword: true,
- securityCode: '',
- continuationToken: '',
- isLoading: false,
- loaderColor: 'grey darken-4',
- loaderTitle: 'Working...',
- isShown: false,
- newPassword: '',
- newPasswordVerify: '',
- isTFAShown: false,
- isTFASetupShown: false,
- tfaQRImage: '',
- errorShown: false,
- errorMessage: ''
- }
- },
- computed: {
- activeModal: sync('editor/activeModal'),
- siteTitle () {
- return siteConfig.title
- },
- isSocialShown () {
- return this.strategies.length > 1
- },
- logoUrl () { return siteConfig.logoUrl },
- filteredStrategies () {
- const qParams = new URLSearchParams(window.location.search)
- if (this.hideLocal && !qParams.has('all')) {
- return _.reject(this.strategies, ['key', 'local'])
- } else {
- return this.strategies
- }
- },
- isUsernameEmail () {
- return this.selectedStrategy.strategy.usernameType === `email`
- }
- },
- watch: {
- filteredStrategies (newValue, oldValue) {
- if (_.head(newValue).strategy.useForm) {
- this.selectedStrategyKey = _.head(newValue).key
- }
- },
- selectedStrategyKey (newValue, oldValue) {
- this.selectedStrategy = _.find(this.strategies, ['key', newValue])
- if (this.screen === 'changePwd') {
- return
- }
- this.screen = 'login'
- if (!this.selectedStrategy.strategy.useForm) {
- this.isLoading = true
- window.location.assign('/login/' + newValue)
- } else {
- this.$nextTick(() => {
- this.$refs.iptEmail.focus()
- })
- }
- }
- },
- mounted () {
- this.isShown = true
- if (this.changePwdContinuationToken) {
- this.screen = 'changePwd'
- this.continuationToken = this.changePwdContinuationToken
- }
- },
- methods: {
- /**
- * LOGIN
- */
- async login () {
- this.errorShown = false
- if (this.username.length < 2) {
- this.errorMessage = this.$t('auth:invalidEmailUsername')
- this.errorShown = true
- this.$refs.iptEmail.focus()
- } else if (this.password.length < 2) {
- 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 {
- const resp = await this.$apollo.mutate({
- 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
- mustSetupTFA
- continuationToken
- redirect
- tfaQRImage
- }
- }
- }
- `,
- variables: {
- username: this.username,
- password: this.password,
- strategy: this.selectedStrategy.key
- }
- })
- if (_.has(resp, 'data.authentication.login')) {
- const respObj = _.get(resp, 'data.authentication.login', {})
- if (respObj.responseResult.succeeded === true) {
- this.handleLoginResponse(respObj)
- } else {
- throw new Error(respObj.responseResult.message)
- }
- } else {
- throw new Error(this.$t('auth:genericError'))
- }
- } catch (err) {
- console.error(err)
- this.$store.commit('showNotification', {
- style: 'red',
- message: err.message,
- icon: 'alert'
- })
- this.isLoading = false
- }
- }
- },
- /**
- * VERIFY TFA CODE
- */
- async verifySecurityCode (setup = false) {
- if (this.securityCode.length !== 6) {
- this.$store.commit('showNotification', {
- style: 'red',
- message: 'Enter a valid security code.',
- icon: 'alert'
- })
- 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
- 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
- }
- })
- if (_.has(resp, 'data.authentication.loginTFA')) {
- let respObj = _.get(resp, 'data.authentication.loginTFA', {})
- if (respObj.responseResult.succeeded === true) {
- 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) {
- console.error(err)
- this.$store.commit('showNotification', {
- style: 'red',
- message: err.message,
- icon: 'alert'
- })
- this.isLoading = false
- }
- }
- },
- /**
- * CHANGE PASSWORD
- */
- async changePassword () {
- this.loaderColor = 'grey darken-4'
- this.loaderTitle = this.$t('auth:changePwd.loading')
- this.isLoading = true
- try {
- const resp = await this.$apollo.mutate({
- mutation: gql`
- mutation (
- $continuationToken: String!
- $newPassword: String!
- ) {
- authentication {
- loginChangePassword (
- continuationToken: $continuationToken
- newPassword: $newPassword
- ) {
- responseResult {
- succeeded
- errorCode
- slug
- message
- }
- jwt
- continuationToken
- redirect
- }
- }
- }
- `,
- variables: {
- continuationToken: this.continuationToken,
- newPassword: this.newPassword
- }
- })
- if (_.has(resp, 'data.authentication.loginChangePassword')) {
- let respObj = _.get(resp, 'data.authentication.loginChangePassword', {})
- if (respObj.responseResult.succeeded === true) {
- this.handleLoginResponse(respObj)
- } else {
- throw new Error(respObj.responseResult.message)
- }
- } else {
- throw new Error(this.$t('auth:genericError'))
- }
- } catch (err) {
- console.error(err)
- this.$store.commit('showNotification', {
- style: 'red',
- message: err.message,
- icon: 'alert'
- })
- this.isLoading = false
- }
- },
- /**
- * SWITCH TO FORGOT PASSWORD SCREEN
- */
- forgotPassword () {
- this.screen = 'forgot'
- this.$nextTick(() => {
- this.$refs.iptForgotPwdEmail.focus()
- })
- },
- /**
- * FORGOT PASSWORD SUBMIT
- */
- async forgotPasswordSubmit () {
- this.loaderColor = 'grey darken-4'
- this.loaderTitle = this.$t('auth:forgotPasswordLoading')
- this.isLoading = true
- try {
- const resp = await this.$apollo.mutate({
- mutation: gql`
- mutation (
- $email: String!
- ) {
- authentication {
- forgotPassword (
- email: $email
- ) {
- responseResult {
- succeeded
- errorCode
- slug
- message
- }
- }
- }
- }
- `,
- variables: {
- email: this.username
- }
- })
- if (_.has(resp, 'data.authentication.forgotPassword.responseResult')) {
- let respObj = _.get(resp, 'data.authentication.forgotPassword.responseResult', {})
- if (respObj.succeeded === true) {
- this.$store.commit('showNotification', {
- style: 'success',
- message: this.$t('auth:forgotPasswordSuccess'),
- icon: 'email'
- })
- this.screen = 'login'
- } else {
- throw new Error(respObj.message)
- }
- } else {
- throw new Error(this.$t('auth:genericError'))
- }
- } catch (err) {
- console.error(err)
- this.$store.commit('showNotification', {
- style: 'red',
- message: err.message,
- icon: 'alert'
- })
- }
- this.isLoading = false
- },
- 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 === '/' && respObj.redirect) {
- Cookies.remove('loginRedirect')
- window.location.replace(respObj.redirect)
- } else if (loginRedirect) {
- Cookies.remove('loginRedirect')
- window.location.replace(loginRedirect)
- } else if (respObj.redirect) {
- window.location.replace(respObj.redirect)
- } else {
- window.location.replace('/')
- }
- }, 1000)
- }
- }
- },
- apollo: {
- strategies: {
- query: gql`
- {
- authentication {
- activeStrategies(enabledOnly: true) {
- key
- strategy {
- key
- logo
- color
- icon
- useForm
- usernameType
- }
- displayName
- order
- selfRegistration
- }
- }
- }
- `,
- update: (data) => _.sortBy(data.authentication.activeStrategies, ['order']),
- watchLoading (isLoading) {
- this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'login-strategies-refresh')
- }
- }
- }
- }
- </script>
- <style lang="scss">
- .login {
- // background-image: url('/_assets/img/splash/1.jpg');
- background-color: mc('grey', '900');
- background-size: cover;
- background-position: center center;
- width: 100%;
- height: 100%;
- &-sd {
- background-color: rgba(255,255,255,.8);
- backdrop-filter: blur(10px);
- -webkit-backdrop-filter: blur(10px);
- border-left: 1px solid rgba(255,255,255,.85);
- border-right: 1px solid rgba(255,255,255,.85);
- width: 450px;
- height: 100%;
- 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;
- align-items: center;
- text-shadow: .5px .5px #FFF;
- }
- &-subtitle {
- padding: 24px 12px 12px 12px;
- color: #111;
- font-weight: 500;
- 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));
- text-align: center;
- border-bottom: 1px solid rgba(0,0,0,.3);
- }
- &-info {
- border-top: 1px solid rgba(255,255,255,.85);
- background-color: rgba(255,255,255,.15);
- border-bottom: 1px solid rgba(0,0,0,.15);
- padding: 12px;
- font-size: 13px;
- text-align: center;
- color: mc('grey', '900');
- }
- &-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;
- }
- &-tfa {
- background-color: #EEE;
- border: 7px solid #FFF;
- &-field input {
- text-align: center;
- }
- &-qr {
- background-color: #FFF;
- padding: 5px;
- border-radius: 5px;
- width: 200px;
- height: 200px;
- margin: 0 auto;
- }
- }
- }
- </style>
|