From 77abd82c3affe4631951542642f780799e106a09 Mon Sep 17 00:00:00 2001
From: "Crane.z" <1481445951@qq.com>
Date: Mon, 9 Mar 2026 14:33:47 +0800
Subject: [PATCH] fix: make OTP option always selectable in MFA selection page
---
apps/authentication/mfa/otp.py | 4 +++
.../templates/authentication/login_mfa.html | 32 +++++++++++++++++++
apps/authentication/views/mfa.py | 27 ++++++++++++++++
apps/templates/_mfa_login_field.html | 2 +-
4 files changed, 64 insertions(+), 1 deletion(-)
diff --git a/apps/authentication/mfa/otp.py b/apps/authentication/mfa/otp.py
index ac866b882..8c507282e 100644
--- a/apps/authentication/mfa/otp.py
+++ b/apps/authentication/mfa/otp.py
@@ -12,6 +12,10 @@ class MFAOtp(BaseMFA):
display_name = MFAType.OTP.name
placeholder = _('OTP verification code')
+ def is_configured(self):
+ """检查 OTP 是否已配置"""
+ return bool(self.user.otp_secret_key) if self.is_authenticated() else False
+
def _check_code(self, code):
from users.utils import check_otp_code
assert self.is_authenticated()
diff --git a/apps/authentication/templates/authentication/login_mfa.html b/apps/authentication/templates/authentication/login_mfa.html
index 4e33af224..4948140a9 100644
--- a/apps/authentication/templates/authentication/login_mfa.html
+++ b/apps/authentication/templates/authentication/login_mfa.html
@@ -15,6 +15,14 @@
{% include '_mfa_login_field.html' %}
+
+ {% if show_otp_hint %}
+
+
+ {% trans "To set up OTP, leave the field empty and click Next. You'll be guided to the setup page." %}
+
+ {% endif %}
+
@@ -27,4 +35,28 @@
margin-top: 15px;
}
+
{% endblock %}
diff --git a/apps/authentication/views/mfa.py b/apps/authentication/views/mfa.py
index 4898cc093..58a3b490b 100644
--- a/apps/authentication/views/mfa.py
+++ b/apps/authentication/views/mfa.py
@@ -42,6 +42,16 @@ class UserLoginMFAView(mixins.AuthMixin, FormView):
return redirect(reverse('authentication:login-face-capture'))
elif mfa_type == MFAType.Passkey:
return redirect('/api/v1/authentication/passkeys/login/')
+
+ # 特殊处理:如果选择 OTP 且未配置,直接跳转到设置页面
+ if mfa_type == 'otp':
+ user = self.get_user_from_session()
+ mfa_backend = user.get_mfa_backend_by_type(mfa_type)
+ if mfa_backend and hasattr(mfa_backend, 'is_configured'):
+ if not mfa_backend.is_configured():
+ set_url = mfa_backend.get_enable_url()
+ return redirect(set_url + '?_=login_mfa')
+
return self.do_mfa_check(form, code, mfa_type)
def do_mfa_check(self, form, code, mfa_type):
@@ -67,7 +77,24 @@ class UserLoginMFAView(mixins.AuthMixin, FormView):
def get_context_data(self, **kwargs):
user = self.get_user_from_session()
mfa_context = self.get_user_mfa_context(user)
+
+ # 检查是否需要显示 OTP 设置提示
+ # 只有在有多个 MFA 选项且 OTP 未配置时才显示
+ mfa_backends = mfa_context.get('mfa_backends', [])
+ show_otp_hint = False
+
+ if len(mfa_backends) > 1: # 有多个 MFA 选项
+ for backend in mfa_backends:
+ if backend.name == 'otp':
+ if hasattr(backend, 'is_configured'):
+ show_otp_hint = not backend.is_configured()
+ else:
+ show_otp_hint = not backend.is_active()
+ break
+
kwargs.update(mfa_context)
+ kwargs['show_otp_hint'] = show_otp_hint
+ return kwargs
return kwargs
diff --git a/apps/templates/_mfa_login_field.html b/apps/templates/_mfa_login_field.html
index 7623f6532..056611080 100644
--- a/apps/templates/_mfa_login_field.html
+++ b/apps/templates/_mfa_login_field.html
@@ -6,7 +6,7 @@
>
{% for backend in mfa_backends %}