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/08 09:50:46 UTC

[cloudstack] 01/03: Added UI changes and permissions

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 4bc15cd69833d6ba6fb3fae48621c89dc1817cd9
Author: Harikrishna Patnala <ha...@gmail.com>
AuthorDate: Mon Nov 7 15:12:43 2022 +0530

    Added UI changes and permissions
---
 .../cloudstack/api/response/UserResponse.java      | 12 ++++
 .../resources/META-INF/db/schema-41710to41800.sql  | 48 +++++++++++++-
 ...ListUserTwoFactorAuthenticatorProvidersCmd.java |  2 +
 .../auth/SetupUserTwoFactorAuthenticationCmd.java  | 17 ++++-
 ...ValidateUserTwoFactorAuthenticationCodeCmd.java |  2 +
 .../api/query/dao/UserAccountJoinDaoImpl.java      |  1 +
 .../com/cloud/api/query/vo/UserAccountJoinVO.java  |  7 ++
 .../java/com/cloud/user/AccountManagerImpl.java    | 48 +++++++++++---
 ui/public/locales/en.json                          |  5 +-
 ui/src/config/section/user.js                      | 26 +++++++-
 ui/src/permission.js                               |  8 +++
 ui/src/store/modules/user.js                       |  4 +-
 ui/src/views/auth/Login.vue                        |  4 +-
 ui/src/views/dashboard/TwoFa.vue                   |  6 +-
 ui/src/views/iam/RegisterTwoFactorAuth.vue         | 76 ++++++++++++----------
 15 files changed, 206 insertions(+), 60 deletions(-)

diff --git a/api/src/main/java/org/apache/cloudstack/api/response/UserResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/UserResponse.java
index 1c81027f0a8..d521ce3a07d 100644
--- a/api/src/main/java/org/apache/cloudstack/api/response/UserResponse.java
+++ b/api/src/main/java/org/apache/cloudstack/api/response/UserResponse.java
@@ -120,6 +120,10 @@ public class UserResponse extends BaseResponse implements SetResourceIconRespons
     @Param(description = "Base64 string representation of the resource icon", since = "4.16.0.0")
     ResourceIconResponse icon;
 
+    @SerializedName(ApiConstants.IS_2FA_ENABLED)
+    @Param(description = "true if user has two factor authentication enabled", since = "4.18.0.0")
+    private Boolean is2FAenabled;
+
     @Override
     public String getObjectId() {
         return this.getId();
@@ -285,4 +289,12 @@ public class UserResponse extends BaseResponse implements SetResourceIconRespons
     public void setResourceIconResponse(ResourceIconResponse icon) {
         this.icon = icon;
     }
+
+    public Boolean Is2FAenabled() {
+        return is2FAenabled;
+    }
+
+    public void set2FAenabled(Boolean is2FAenabled) {
+        this.is2FAenabled = is2FAenabled;
+    }
 }
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41710to41800.sql b/engine/schema/src/main/resources/META-INF/db/schema-41710to41800.sql
index 4618e2ca03e..67c9729e591 100644
--- a/engine/schema/src/main/resources/META-INF/db/schema-41710to41800.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-41710to41800.sql
@@ -52,4 +52,50 @@ ALTER TABLE `cloud`.`vpc`
 
 ALTER TABLE `cloud`.`user` ADD COLUMN `two_factor_authentication_enabled` tinyint NOT NULL DEFAULT 0;
 ALTER TABLE `cloud`.`user` ADD COLUMN `key_for_2fa` varchar(255) default NULL;
-ALTER TABLE `cloud`.`user` ADD COLUMN `user_2fa_provider` varchar(255) default NULL;
\ No newline at end of file
+ALTER TABLE `cloud`.`user` ADD COLUMN `user_2fa_provider` varchar(255) default NULL;
+
+DROP VIEW IF EXISTS `cloud`.`user_view`;
+CREATE VIEW `cloud`.`user_view` AS
+    select
+        user.id,
+        user.uuid,
+        user.username,
+        user.password,
+        user.firstname,
+        user.lastname,
+        user.email,
+        user.state,
+        user.api_key,
+        user.secret_key,
+        user.created,
+        user.removed,
+        user.timezone,
+        user.registration_token,
+        user.is_registered,
+        user.incorrect_login_attempts,
+        user.source,
+        user.default,
+        account.id account_id,
+        account.uuid account_uuid,
+        account.account_name account_name,
+        account.type account_type,
+        account.role_id account_role_id,
+        domain.id domain_id,
+        domain.uuid domain_uuid,
+        domain.name domain_name,
+        domain.path domain_path,
+        async_job.id job_id,
+        async_job.uuid job_uuid,
+        async_job.job_status job_status,
+        async_job.account_id job_account_id,
+        user.two_factor_authentication_enabled two_factor_authentication_enabled
+    from
+        `cloud`.`user`
+            inner join
+        `cloud`.`account` ON user.account_id = account.id
+            inner join
+        `cloud`.`domain` ON account.domain_id = domain.id
+            left join
+        `cloud`.`async_job` ON async_job.instance_id = user.id
+            and async_job.instance_type = 'User'
+            and async_job.job_status = 0;
\ No newline at end of file
diff --git a/server/src/main/java/com/cloud/api/auth/ListUserTwoFactorAuthenticatorProvidersCmd.java b/server/src/main/java/com/cloud/api/auth/ListUserTwoFactorAuthenticatorProvidersCmd.java
index aa82061ac16..f16ddf1ce5a 100644
--- a/server/src/main/java/com/cloud/api/auth/ListUserTwoFactorAuthenticatorProvidersCmd.java
+++ b/server/src/main/java/com/cloud/api/auth/ListUserTwoFactorAuthenticatorProvidersCmd.java
@@ -18,6 +18,7 @@ package com.cloud.api.auth;
 
 import com.cloud.user.Account;
 import com.cloud.user.AccountManager;
+import org.apache.cloudstack.acl.RoleType;
 import org.apache.cloudstack.api.APICommand;
 import org.apache.cloudstack.api.ApiConstants;
 import org.apache.cloudstack.api.BaseCmd;
@@ -32,6 +33,7 @@ import java.util.List;
 
 @APICommand(name = ListUserTwoFactorAuthenticatorProvidersCmd.APINAME,
         description = "Lists user two factor authenticator providers",
+        authorized = {RoleType.Admin, RoleType.DomainAdmin, RoleType.ResourceAdmin, RoleType.User},
         responseObject = UserTwoFactorAuthenticatorProviderResponse.class, since = "4.18.0")
 public class ListUserTwoFactorAuthenticatorProvidersCmd extends BaseCmd {
 
diff --git a/server/src/main/java/com/cloud/api/auth/SetupUserTwoFactorAuthenticationCmd.java b/server/src/main/java/com/cloud/api/auth/SetupUserTwoFactorAuthenticationCmd.java
index 5ef0341662c..055d84c816b 100644
--- a/server/src/main/java/com/cloud/api/auth/SetupUserTwoFactorAuthenticationCmd.java
+++ b/server/src/main/java/com/cloud/api/auth/SetupUserTwoFactorAuthenticationCmd.java
@@ -17,18 +17,21 @@
 package com.cloud.api.auth;
 
 import com.cloud.user.AccountManager;
+import org.apache.cloudstack.acl.RoleType;
 import org.apache.cloudstack.api.APICommand;
 import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.ApiCommandResourceType;
 import org.apache.cloudstack.api.BaseCmd;
 import org.apache.cloudstack.api.Parameter;
 import org.apache.cloudstack.api.ServerApiException;
+import org.apache.cloudstack.api.response.UserResponse;
 import org.apache.cloudstack.api.response.UserTwoFactorAuthenticationSetupResponse;
 import org.apache.cloudstack.context.CallContext;
 import org.apache.log4j.Logger;
 
 import javax.inject.Inject;
 
-@APICommand(name = SetupUserTwoFactorAuthenticationCmd.APINAME, description = "Setup the 2fa for the user.", requestHasSensitiveInfo = false,
+@APICommand(name = SetupUserTwoFactorAuthenticationCmd.APINAME, description = "Setup the 2fa for the user.", authorized = {RoleType.Admin, RoleType.DomainAdmin, RoleType.ResourceAdmin, RoleType.User}, requestHasSensitiveInfo = false,
         responseObject = UserTwoFactorAuthenticationSetupResponse.class, entityType = {}, since = "4.18.0")
 public class SetupUserTwoFactorAuthenticationCmd extends BaseCmd {
 
@@ -48,6 +51,9 @@ public class SetupUserTwoFactorAuthenticationCmd extends BaseCmd {
     @Parameter(name = ApiConstants.ENABLE, type = CommandType.BOOLEAN, description = "Enabled by default, provide false to disable 2FA")
     private Boolean enable;
 
+    @Parameter(name = ApiConstants.USER_ID, type = CommandType.STRING, entityType = UserResponse.class, description = "optional: the id of the user for which 2FA has to be disabled")
+    private Long userId;
+
     /////////////////////////////////////////////////////
     /////////////////// Accessors ///////////////////////
     /////////////////////////////////////////////////////
@@ -60,6 +66,10 @@ public class SetupUserTwoFactorAuthenticationCmd extends BaseCmd {
         return enable == null ? true : enable;
     }
 
+    public Long getUserId() {
+        return userId;
+    }
+
     @Override
     public void execute() throws ServerApiException {
         UserTwoFactorAuthenticationSetupResponse response = accountManager.setupUserTwoFactorAuthentication(this);
@@ -78,4 +88,9 @@ public class SetupUserTwoFactorAuthenticationCmd extends BaseCmd {
         return CallContext.current().getCallingAccount().getId();
     }
 
+    @Override
+    public ApiCommandResourceType getApiResourceType() {
+        return ApiCommandResourceType.User;
+    }
+
 }
diff --git a/server/src/main/java/com/cloud/api/auth/ValidateUserTwoFactorAuthenticationCodeCmd.java b/server/src/main/java/com/cloud/api/auth/ValidateUserTwoFactorAuthenticationCodeCmd.java
index a3405761eac..67d13370819 100644
--- a/server/src/main/java/com/cloud/api/auth/ValidateUserTwoFactorAuthenticationCodeCmd.java
+++ b/server/src/main/java/com/cloud/api/auth/ValidateUserTwoFactorAuthenticationCodeCmd.java
@@ -21,6 +21,7 @@ import com.cloud.api.response.ApiResponseSerializer;
 import com.cloud.exception.CloudAuthenticationException;
 import com.cloud.user.AccountManager;
 import com.cloud.user.UserAccount;
+import org.apache.cloudstack.acl.RoleType;
 import org.apache.cloudstack.api.APICommand;
 import org.apache.cloudstack.api.ApiConstants;
 import org.apache.cloudstack.api.ApiErrorCode;
@@ -44,6 +45,7 @@ import java.util.List;
 import java.util.Map;
 
 @APICommand(name = ValidateUserTwoFactorAuthenticationCodeCmd.APINAME, description = "Checks the 2fa code for the user.", requestHasSensitiveInfo = false,
+        authorized = {RoleType.Admin, RoleType.DomainAdmin, RoleType.ResourceAdmin, RoleType.User},
         responseObject = SuccessResponse.class, entityType = {}, since = "4.18.0")
 public class ValidateUserTwoFactorAuthenticationCodeCmd extends BaseCmd implements APIAuthenticator {
 
diff --git a/server/src/main/java/com/cloud/api/query/dao/UserAccountJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/UserAccountJoinDaoImpl.java
index 8d06bcd0a26..ee6e08d69db 100644
--- a/server/src/main/java/com/cloud/api/query/dao/UserAccountJoinDaoImpl.java
+++ b/server/src/main/java/com/cloud/api/query/dao/UserAccountJoinDaoImpl.java
@@ -72,6 +72,7 @@ public class UserAccountJoinDaoImpl extends GenericDaoBase<UserAccountJoinVO, Lo
         userResponse.setApiKey(usr.getApiKey());
         userResponse.setSecretKey(usr.getSecretKey());
         userResponse.setIsDefault(usr.isDefault());
+        userResponse.set2FAenabled(usr.isTwoFactorAuthenticationEnabled());
 
         // set async job
         if (usr.getJobId() != null) {
diff --git a/server/src/main/java/com/cloud/api/query/vo/UserAccountJoinVO.java b/server/src/main/java/com/cloud/api/query/vo/UserAccountJoinVO.java
index 2c6a5060076..4b73fa58ed6 100644
--- a/server/src/main/java/com/cloud/api/query/vo/UserAccountJoinVO.java
+++ b/server/src/main/java/com/cloud/api/query/vo/UserAccountJoinVO.java
@@ -130,6 +130,9 @@ public class UserAccountJoinVO extends BaseViewVO implements InternalIdentity, I
     @Enumerated(value = EnumType.STRING)
     private User.Source source;
 
+    @Column(name = "two_factor_authentication_enabled")
+    boolean twoFactorAuthenticationEnabled;
+
     public UserAccountJoinVO() {
     }
 
@@ -274,4 +277,8 @@ public class UserAccountJoinVO extends BaseViewVO implements InternalIdentity, I
     public User.Source getSource() {
         return source;
     }
+
+    public boolean isTwoFactorAuthenticationEnabled() {
+        return twoFactorAuthenticationEnabled;
+    }
 }
diff --git a/server/src/main/java/com/cloud/user/AccountManagerImpl.java b/server/src/main/java/com/cloud/user/AccountManagerImpl.java
index 3c2fbe50a2a..40cd5a8d7fb 100644
--- a/server/src/main/java/com/cloud/user/AccountManagerImpl.java
+++ b/server/src/main/java/com/cloud/user/AccountManagerImpl.java
@@ -3197,42 +3197,72 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
         Account caller = CallContext.current().getCallingAccount();
         Account owner = _accountService.getActiveAccountById(caller.getId());
 
-        checkAccess(caller, null, true, owner);
-
         UserTwoFactorAuthenticationSetupResponse response = new UserTwoFactorAuthenticationSetupResponse();
         if (cmd.getEnable()) {
+            checkAccess(caller, null, true, owner);
+            Long userId = CallContext.current().getCallingUserId();
+
             if (StringUtils.isEmpty(providerName)) {
                 throw new InvalidParameterValueException("Provider name is mandatory to setup 2FA");
             }
             UserTwoFactorAuthenticator provider = getUserTwoFactorAuthenticationProvider(providerName);
-            UserAccountVO userAccount = _userAccountDao.findById(owner.getId());
+            UserAccountVO userAccount = _userAccountDao.findById(userId);
+            UserVO userVO = _userDao.findById(userId);
             String code = provider.setup2FAKey(userAccount);
 
             UserVO user = _userDao.createForUpdate();
             user.setKeyFor2fa(code);
             user.setUser2faProvider(provider.getName());
             user.setTwoFactorAuthenticationEnabled(true);
-            _userDao.update(owner.getId(), user);
+            _userDao.update(userId, user);
 
-            response.setId(owner.getUuid());
-            response.setUsername(owner.getName());
+            response.setId(userVO.getUuid());
+            response.setUsername(userAccount.getUsername());
             response.setSecretCode(code);
 
             return response;
         }
 
+        // Admin can disable 2FA of the users
+        UserVO userVO = null;
+        Long userId = cmd.getUserId();
+        if (userId != null) {
+            userVO = validateUser(userId, caller.getDomainId());
+            if (userVO == null) {
+                throw new InvalidParameterValueException("Unable to find user= " + userVO.getUsername() + " in domain id = " + caller.getDomainId());
+            }
+            owner = _accountService.getActiveAccountById(userId);
+        } else {
+            userVO = _userDao.findById(caller.getId());
+        }
+        checkAccess(caller, null, true, owner);
+
         UserVO user = _userDao.createForUpdate();
         user.setKeyFor2fa(null);
         user.setUser2faProvider(null);
         user.setTwoFactorAuthenticationEnabled(false);
-        _userDao.update(owner.getId(), user);
+        _userDao.update(userVO.getId(), user);
 
-        response.setId(owner.getUuid());
-        response.setUsername(owner.getName());
+        response.setId(userVO.getUuid());
+        response.setUsername(userVO.getUsername());
 
         return response;
     }
 
+    private UserVO validateUser(Long userId, Long domainId) {
+        UserVO user = null;
+        if (userId != null) {
+            user = _userDao.findById(userId);
+            if (user == null) {
+                throw new InvalidParameterValueException("Invalid user ID provided");
+            }
+            if (_accountDao.findById(user.getAccountId()).getDomainId() != domainId) {
+                throw new InvalidParameterValueException("User doesn't belong to the specified account or domain");
+            }
+        }
+        return user;
+    }
+
     public UserTwoFactorAuthenticator getUserTwoFactorAuthenticator(final String name) {
         if (StringUtils.isEmpty(name)) {
             throw new CloudRuntimeException("Invalid UserTwoFactorAuthenticator name provided");
diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json
index 508c6f93098..aa2ac1c9336 100644
--- a/ui/public/locales/en.json
+++ b/ui/public/locales/en.json
@@ -136,7 +136,8 @@
 "label.action.reboot.systemvm": "Reboot system VM",
 "label.action.recover.volume": "Recover volume",
 "label.action.recurring.snapshot": "Recurring snapshots",
-"label.action.register.2FA.user.auth": "Register user for Two Factor Authentication",
+"label.action.register.2FA.user.auth": "Register user Two Factor Authentication",
+"label.action.disable.2FA.user.auth": "Disable user Two Factor Authentication",
 "label.action.register.iso": "Register ISO",
 "label.action.register.template": "Register template from URL",
 "label.action.release.ip": "Release IP",
@@ -895,6 +896,7 @@
 "label.iqn": "Target IQN",
 "label.is.in.progress": "is in progress",
 "label.is.shared": "Is shared",
+"label.is2faenabled": "Is 2FA enabled",
 "label.isadvanced": "Show advanced settings",
 "label.iscsi": "iSCSI",
 "label.iscustomized": "Custom disk size",
@@ -1944,6 +1946,7 @@
 "message.action.destroy.instance.with.backups": "Please confirm that you want to destroy the instance. There may be backups associated with the instance which will not be deleted.",
 "message.action.destroy.systemvm": "Please confirm that you want to destroy the System VM.",
 "message.action.destroy.volume": "Please confirm that you want to destroy the volume.",
+"message.action.disable.2FA.user.auth": "Please confirm that you want to disable user Two factor authentication.",
 "message.action.disable.cluster": "Please confirm that you want to disable this cluster.",
 "message.action.disable.physical.network": "Please confirm that you want to disable this physical network.",
 "message.action.disable.pod": "Please confirm that you want to disable this pod.",
diff --git a/ui/src/config/section/user.js b/ui/src/config/section/user.js
index 5a298134839..cb86d395396 100644
--- a/ui/src/config/section/user.js
+++ b/ui/src/config/section/user.js
@@ -26,7 +26,7 @@ export default {
   hidden: true,
   permission: ['listUsers'],
   columns: ['username', 'state', 'firstname', 'lastname', 'email', 'account'],
-  details: ['username', 'id', 'firstname', 'lastname', 'email', 'usersource', 'timezone', 'rolename', 'roletype', 'account', 'domain', 'created'],
+  details: ['username', 'id', 'firstname', 'lastname', 'email', 'usersource', 'timezone', 'rolename', 'roletype', 'is2faenabled', 'account', 'domain', 'created'],
   tabs: [
     {
       name: 'details',
@@ -107,14 +107,34 @@ export default {
       component: shallowRef(defineAsyncComponent(() => import('@/views/iam/ConfigureSamlSsoAuth.vue')))
     },
     {
-      // update API name
-      api: 'updateUser',
+      api: 'setupUserTwoFactorAuthentication',
       icon: 'scan-outlined',
       label: 'label.action.register.2FA.user.auth',
       dataView: true,
       popup: true,
+      show: (record, store) => {
+        return (record.is2faenabled === false && record.id === store.userInfo.id)
+      },
       component: shallowRef(defineAsyncComponent(() => import('@/views/iam/RegisterTwoFactorAuth.vue')))
     },
+    {
+      api: 'setupUserTwoFactorAuthentication',
+      icon: 'scan-outlined',
+      label: 'label.action.disable.2FA.user.auth',
+      message: 'message.action.disable.2FA.user.auth',
+      dataView: true,
+      groupAction: true,
+      popup: true,
+      args: ['enable'],
+      mapping: {
+        enable: {
+          value: (record) => { return false }
+        }
+      },
+      show: (record, store) => {
+        return (record.is2faenabled === true) && (record.id === store.userInfo.id || ['Admin', 'DomainAdmin'].includes(store.userInfo.roletype))
+      }
+    },
     {
       api: 'deleteUser',
       icon: 'delete-outlined',
diff --git a/ui/src/permission.js b/ui/src/permission.js
index b14b826487c..834d0e098be 100644
--- a/ui/src/permission.js
+++ b/ui/src/permission.js
@@ -60,6 +60,14 @@ router.beforeEach((to, from, next) => {
       console.log('hari3')
       next({ path: '/dashboard' })
       NProgress.done()
+    } else if (to.path === '/2FA') {
+      if (store.getters.twoFaEnabled && !store.getters.loginFlag) {
+        console.log('Do Two-factor authentication')
+        next()
+      } else {
+        next({ path: '/dashboard' })
+        NProgress.done()
+      }
     } else {
       console.log('hari4')
       if (Object.keys(store.getters.apis).length === 0) {
diff --git a/ui/src/store/modules/user.js b/ui/src/store/modules/user.js
index 5a83e9bb1da..42c7db72ca3 100644
--- a/ui/src/store/modules/user.js
+++ b/ui/src/store/modules/user.js
@@ -180,9 +180,7 @@ const user = {
           commit('SET_CLOUDIAN', {})
           commit('SET_DOMAIN_STORE', {})
           commit('SET_LOGOUT_FLAG', false)
-          // TODO: get value from session and set - currently hard-coding it
-          // commit('SET_2FA_ENABLED', (result.is2faenabled === 'true'))
-          commit('SET_2FA_ENABLED', true)
+          commit('SET_2FA_ENABLED', (result.is2faenabled === 'true'))
           commit('SET_LOGIN_FLAG', false)
           notification.destroy()
 
diff --git a/ui/src/views/auth/Login.vue b/ui/src/views/auth/Login.vue
index 3fd287b7383..4b1f773ddb0 100644
--- a/ui/src/views/auth/Login.vue
+++ b/ui/src/views/auth/Login.vue
@@ -298,12 +298,10 @@ export default {
     loginSuccess (res) {
       this.$notification.destroy()
       this.$store.commit('SET_COUNT_NOTIFY', 0)
-      this.$store.commit('SET_LOGIN_FLAG', true)
-      console.log(store.getters.twoFaEnabled)
       if (store.getters.twoFaEnabled === true) {
         this.$router.push({ path: '/2FA' }).catch(() => {})
       } else {
-        console.log('hari2')
+        this.$store.commit('SET_LOGIN_FLAG', true)
         this.$router.push({ path: '/dashboard' }).catch(() => {})
       }
     },
diff --git a/ui/src/views/dashboard/TwoFa.vue b/ui/src/views/dashboard/TwoFa.vue
index 0f9e6017642..8d9eb5cb6e1 100644
--- a/ui/src/views/dashboard/TwoFa.vue
+++ b/ui/src/views/dashboard/TwoFa.vue
@@ -86,6 +86,9 @@ export default {
         api('validateUserTwoFactorAuthenticationCode', { '2facode': values.secretkey }).then(response => {
           this.twoFAresponse = true
           if (this.twoFAresponse) {
+            this.$notification.destroy()
+            this.$store.commit('SET_COUNT_NOTIFY', 0)
+            this.$store.commit('SET_LOGIN_FLAG', true)
             this.$router.push({ path: '/dashboard' }).catch(() => {})
           }
           console.log(response)
@@ -96,9 +99,6 @@ export default {
           })
         })
       })
-
-      // Add logic to set loginFlag to true
-      this.$store.dispatch('SetLoginFlag', true)
     }
   }
 }
diff --git a/ui/src/views/iam/RegisterTwoFactorAuth.vue b/ui/src/views/iam/RegisterTwoFactorAuth.vue
index 1c1db3e28cf..67eab5bbefa 100644
--- a/ui/src/views/iam/RegisterTwoFactorAuth.vue
+++ b/ui/src/views/iam/RegisterTwoFactorAuth.vue
@@ -38,43 +38,45 @@
         </a-select-option>
       </a-select>
     </div>
-    <div v-if="selectedProvider === 'google'">
-      <br />
-      <div> {{ $t('message.two.fa.auth.register.account') }} </div>
-      <vue-qrious
-        class="center-align"
-        :value="googleUrl"
-        @change="onDataUrlChange"
-      />
-    </div>
-    <div v-else-if="selectedProvider === 'staticpin'">
-      <div> <a @click="setup2FAProvider"> {{ $t('message.two.fa.static.pin.part2') }}</a></div>
-    </div>
-    <div v-else-if="selectedProvider !== null && selectedProvider !== 'staticpin'">
-      <div> {{ $t('message.two.fa.static.pin.part1') }} <a @click="setup2FAProvider"> {{ $t('message.two.fa.static.pin.part2') }}</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.ok') }}</a-button>
-        </div>
-      </a-form>
-    </div>
+    <div v-if="show2FAdetails">
+      <div v-if="selectedProvider === 'google'">
+        <br />
+        <div> {{ $t('message.two.fa.auth.register.account') }} </div>
+        <vue-qrious
+          class="center-align"
+          :value="googleUrl"
+          @change="onDataUrlChange"
+        />
+      </div>
+      <div v-else-if="selectedProvider === 'staticpin'">
+        <div> <a @click="setup2FAProvider"> {{ $t('message.two.fa.static.pin.part2') }}</a></div>
+      </div>
+      <div v-else-if="selectedProvider !== null && selectedProvider !== 'staticpin'">
+        <div> {{ $t('message.two.fa.static.pin.part1') }} <a @click="setup2FAProvider"> {{ $t('message.two.fa.static.pin.part2') }}</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.ok') }}</a-button>
+          </div>
+        </a-form>
+      </div>
 
-    <a-modal
-      v-if="showPin"
-      :visible="showPin"
-      :title="$t('label.two.factor.secret')"
-      :closable="true"
-      :footer="null"
-      @cancel="onCloseModal"
-      centered
-      width="450px">
-      <div> {{ pin }} </div>
-    </a-modal>
+      <a-modal
+        v-if="showPin"
+        :visible="showPin"
+        :title="$t('label.two.factor.secret')"
+        :closable="true"
+        :footer="null"
+        @cancel="onCloseModal"
+        centered
+        width="450px">
+        <div> {{ pin }} </div>
+      </a-modal>
+    </div>
   </a-form>
   </div>
 </template>
@@ -100,6 +102,7 @@ export default {
       pin: '',
       code: '',
       showPin: false,
+      show2FAdetails: false,
       providers: [],
       selectedProvider: null
     }
@@ -124,6 +127,7 @@ export default {
           this.googleUrl = 'otpauth://totp/CloudStack:' + this.username + '?secret=' + this.pin + '&issuer=CloudStack'
         }
         this.showPin = true
+        this.show2FAdetails = true
       }).catch(error => {
         this.$notification.error({
           message: this.$t('message.request.failed'),