feat(mfa): nudge users with a dashboard banner when backup codes run low
This commit is contained in:
@@ -5,6 +5,7 @@ import NetworkNotification from './components/NetworkNotification.vue';
|
||||
import UpdateBanner from './components/app/UpdateBanner.vue';
|
||||
import PaymentPendingBanner from './components/app/PaymentPendingBanner.vue';
|
||||
import PendingEmailVerificationBanner from './components/app/PendingEmailVerificationBanner.vue';
|
||||
import LowBackupCodesBanner from './components/app/LowBackupCodesBanner.vue';
|
||||
import vueActionCable from './helper/actionCable';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useStore } from 'dashboard/composables/store';
|
||||
@@ -30,6 +31,7 @@ export default {
|
||||
PaymentPendingBanner,
|
||||
WootSnackbarBox,
|
||||
PendingEmailVerificationBanner,
|
||||
LowBackupCodesBanner,
|
||||
},
|
||||
setup() {
|
||||
const router = useRouter();
|
||||
@@ -140,6 +142,7 @@ export default {
|
||||
<template v-if="currentAccountId">
|
||||
<PendingEmailVerificationBanner v-if="hideOnOnboardingView" />
|
||||
<PaymentPendingBanner v-if="hideOnOnboardingView" />
|
||||
<LowBackupCodesBanner v-if="hideOnOnboardingView" />
|
||||
</template>
|
||||
<router-view v-slot="{ Component }">
|
||||
<transition name="fade" mode="out-in">
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import { parseBoolean } from '@chatwoot/utils';
|
||||
import Banner from 'dashboard/components/ui/Banner.vue';
|
||||
import mfaAPI from 'dashboard/api/mfa';
|
||||
|
||||
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;
|
||||
|
||||
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 },
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<!-- eslint-disable-next-line vue/no-root-v-if -->
|
||||
<template>
|
||||
<Banner
|
||||
v-if="shouldShowBanner"
|
||||
:color-scheme="bannerColorScheme"
|
||||
:banner-message="bannerMessage"
|
||||
:action-button-label="$t('MFA_SETTINGS.LOW_BACKUP_CODES.ACTION')"
|
||||
action-button-icon="i-lucide-key"
|
||||
has-action-button
|
||||
@primary-action="goToMfaSettings"
|
||||
/>
|
||||
</template>
|
||||
@@ -49,6 +49,11 @@
|
||||
"DISABLE_MFA_DESC": "Remove two-factor authentication from your account",
|
||||
"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.",
|
||||
"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"
|
||||
},
|
||||
"DISABLE": {
|
||||
"TITLE": "Disable Two-Factor Authentication",
|
||||
"DESCRIPTION": "You'll need to enter your password and either a verification code from your authenticator app or a backup code to disable two-factor authentication.",
|
||||
|
||||
Reference in New Issue
Block a user