Skip to content

Commit

Permalink
feat(signup): add password validation
Browse files Browse the repository at this point in the history
  • Loading branch information
alexisig committed Oct 9, 2024
1 parent 28d7fb4 commit cff6c9f
Show file tree
Hide file tree
Showing 5 changed files with 50 additions and 36 deletions.
3 changes: 3 additions & 0 deletions config/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,9 @@
{
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
},
{
"NAME": "utils.validators.ContainsSpecialCharacterValidator",
},
]


Expand Down
18 changes: 15 additions & 3 deletions users/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
UserChangeForm,
UserCreationForm,
)
from django.contrib.auth.password_validation import validate_password

from .models import User

Expand All @@ -21,7 +22,9 @@ class Meta:


class SignupForm(forms.ModelForm):
password1 = forms.CharField(label="Mot de passe", widget=forms.PasswordInput(), max_length=50)
password1 = forms.CharField(
label="Mot de passe", widget=forms.PasswordInput(), validators=[validate_password], max_length=50
)
password2 = forms.CharField(
label="Confirmer le mot de passe",
widget=forms.PasswordInput(),
Expand All @@ -47,8 +50,10 @@ def clean(self):
cleaned_data = super().clean()
password1 = cleaned_data.get("password1")
password2 = cleaned_data.get("password2")

if password1 is not None and password1 != password2:
self.add_error("password2", "Les mots de passe ne sont pas identiques")

return cleaned_data

def save(self, commit=True):
Expand All @@ -73,8 +78,15 @@ class SigninForm(AuthenticationForm):

class UpdatePasswordForm(forms.Form):
old_password = forms.CharField(label="Ancien mot de passe", widget=forms.PasswordInput())
new_password = forms.CharField(label="Nouveau mot de passe", widget=forms.PasswordInput())
new_password2 = forms.CharField(label="Répétez votre nouveau mot de passe", widget=forms.PasswordInput())
new_password = forms.CharField(
label="Nouveau mot de passe",
widget=forms.PasswordInput(),
validators=[validate_password],
)
new_password2 = forms.CharField(
label="Répétez votre nouveau mot de passe",
widget=forms.PasswordInput(),
)

def __init__(self, *args, **kwargs):
self.user = kwargs.pop("user")
Expand Down
38 changes: 6 additions & 32 deletions users/tests/test_signup.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from django.test import TestCase

from utils.validators import MISSING_SPECIAL_CHAR_ERROR

valid_payload = {
"first_name": "John",
"last_name": "Doe",
Expand Down Expand Up @@ -101,35 +103,6 @@ def test_strong_password_are_accepted(self):
errors=[],
)

def test_password_do_not_contain_user_info(self):
payload_without_password = {
"first_name": "John",
"last_name": "Doe",
"email": "[email protected]",
"organism": "Commune",
"function": "Maire",
}

bad_password_payloads = [
{"password1": "JohnDoe!", "password2": "JohnDoe!"},
{"password1": "John!", "password2": "John!"},
{"password1": "Doe!", "password2": "Doe!"},
{"password1": "Commune!", "password2": "Commune!"},
{"password1": "Maire!", "password2": "Maire!"},
{"password1": "CommuneMaire!", "password2": "CommuneMaire!"},
]

for bad_password_payload in bad_password_payloads:
with self.subTest(bad_password_payload=bad_password_payload):
data = {**payload_without_password, **bad_password_payload}
response = self.client.post(path=form_url, data=data)
self.assertFormError(
response=response,
form="form",
field="password1",
errors="Le mot de passe ne doit pas contenir vos informations personnelles.",
)

def test_password_do_not_contain_common_password(self):
password_payload = {
"password1": "password!",
Expand All @@ -138,11 +111,12 @@ def test_password_do_not_contain_common_password(self):

data = {**valid_payload, **password_payload}
response = self.client.post(path=form_url, data=data)

self.assertFormError(
response=response,
form="form",
field="password1",
errors="Le mot de passe est trop commun.",
errors="Ce mot de passe est trop courant.",
)

def test_password_minimum_length(self):
Expand All @@ -157,7 +131,7 @@ def test_password_minimum_length(self):
response=response,
form="form",
field="password1",
errors="Le mot de passe doit contenir au moins 8 caractères.",
errors="Ce mot de passe est trop court. Il doit contenir au minimum 8 caractères.",
)

def test_password_contains_special_chars(self):
Expand All @@ -172,5 +146,5 @@ def test_password_contains_special_chars(self):
response=response,
form="form",
field="password1",
errors="Le mot de passe doit contenir au moins un caractère spécial.",
errors=MISSING_SPECIAL_CHAR_ERROR,
)
11 changes: 11 additions & 0 deletions utils/tests_validators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from django.core.exceptions import ValidationError
from django.test import TestCase

from .validators import ContainsSpecialCharacterValidator


class TestValidators(TestCase):
def test_contains_special_character_validator_with_invalid_password(self) -> None:
validator = ContainsSpecialCharacterValidator()
with self.assertRaises(ValidationError):
validator.validate(password="password")
16 changes: 15 additions & 1 deletion utils/validators.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
from string import punctuation

from django.core.exceptions import ValidationError

NOT_ALPHA_ERROR = "Le champ ne doit contenir que des lettres, des espaces ou des tirets."
MISSING_SPECIAL_CHAR_ERROR = "Le mot de passe doit contenir au moins un caractère spécial."


def is_alpha_valid(value: str) -> bool:
special_chars_allowed = [" ", "-", "'"]
Expand All @@ -8,4 +13,13 @@ def is_alpha_valid(value: str) -> bool:

def is_alpha_validator(value: str) -> bool:
if not is_alpha_valid(value):
raise ValidationError("Le champ ne doit contenir que des lettres, des espaces ou des tirets.")
raise ValidationError(NOT_ALPHA_ERROR)


class ContainsSpecialCharacterValidator:
def validate(self, password, user=None):
if not any(char in password for char in punctuation):
raise ValidationError(MISSING_SPECIAL_CHAR_ERROR)

def get_help_text(self):
return MISSING_SPECIAL_CHAR_ERROR

0 comments on commit cff6c9f

Please sign in to comment.