From 53e57ed0f0b138578d01449ec5115c0bb5fb3774 Mon Sep 17 00:00:00 2001
From: Paul Marillonnet <pmarillonnet@entrouvert.com>
Date: Mon, 19 Dec 2022 16:43:39 +0100
Subject: [PATCH] registration: provide a more user-friendly input code form
 (#72507)

---
 src/authentic2/forms/registration.py                       | 7 ++++++-
 src/authentic2/models.py                                   | 5 ++++-
 .../registration/sms_input_registration_code.html          | 1 +
 src/authentic2/views.py                                    | 5 +++++
 tests/test_registration.py                                 | 2 ++
 5 files changed, 18 insertions(+), 2 deletions(-)

diff --git a/src/authentic2/forms/registration.py b/src/authentic2/forms/registration.py
index c70e0b7ec..0e71cf2d5 100644
--- a/src/authentic2/forms/registration.py
+++ b/src/authentic2/forms/registration.py
@@ -16,6 +16,7 @@
 
 import re
 
+from django.conf import settings
 from django.contrib.auth import get_user_model
 from django.contrib.auth.models import BaseUserManager, Group
 from django.core.exceptions import ValidationError
@@ -195,4 +196,8 @@ class RegistrationCompletionForm(RegistrationCompletionFormNoPassword):
 
 
 class InputRegistrationCodeForm(Form):
-    registration_code = CharField()
+    registration_code = CharField(
+        label=_('Registration code'),
+        help_text=_('The registration code you received by SMS.'),
+        max_length=settings.SMS_CODE_LENGTH,
+    )
diff --git a/src/authentic2/models.py b/src/authentic2/models.py
index 0f32fe04d..ca8378acd 100644
--- a/src/authentic2/models.py
+++ b/src/authentic2/models.py
@@ -815,6 +815,7 @@ class APIClient(models.Model):
 
 
 class SMSCode(models.Model):
+    CODE_DURATION = 120
     KIND_REGISTRATION = 'registration'
     KIND_PASSWORD_LOST = 'password-lost'
     value = models.CharField(
@@ -838,8 +839,10 @@ class SMSCode(models.Model):
         cls.objects.filter(expires__lte=now).delete()
 
     @classmethod
-    def create(cls, phone, kind=None, expires=None, duration=120):
+    def create(cls, phone, kind=None, expires=None, duration=None):
         if not kind:
             kind = cls.KIND_REGISTRATION
+        if not duration:
+            duration = cls.CODE_DURATION
         expires = expires or (timezone.now() + datetime.timedelta(seconds=duration))
         return cls.objects.create(kind=kind, phone=phone, expires=expires)
diff --git a/src/authentic2/templates/registration/sms_input_registration_code.html b/src/authentic2/templates/registration/sms_input_registration_code.html
index ad63c358c..670646131 100644
--- a/src/authentic2/templates/registration/sms_input_registration_code.html
+++ b/src/authentic2/templates/registration/sms_input_registration_code.html
@@ -8,6 +8,7 @@
 {% block content %}
   <form method="post" action=".">
     <p>{% blocktrans trimmed %}Input your account activation code.{% endblocktrans %}</p>
+    <p>{% blocktrans trimmed with plural=duration|pluralize %}Your code is valid for the next {{ duration }} minute{{ plural }}.{% endblocktrans %}</p>
     {% csrf_token %}
     {{ form }}
     <div class="buttons">
diff --git a/src/authentic2/views.py b/src/authentic2/views.py
index eca08ef0a..94d938be6 100644
--- a/src/authentic2/views.py
+++ b/src/authentic2/views.py
@@ -1251,6 +1251,11 @@ class InputRegistrationCodeView(cbv.ValidateCSRFMixin, FormView):
             return HttpResponseBadRequest(_('Invalid account activation code'))
         return super().dispatch(request, *args, **kwargs)
 
+    def get_context_data(self, **kwargs):
+        ctx = super().get_context_data(**kwargs)
+        ctx['duration'] = models.SMSCode.CODE_DURATION // 60
+        return ctx
+
     def post(self, request, *args, **kwargs):
         if 'cancel' in request.POST:
             self.code.delete()
diff --git a/tests/test_registration.py b/tests/test_registration.py
index c8255bf31..e52789d0d 100644
--- a/tests/test_registration.py
+++ b/tests/test_registration.py
@@ -1072,6 +1072,8 @@ def test_phone_registration(app, db, settings):
     assert body['message'].startswith('Your code is')
     code = SMSCode.objects.get()
     assert body['message'][-code_length:] == code.value
+    assert ("Your code is valid for the next %s minute" % (SMSCode.CODE_DURATION // 60)) in resp.text
+    assert "The registration code you received by SMS." in resp.text
     resp.form.set('registration_code', code.value)
     resp = resp.form.submit().follow()
     assert Token.objects.count() == 1
-- 
2.38.1

