diff --git a/apps/authentication/backends/passkey/api.py b/apps/authentication/backends/passkey/api.py index ace882a92..86c4ebb3d 100644 --- a/apps/authentication/backends/passkey/api.py +++ b/apps/authentication/backends/passkey/api.py @@ -1,9 +1,12 @@ +import time + from django.conf import settings from django.http import JsonResponse from django.shortcuts import render from django.utils.translation import gettext as _ from rest_framework.decorators import action from rest_framework.permissions import IsAuthenticated, AllowAny +from rest_framework.response import Response from authentication.mixins import AuthMixin from common.api import JMSModelViewSet @@ -44,6 +47,9 @@ class PasskeyViewSet(AuthMixin, FlashMessageMixin, JMSModelViewSet): @action(methods=['get'], detail=False, url_path='login', permission_classes=[AllowAny]) def login(self, request): + confirm_mfa = request.GET.get('mfa') + if confirm_mfa: + request.session['passkey_confirm_mfa'] = '1' return render(request, 'authentication/passkey.html', {}) def redirect_to_error(self, error): @@ -64,8 +70,16 @@ class PasskeyViewSet(AuthMixin, FlashMessageMixin, JMSModelViewSet): if not user: return self.redirect_to_error(_('Auth failed')) + confirm_mfa = request.session.get('passkey_confirm_mfa') + if confirm_mfa: + request.session['CONFIRM_LEVEL'] = ConfirmType.values.index('mfa') + 1 + request.session['CONFIRM_TIME'] = int(time.time()) + request.session['passkey_confirm_mfa'] = '' + return Response('ok') + try: self.check_oauth2_auth(user, settings.AUTH_BACKEND_PASSKEY) + self.mark_mfa_ok('passkey', user) return self.redirect_to_guard_view() except Exception as e: msg = getattr(e, 'msg', '') or str(e) diff --git a/apps/authentication/const.py b/apps/authentication/const.py index e95d97b11..ad38eebf6 100644 --- a/apps/authentication/const.py +++ b/apps/authentication/const.py @@ -34,6 +34,7 @@ class MFAType(TextChoices): Email = 'email', _('Email') Face = 'face', _('Face Recognition') Radius = 'otp_radius', _('Radius') + Passkey = 'passkey', _('Passkey') Custom = 'mfa_custom', _('Custom') diff --git a/apps/authentication/mfa/base.py b/apps/authentication/mfa/base.py index ace5a1424..b7f7ae4ee 100644 --- a/apps/authentication/mfa/base.py +++ b/apps/authentication/mfa/base.py @@ -7,6 +7,7 @@ from django.utils.translation import gettext_lazy as _ class BaseMFA(abc.ABC): placeholder = _('Please input security code') skip_cache_check = False + has_code = True def __init__(self, user): """ diff --git a/apps/authentication/mfa/face.py b/apps/authentication/mfa/face.py index 494a44277..424ee3f80 100644 --- a/apps/authentication/mfa/face.py +++ b/apps/authentication/mfa/face.py @@ -11,6 +11,7 @@ class MFAFace(BaseMFA, AuthFaceMixin): display_name = MFAType.Face.name placeholder = 'Face Recognition' skip_cache_check = True + has_code = False def _check_code(self, code): assert self.is_authenticated() diff --git a/apps/authentication/mfa/otp.py b/apps/authentication/mfa/otp.py index 6e56f8a14..ac866b882 100644 --- a/apps/authentication/mfa/otp.py +++ b/apps/authentication/mfa/otp.py @@ -49,4 +49,3 @@ class MFAOtp(BaseMFA): def help_text_of_disable(self): return '' - diff --git a/apps/authentication/mfa/passkey.py b/apps/authentication/mfa/passkey.py new file mode 100644 index 000000000..572d3d859 --- /dev/null +++ b/apps/authentication/mfa/passkey.py @@ -0,0 +1,46 @@ +from django.conf import settings +from django.utils.translation import gettext_lazy as _ + +from authentication.mfa.base import BaseMFA +from ..const import MFAType + + +class MFAPasskey(BaseMFA): + name = MFAType.Passkey.value + display_name = MFAType.Passkey.name + placeholder = 'Passkey' + has_code = False + + def _check_code(self, code): + assert self.is_authenticated() + + return False, '' + + def is_active(self): + if not self.is_authenticated(): + return True + return self.user.passkey_set.count() + + @staticmethod + def global_enabled(): + return settings.AUTH_PASSKEY + + def get_enable_url(self) -> str: + return '/ui/#/profile/passkeys' + + def get_disable_url(self) -> str: + return '/ui/#/profile/passkeys' + + def disable(self): + pass + + def can_disable(self) -> bool: + return False + + @staticmethod + def help_text_of_enable(): + return _("Using passkey as MFA") + + @staticmethod + def help_text_of_disable(): + return _("Using passkey as MFA") diff --git a/apps/authentication/templates/authentication/passkey.html b/apps/authentication/templates/authentication/passkey.html index 6db7141e3..9d87bff27 100644 --- a/apps/authentication/templates/authentication/passkey.html +++ b/apps/authentication/templates/authentication/passkey.html @@ -5,12 +5,13 @@