This is an automated email from the ASF dual-hosted git repository. harikrishna pushed a commit to branch 2FA in repository https://gitbox.apache.org/repos/asf/cloudstack.git
commit 9018e8841556f4d6ca75a51d256d3262ebce03bf Author: Harikrishna Patnala <[email protected]> AuthorDate: Fri Nov 25 16:03:05 2022 +0530 Fixed 2FA at login page --- ui/src/views/auth/Login.vue | 4 +- ui/src/views/dashboard/SetupTwoFaAtLogin.vue | 248 +++++++++++++++++++++++++++ 2 files changed, 250 insertions(+), 2 deletions(-) diff --git a/ui/src/views/auth/Login.vue b/ui/src/views/auth/Login.vue index e4c52eaded9..34f5a7040d5 100644 --- a/ui/src/views/auth/Login.vue +++ b/ui/src/views/auth/Login.vue @@ -298,9 +298,9 @@ export default { loginSuccess (res) { this.$notification.destroy() this.$store.commit('SET_COUNT_NOTIFY', 0) - if (store.getters.twoFaEnabled === true && store.getters.twoFaProvider !== '') { + if (store.getters.twoFaEnabled === true && store.getters.twoFaProvider !== '' && store.getters.twoFaProvider !== undefined) { this.$router.push({ path: '/verify2FA' }).catch(() => {}) - } else if (store.getters.twoFaEnabled === true && store.getters.twoFaProvider === '') { + } else if (store.getters.twoFaEnabled === true && (store.getters.twoFaProvider === '' || store.getters.twoFaProvider === undefined)) { this.$router.push({ path: '/setup2FA' }).catch(() => {}) } else { this.$store.commit('SET_LOGIN_FLAG', true) diff --git a/ui/src/views/dashboard/SetupTwoFaAtLogin.vue b/ui/src/views/dashboard/SetupTwoFaAtLogin.vue new file mode 100644 index 00000000000..0c1d8202974 --- /dev/null +++ b/ui/src/views/dashboard/SetupTwoFaAtLogin.vue @@ -0,0 +1,248 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +<template> + <div class="center"> + <a-form> + <img + v-if="$config.banner" + :style="{ + width: $config.theme['@banner-width'], + height: $config.theme['@banner-height'] + }" + :src="$config.banner" + class="user-layout-logo" + alt="logo"> + <h1 style="text-align: center; font-size: 24px; color: gray"> {{ $t('label.two.factor.authentication') }} </h1> + <br /> + <br /> + </a-form> + <h3> {{ $t('label.select.2fa.provider') }} </h3> + <a-form + :rules="rules" + layout="vertical"> + <div class="form-layout form-align" v-ctrl-enter="submitPin"> + <a-select + :disabled="twoFAenabled === true" + v-model:value="selectedProvider" + optionFilterProp="label" + :filterOption="(input, option) => { + return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0 + }" + style="width: 100%" + @change="val => { handleSelectChange(val) }"> + <a-select-option + v-for="(opt) in providers" + :key="opt.name" + :disabled="opt.enabled === false"> + {{ opt.name }} + </a-select-option> + </a-select> + <div :span="24" v-if="selectedProvider"> + <a-button ref="submit" type="primary" @click="setup2FAProvider">{{ $t('label.setup') }}</a-button> + </div> + </div> + <div v-if="twoFAenabled"> + <div v-if="selectedProvider !== 'staticpin'"> + <br /> + <p v-html="$t('message.two.fa.register.account')"></p> + <vue-qrious + class="center-align" + :value="googleUrl" + @change="onDataUrlChange" + /> + <div style="text-align: center"> <a @click="showConfiguredPin"> {{ $t('message.two.fa.view.setup.key') }}</a></div> + </div> + <div v-if="selectedProvider === 'staticpin'"> + <br> + <p v-html="$t('message.two.fa.staticpin')"></p> + <br> + <div> <a @click="showConfiguredPin"> {{ $t('message.two.fa.view.static.pin') }}</a></div> + </div> + <div v-if="selectedProvider"> + <br /> + <h3> {{ $t('label.enter.code') }} </h3> + <a-form @finish="submitPin" v-ctrl-enter="submitPin" class="container"> + <a-input v-model:value="code" /> + <div :span="24"> + <a-button ref="submit" type="primary" @click="submitPin">{{ $t('label.verify') }}</a-button> + </div> + </a-form> + </div> + + <a-modal + v-if="showPin" + :visible="showPin" + :title="$t(selectedProvider === 'staticpin'? 'label.two.factor.authentication.static.pin' : 'label.two.factor.authentication.secret.key')" + :closable="true" + :footer="null" + @cancel="onCloseModal" + centered + width="450px"> + <div> {{ pin }} </div> + </a-modal> + </div> + </a-form> + </div> +</template> +<script> + +import { api } from '@/api' +import VueQrious from 'vue-qrious' +import eventBus from '@/config/eventBus' +export default { + name: 'RegisterTwoFactorAuth', + props: { + resource: { + type: Object, + required: true + } + }, + components: { + VueQrious + }, + data () { + return { + googleUrl: '', + dataUrl: '', + pin: '', + code: '', + showPin: false, + twoFAenabled: false, + twoFAverified: false, + providers: [], + selectedProvider: null + } + }, + mounted () { + this.list2FAProviders() + }, + created () { + eventBus.on('action-closing', (args) => { + if (args.action.api === 'setupUserTwoFactorAuthentication' && this.twoFAenabled && !this.twoFAverified) { + this.disable2FAProvider() + } + }) + }, + methods: { + onDataUrlChange (dataUrl) { + this.dataUrl = dataUrl + }, + handleSelectChange (val) { + this.selectedProvider = val + }, + setup2FAProvider () { + if (!this.twoFAenabled) { + api('setupUserTwoFactorAuthentication', { provider: this.selectedProvider }).then(response => { + console.log(response) + this.pin = response.setupusertwofactorauthenticationresponse.setup2fa.secretcode + if (this.selectedProvider === 'google') { + this.username = response.setupusertwofactorauthenticationresponse.setup2fa.username + this.googleUrl = 'otpauth://totp/CloudStack:' + this.username + '?secret=' + this.pin + '&issuer=CloudStack' + this.showPin = false + } + if (this.selectedProvider === 'staticpin') { + this.showPin = true + } + this.twoFAenabled = true + this.twoFAverified = false + }).catch(error => { + this.$notification.error({ + message: this.$t('message.request.failed'), + description: (error.response && error.response.headers && error.response.headers['x-description']) || error.message + }) + }) + } + }, + disable2FAProvider () { + api('setupUserTwoFactorAuthentication', { enable: false }).then(response => { + this.showPin = false + this.twoFAenabled = false + this.twoFAverified = false + }).catch(error => { + this.$notification.error({ + message: this.$t('message.request.failed'), + description: (error.response && error.response.headers && error.response.headers['x-description']) || error.message + }) + }) + }, + list2FAProviders () { + api('listUserTwoFactorAuthenticatorProviders', {}).then(response => { + this.providers = response.listusertwofactorauthenticatorprovidersresponse.providers || [] + }) + }, + submitPin () { + api('validateUserTwoFactorAuthenticationCode', { '2facode': this.code }).then(response => { + this.$message.success({ + content: `${this.$t('label.action.enable.two.factor.authentication')}`, + duration: 2 + }) + this.$notification.destroy() + this.$store.commit('SET_COUNT_NOTIFY', 0) + this.$store.commit('SET_LOGIN_FLAG', true) + this.$router.push({ path: '/dashboard' }).catch(() => {}) + + this.twoFAverified = true + this.$emit('refresh-data') + }).catch(error => { + this.$notification.error({ + message: this.$t('message.request.failed'), + description: (error.response && error.response.headers && error.response.headers['x-description']) || error.message + }) + }) + this.closeAction() + }, + closeAction () { + this.$emit('close-action') + }, + showConfiguredPin () { + this.showPin = true + }, + onCloseModal () { + this.showPin = false + } + } +} +</script> + +<style scoped> + .center { + position: fixed; + top: 42.5%; + left: 50%; + -webkit-transform: translate(-50%, -50%); + + background-color: #D3D3D3; + padding: 70px 50px 70px 50px; + z-index: 100; + } + .center-align { + display: block; + margin-left: auto; + margin-right: auto; + } + .form-align { + display: flex; + flex-direction: row; + } + .top-padding { + padding-top: 35px; + } + .container { + display: flex; + } + +</style>
