refactor(mfa): address review feedback on low-backup-codes banner
This commit is contained in:
@@ -1,61 +1,73 @@
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
<script setup>
|
||||
import { computed, onMounted, onBeforeUnmount, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useStore } from 'vuex';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { parseBoolean } from '@chatwoot/utils';
|
||||
import Banner from 'dashboard/components/ui/Banner.vue';
|
||||
import mfaAPI from 'dashboard/api/mfa';
|
||||
import { emitter } from 'shared/helpers/mitt';
|
||||
import { BUS_EVENTS } from 'shared/constants/busEvents';
|
||||
|
||||
const LOW_BACKUP_CODES_THRESHOLD = 3;
|
||||
|
||||
export default {
|
||||
components: { Banner },
|
||||
data() {
|
||||
return {
|
||||
mfaEnabled: false,
|
||||
remainingBackupCodes: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({ currentAccountId: 'getCurrentAccountId' }),
|
||||
shouldShowBanner() {
|
||||
if (!this.mfaEnabled) return false;
|
||||
if (this.remainingBackupCodes === null) return false;
|
||||
return this.remainingBackupCodes <= LOW_BACKUP_CODES_THRESHOLD;
|
||||
},
|
||||
bannerColorScheme() {
|
||||
return this.remainingBackupCodes === 0 ? 'alert' : 'warning';
|
||||
},
|
||||
bannerMessage() {
|
||||
if (this.remainingBackupCodes === 0) {
|
||||
return this.$t('MFA_SETTINGS.LOW_BACKUP_CODES.NONE_LEFT');
|
||||
}
|
||||
return this.$t('MFA_SETTINGS.LOW_BACKUP_CODES.MESSAGE', {
|
||||
count: this.remainingBackupCodes,
|
||||
});
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.fetchMfaStatus();
|
||||
},
|
||||
methods: {
|
||||
async fetchMfaStatus() {
|
||||
if (!parseBoolean(window.chatwootConfig?.isMfaEnabled)) return;
|
||||
const { t } = useI18n();
|
||||
const store = useStore();
|
||||
const router = useRouter();
|
||||
|
||||
try {
|
||||
const { data } = await mfaAPI.get();
|
||||
this.mfaEnabled = data.enabled;
|
||||
this.remainingBackupCodes = data.remaining_backup_codes ?? null;
|
||||
} catch {
|
||||
// ignore; banner stays hidden
|
||||
}
|
||||
},
|
||||
goToMfaSettings() {
|
||||
this.$router.push({
|
||||
name: 'profile_settings_mfa',
|
||||
params: { accountId: this.currentAccountId },
|
||||
});
|
||||
},
|
||||
},
|
||||
const mfaEnabled = ref(false);
|
||||
const remainingBackupCodes = ref(null);
|
||||
|
||||
const currentAccountId = computed(() => store.getters.getCurrentAccountId);
|
||||
|
||||
const shouldShowBanner = computed(() => {
|
||||
if (!mfaEnabled.value) return false;
|
||||
if (remainingBackupCodes.value === null) return false;
|
||||
return remainingBackupCodes.value <= LOW_BACKUP_CODES_THRESHOLD;
|
||||
});
|
||||
|
||||
const bannerColorScheme = computed(() =>
|
||||
remainingBackupCodes.value === 0 ? 'alert' : 'warning'
|
||||
);
|
||||
|
||||
const bannerMessage = computed(() => {
|
||||
if (remainingBackupCodes.value === 0) {
|
||||
return t('MFA_SETTINGS.LOW_BACKUP_CODES.NONE_LEFT');
|
||||
}
|
||||
return t(
|
||||
'MFA_SETTINGS.LOW_BACKUP_CODES.MESSAGE',
|
||||
{ count: remainingBackupCodes.value },
|
||||
remainingBackupCodes.value
|
||||
);
|
||||
});
|
||||
|
||||
const fetchMfaStatus = async () => {
|
||||
if (!parseBoolean(window.chatwootConfig?.isMfaEnabled)) return;
|
||||
|
||||
try {
|
||||
const { data } = await mfaAPI.get();
|
||||
mfaEnabled.value = data.enabled;
|
||||
remainingBackupCodes.value = data.remaining_backup_codes ?? null;
|
||||
} catch {
|
||||
// ignore; banner stays hidden
|
||||
}
|
||||
};
|
||||
|
||||
const goToMfaSettings = () => {
|
||||
router.push({
|
||||
name: 'profile_settings_mfa',
|
||||
params: { accountId: currentAccountId.value },
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
fetchMfaStatus();
|
||||
emitter.on(BUS_EVENTS.MFA_STATE_CHANGED, fetchMfaStatus);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
emitter.off(BUS_EVENTS.MFA_STATE_CHANGED, fetchMfaStatus);
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- eslint-disable-next-line vue/no-root-v-if -->
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
"DISABLE_BUTTON": "Disable Two-Factor Authentication"
|
||||
},
|
||||
"LOW_BACKUP_CODES": {
|
||||
"MESSAGE": "You have {count} backup codes remaining. Generate new codes to avoid getting locked out if you lose access to your authenticator.",
|
||||
"MESSAGE": "You have {count} backup code remaining. Generate new codes to avoid getting locked out if you lose access to your authenticator. | You have {count} backup codes remaining. Generate new codes to avoid getting locked out if you lose access to your authenticator.",
|
||||
"NONE_LEFT": "You have no backup codes remaining. Generate new codes now to avoid getting locked out if you lose access to your authenticator.",
|
||||
"ACTION": "Generate codes"
|
||||
},
|
||||
|
||||
@@ -5,6 +5,8 @@ import { useRouter, useRoute } from 'vue-router';
|
||||
import { parseBoolean } from '@chatwoot/utils';
|
||||
import mfaAPI from 'dashboard/api/mfa';
|
||||
import { useAlert } from 'dashboard/composables';
|
||||
import { emitter } from 'shared/helpers/mitt';
|
||||
import { BUS_EVENTS } from 'shared/constants/busEvents';
|
||||
import MfaStatusCard from './MfaStatusCard.vue';
|
||||
import MfaSetupWizard from './MfaSetupWizard.vue';
|
||||
import MfaManagementActions from './MfaManagementActions.vue';
|
||||
@@ -95,6 +97,7 @@ const completeMfaSetup = () => {
|
||||
mfaEnabled.value = true;
|
||||
backupCodesGenerated.value = true;
|
||||
showSetup.value = false;
|
||||
emitter.emit(BUS_EVENTS.MFA_STATE_CHANGED);
|
||||
useAlert(t('MFA_SETTINGS.SETUP.SUCCESS'));
|
||||
};
|
||||
|
||||
@@ -110,6 +113,7 @@ const disableMfa = async ({ password, otpCode, backupCode }) => {
|
||||
mfaEnabled.value = false;
|
||||
backupCodesGenerated.value = false;
|
||||
managementActionsRef.value?.resetDisableForm();
|
||||
emitter.emit(BUS_EVENTS.MFA_STATE_CHANGED);
|
||||
useAlert(t('MFA_SETTINGS.DISABLE.SUCCESS'));
|
||||
} catch (error) {
|
||||
useAlert(t('MFA_SETTINGS.DISABLE.ERROR'));
|
||||
@@ -123,6 +127,7 @@ const regenerateBackupCodes = async ({ otpCode }) => {
|
||||
backupCodes.value = response.data.backup_codes;
|
||||
managementActionsRef.value?.resetRegenerateForm();
|
||||
managementActionsRef.value?.showBackupCodesDialog();
|
||||
emitter.emit(BUS_EVENTS.MFA_STATE_CHANGED);
|
||||
useAlert(t('MFA_SETTINGS.REGENERATE.SUCCESS'));
|
||||
} catch (error) {
|
||||
useAlert(t('MFA_SETTINGS.REGENERATE.ERROR'));
|
||||
|
||||
@@ -13,4 +13,5 @@ export const BUS_EVENTS = {
|
||||
NEW_CONVERSATION_MODAL: 'newConversationModal',
|
||||
INSERT_INTO_RICH_EDITOR: 'insertIntoRichEditor',
|
||||
INSERT_INTO_NORMAL_EDITOR: 'insertIntoNormalEditor',
|
||||
MFA_STATE_CHANGED: 'MFA_STATE_CHANGED',
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user