You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by ha...@apache.org on 2022/11/27 17:18:41 UTC
[cloudstack] 02/03: Fixed 2FA at login page
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 <ha...@gmail.com>
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>