From d0ca1c9e3c70896190e346d45e42e981753b8f2b Mon Sep 17 00:00:00 2001 From: SKairinos Date: Fri, 17 Jan 2025 16:18:54 +0000 Subject: [PATCH] fix: new otp secret --- codeforlife/user/signals/auth_factor.py | 25 +++++++++++++++++ codeforlife/user/signals/auth_factor_test.py | 28 ++++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 codeforlife/user/signals/auth_factor.py create mode 100644 codeforlife/user/signals/auth_factor_test.py diff --git a/codeforlife/user/signals/auth_factor.py b/codeforlife/user/signals/auth_factor.py new file mode 100644 index 0000000..0345778 --- /dev/null +++ b/codeforlife/user/signals/auth_factor.py @@ -0,0 +1,25 @@ +""" +© Ocado Group +Created on 17/01/2025 at 15:55:22(+00:00). +""" + +import pyotp +from django.db.models import signals +from django.dispatch import receiver + +from ..models import AuthFactor + +# pylint: disable=missing-function-docstring +# pylint: disable=unused-argument + + +@receiver(signals.post_delete, sender=AuthFactor) +def auth_factor__post_delete(sender, instance: AuthFactor, **kwargs): + # Create new secret to ensure secrets are not recycled. + if instance.type == AuthFactor.Type.OTP: + otp_secret = instance.user.userprofile.otp_secret + # Ensure the randomly generated new secret is different to the previous. + while otp_secret == instance.user.userprofile.otp_secret: + instance.user.userprofile.otp_secret = pyotp.random_base32() + + instance.user.userprofile.save(update_fields=["otp_secret"]) diff --git a/codeforlife/user/signals/auth_factor_test.py b/codeforlife/user/signals/auth_factor_test.py new file mode 100644 index 0000000..efc763e --- /dev/null +++ b/codeforlife/user/signals/auth_factor_test.py @@ -0,0 +1,28 @@ +""" +© Ocado Group +Created on 17/01/2025 at 16:04:46(+00:00). +""" + +from django.test import TestCase + +from ..models import AuthFactor + + +# pylint: disable-next=missing-class-docstring +class TestAuthFactor(TestCase): + fixtures = ["school_2"] + + def test_post_delete(self): + """Deleting an otp-auth-factor assigns a new otp-secret to its user.""" + auth_factor = AuthFactor.objects.filter( + type=AuthFactor.Type.OTP + ).first() + assert auth_factor + + userprofile = auth_factor.user.userprofile + otp_secret = userprofile.otp_secret + + auth_factor.delete() + + userprofile.refresh_from_db() + assert otp_secret != userprofile.otp_secret