diff --git a/app/javascript/dashboard/App.vue b/app/javascript/dashboard/App.vue
index a706e2df5..9c4d8b880 100644
--- a/app/javascript/dashboard/App.vue
+++ b/app/javascript/dashboard/App.vue
@@ -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 {
+
diff --git a/app/javascript/dashboard/components/app/LowBackupCodesBanner.vue b/app/javascript/dashboard/components/app/LowBackupCodesBanner.vue
new file mode 100644
index 000000000..deb39d66f
--- /dev/null
+++ b/app/javascript/dashboard/components/app/LowBackupCodesBanner.vue
@@ -0,0 +1,72 @@
+
+
+
+
+
+
diff --git a/app/javascript/dashboard/i18n/locale/en/mfa.json b/app/javascript/dashboard/i18n/locale/en/mfa.json
index 8e356aad4..2f3c54b48 100644
--- a/app/javascript/dashboard/i18n/locale/en/mfa.json
+++ b/app/javascript/dashboard/i18n/locale/en/mfa.json
@@ -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.",
diff --git a/app/services/mfa/management_service.rb b/app/services/mfa/management_service.rb
index d4c01ec1f..187290710 100644
--- a/app/services/mfa/management_service.rb
+++ b/app/services/mfa/management_service.rb
@@ -78,6 +78,10 @@ class Mfa::ManagementService
user.otp_backup_codes.present?
end
+ def remaining_backup_codes_count
+ Array(user.otp_backup_codes).count { |code| code != 'XXXXXXXX' }
+ end
+
def mfa_enabled?
user.otp_required_for_login?
end
diff --git a/app/views/api/v1/profile/mfa/show.json.jbuilder b/app/views/api/v1/profile/mfa/show.json.jbuilder
index 4568f48f9..d9452aeec 100644
--- a/app/views/api/v1/profile/mfa/show.json.jbuilder
+++ b/app/views/api/v1/profile/mfa/show.json.jbuilder
@@ -1,3 +1,6 @@
json.feature_available Chatwoot.mfa_enabled?
json.enabled @user.mfa_enabled?
-json.backup_codes_generated @user.mfa_service.backup_codes_generated? if Chatwoot.mfa_enabled?
+if Chatwoot.mfa_enabled?
+ json.backup_codes_generated @user.mfa_service.backup_codes_generated?
+ json.remaining_backup_codes @user.mfa_service.remaining_backup_codes_count
+end