web/satellite/vuetify: add disable MFA dialog
Added disable MFA dialog and functionality. Issue: https://github.com/storj/storj/issues/6091 Change-Id: I24cf863b2a04cc147894ab0640e1e1491acc430d
This commit is contained in:
parent
185ebe3dcf
commit
00531fe2b0
@ -0,0 +1,198 @@
|
||||
// Copyright (C) 2023 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
<template>
|
||||
<v-dialog
|
||||
v-model="model"
|
||||
width="auto"
|
||||
min-width="320px"
|
||||
max-width="460px"
|
||||
transition="fade-transition"
|
||||
>
|
||||
<v-card ref="innerContent" rounded="xlg">
|
||||
<v-card-item class="pl-7 pr-0 pb-5 pt-0">
|
||||
<v-row align="start" justify="space-between" class="ma-0">
|
||||
<v-row align="center" class="ma-0 pt-5">
|
||||
<img class="flex-shrink-0" src="@poc/assets/icon-mfa.svg" alt="Change password">
|
||||
<v-card-title class="font-weight-bold ml-4">Disable Two-Factor</v-card-title>
|
||||
</v-row>
|
||||
<v-btn
|
||||
icon="$close"
|
||||
variant="text"
|
||||
size="small"
|
||||
color="default"
|
||||
:disabled="isLoading"
|
||||
@click="model = false"
|
||||
/>
|
||||
</v-row>
|
||||
</v-card-item>
|
||||
<v-divider class="mx-8" />
|
||||
<v-card-item class="px-8 py-4">
|
||||
<p>Enter the authentication code generated in your two-factor application to disable 2FA.</p>
|
||||
</v-card-item>
|
||||
<v-divider class="mx-8" />
|
||||
<v-card-item class="px-8 pt-4 pb-0">
|
||||
<v-form v-model="formValid" class="pt-1" :onsubmit="disable">
|
||||
<v-text-field
|
||||
v-model="confirmCode"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
:hint="useRecoveryCode ? '' : 'e.g.: 000000'"
|
||||
:rules="rules"
|
||||
:error-messages="isError ? 'Invalid code. Please re-enter.' : ''"
|
||||
:label="useRecoveryCode ? 'Recovery code' : '2FA Code'"
|
||||
required
|
||||
autofocus
|
||||
/>
|
||||
</v-form>
|
||||
</v-card-item>
|
||||
<v-card-item class="px-8 py-0">
|
||||
<a class="text-decoration-underline" style="cursor: pointer;" @click="toggleRecoveryCodeState">
|
||||
{{ useRecoveryCode ? "or use 2FA code" : "or use a recovery code" }}
|
||||
</a>
|
||||
</v-card-item>
|
||||
<v-divider class="mx-8 my-4" />
|
||||
<v-card-actions dense class="px-7 pb-5 pt-0">
|
||||
<v-col class="pl-0">
|
||||
<v-btn
|
||||
variant="outlined"
|
||||
color="default"
|
||||
block
|
||||
:disabled="isLoading"
|
||||
:loading="isLoading"
|
||||
@click="model = false"
|
||||
>
|
||||
Cancel
|
||||
</v-btn>
|
||||
</v-col>
|
||||
<v-col class="px-0">
|
||||
<v-btn
|
||||
color="primary"
|
||||
variant="flat"
|
||||
block
|
||||
:loading="isLoading"
|
||||
:disabled="!formValid"
|
||||
@click="disable"
|
||||
>
|
||||
Disable
|
||||
</v-btn>
|
||||
</v-col>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Component, computed, ref, watch } from 'vue';
|
||||
import {
|
||||
VBtn,
|
||||
VCard,
|
||||
VCardActions,
|
||||
VCardItem,
|
||||
VCardTitle,
|
||||
VCol,
|
||||
VDialog,
|
||||
VDivider,
|
||||
VForm,
|
||||
VRow,
|
||||
VTextField,
|
||||
} from 'vuetify/components';
|
||||
|
||||
import { useLoading } from '@/composables/useLoading';
|
||||
import { useConfigStore } from '@/store/modules/configStore';
|
||||
import { useUsersStore } from '@/store/modules/usersStore';
|
||||
import { DisableMFARequest } from '@/types/users';
|
||||
import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames';
|
||||
import { useNotify } from '@/utils/hooks';
|
||||
|
||||
const { config } = useConfigStore().state;
|
||||
const usersStore = useUsersStore();
|
||||
const { isLoading, withLoading } = useLoading();
|
||||
const notify = useNotify();
|
||||
|
||||
const innerContent = ref<Component | null>(null);
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: boolean,
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: 'update:modelValue', value: boolean): void,
|
||||
}>();
|
||||
|
||||
const confirmCode = ref<string>('');
|
||||
const isError = ref<boolean>(false);
|
||||
const useRecoveryCode = ref<boolean>(false);
|
||||
const formValid = ref<boolean>(false);
|
||||
|
||||
const model = computed<boolean>({
|
||||
get: () => props.modelValue,
|
||||
set: value => emit('update:modelValue', value),
|
||||
});
|
||||
|
||||
/**
|
||||
* Returns validation rules based on whether recovery
|
||||
* code input is being used.
|
||||
*/
|
||||
const rules = computed(() => {
|
||||
if (useRecoveryCode.value) {
|
||||
return [
|
||||
(value: string) => (!!value || 'Can\'t be empty'),
|
||||
];
|
||||
}
|
||||
return [
|
||||
(value: string) => (!!value || 'Can\'t be empty'),
|
||||
(value: string) => (!value.includes(' ') || 'Can\'t contain spaces'),
|
||||
(value: string) => (!!parseInt(value) || 'Can only be numbers'),
|
||||
(value: string) => (value.length === 6 || 'Can only be 6 numbers long'),
|
||||
];
|
||||
});
|
||||
|
||||
/**
|
||||
* Toggles whether the MFA recovery code input is shown.
|
||||
*/
|
||||
function toggleRecoveryCodeState(): void {
|
||||
isError.value = false;
|
||||
confirmCode.value = '';
|
||||
useRecoveryCode.value = !useRecoveryCode.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables user MFA.
|
||||
*/
|
||||
function disable(): void {
|
||||
if (!formValid.value) return;
|
||||
|
||||
const request = new DisableMFARequest();
|
||||
if (useRecoveryCode.value) {
|
||||
request.recoveryCode = confirmCode.value;
|
||||
} else {
|
||||
request.passcode = confirmCode.value;
|
||||
}
|
||||
|
||||
withLoading(async () => {
|
||||
try {
|
||||
await usersStore.disableUserMFA(request);
|
||||
await usersStore.getUser();
|
||||
|
||||
notify.success('MFA was disabled successfully');
|
||||
model.value = false;
|
||||
} catch (error) {
|
||||
notify.notifyError(error, AnalyticsErrorEventSource.DISABLE_MFA_MODAL);
|
||||
isError.value = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
watch(confirmCode, () => {
|
||||
isError.value = false;
|
||||
});
|
||||
|
||||
watch(innerContent, newContent => {
|
||||
if (newContent) return;
|
||||
// dialog has been closed
|
||||
isError.value = false;
|
||||
confirmCode.value = '';
|
||||
useRecoveryCode.value = false;
|
||||
});
|
||||
</script>
|
@ -22,7 +22,7 @@
|
||||
size="small"
|
||||
color="default"
|
||||
:disabled="isLoading"
|
||||
@click="closeDialog"
|
||||
@click="model = false"
|
||||
/>
|
||||
</v-row>
|
||||
</v-card-item>
|
||||
@ -97,7 +97,7 @@
|
||||
block
|
||||
:disabled="isLoading"
|
||||
:loading="isLoading"
|
||||
@click="closeDialog"
|
||||
@click="model = false"
|
||||
>
|
||||
Cancel
|
||||
</v-btn>
|
||||
@ -130,7 +130,7 @@
|
||||
color="primary"
|
||||
variant="flat"
|
||||
block
|
||||
@click="closeDialog"
|
||||
@click="model = false"
|
||||
>
|
||||
Done
|
||||
</v-btn>
|
||||
@ -260,11 +260,6 @@ async function showCodes() {
|
||||
}
|
||||
}
|
||||
|
||||
function closeDialog() {
|
||||
model.value = false;
|
||||
isError.value = false;
|
||||
}
|
||||
|
||||
watch(canvas, async val => {
|
||||
if (!val) return;
|
||||
try {
|
||||
@ -283,5 +278,6 @@ watch(innerContent, newContent => {
|
||||
// dialog has been closed
|
||||
step.value = 0;
|
||||
confirmPasscode.value = '';
|
||||
isError.value = false;
|
||||
});
|
||||
</script>
|
@ -88,7 +88,7 @@
|
||||
<template #append>
|
||||
<v-list-item-action>
|
||||
<v-btn v-if="!user.isMFAEnabled" size="small" @click="toggleEnableMFADialog">Enable Two-factor</v-btn>
|
||||
<v-btn v-else variant="outlined" color="default" size="small">Disable Two-factor</v-btn>
|
||||
<v-btn v-else variant="outlined" color="default" size="small" @click="isDisableMFADialogShown = true">Disable Two-factor</v-btn>
|
||||
</v-list-item-action>
|
||||
</template>
|
||||
</v-list-item>
|
||||
@ -152,6 +152,10 @@
|
||||
v-model="isEnableMFADialogShown"
|
||||
/>
|
||||
|
||||
<DisableMFADialog
|
||||
v-model="isDisableMFADialogShown"
|
||||
/>
|
||||
|
||||
<SetSessionTimeoutDialog
|
||||
v-model="isSetSessionTimeoutDialogShown"
|
||||
/>
|
||||
@ -182,6 +186,7 @@ import { Duration } from '@/utils/time';
|
||||
import ChangePasswordDialog from '@poc/components/dialogs/ChangePasswordDialog.vue';
|
||||
import ChangeNameDialog from '@poc/components/dialogs/ChangeNameDialog.vue';
|
||||
import EnableMFADialog from '@poc/components/dialogs/EnableMFADialog.vue';
|
||||
import DisableMFADialog from '@poc/components/dialogs/DisableMFADialog.vue';
|
||||
import SetSessionTimeoutDialog from '@poc/components/dialogs/SetSessionTimeoutDialog.vue';
|
||||
|
||||
const usersStore = useUsersStore();
|
||||
@ -190,6 +195,7 @@ const notify = useNotify();
|
||||
const isChangePasswordDialogShown = ref<boolean>(false);
|
||||
const isChangeNameDialogShown = ref<boolean>(false);
|
||||
const isEnableMFADialogShown = ref<boolean>(false);
|
||||
const isDisableMFADialogShown = ref<boolean>(false);
|
||||
const isSetSessionTimeoutDialogShown = ref<boolean>(false);
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user