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 <[email protected]> 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'),
