diff --git a/canaille/core/endpoints/account.py b/canaille/core/endpoints/account.py
index 3f42d5e7..507a375a 100644
--- a/canaille/core/endpoints/account.py
+++ b/canaille/core/endpoints/account.py
@@ -322,6 +322,9 @@ def registration(data=None, hash=None):
]
form["password2"].validators = [
wtforms.validators.DataRequired(),
+ wtforms.validators.EqualTo(
+ "password1", message=_("Password and confirmation do not match.")
+ ),
]
form["password1"].flags.required = True
form["password2"].flags.required = True
diff --git a/canaille/core/endpoints/forms.py b/canaille/core/endpoints/forms.py
index d45d2173..9f700e4f 100644
--- a/canaille/core/endpoints/forms.py
+++ b/canaille/core/endpoints/forms.py
@@ -68,7 +68,12 @@ class ForgottenPasswordForm(Form):
class PasswordResetForm(Form):
password = wtforms.PasswordField(
_("Password"),
- validators=[wtforms.validators.DataRequired()],
+ validators=[
+ wtforms.validators.DataRequired(),
+ password_length_validator,
+ password_too_long_validator,
+ compromised_password_validator,
+ ],
render_kw={
"autocomplete": "new-password",
},
diff --git a/canaille/templates/macro/form.html b/canaille/templates/macro/form.html
index a533ff51..97c9e8ab 100644
--- a/canaille/templates/macro/form.html
+++ b/canaille/templates/macro/form.html
@@ -118,7 +118,7 @@ del_button=false
{% endfor %}
{% endif %}
-{% if field.name == "password1" and field.data|password_strength and not field.errors %}
+{% if (field.name == "password1" or field.name == "password") and field.data|password_strength and not field.errors %}
{% trans %}Password strength{% endtrans %}
diff --git a/canaille/translations/messages.pot b/canaille/translations/messages.pot
index 355f36cd..4e547b3b 100644
--- a/canaille/translations/messages.pot
+++ b/canaille/translations/messages.pot
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2024-12-20 09:35+0100\n"
+"POT-Creation-Date: 2024-12-23 10:40+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
@@ -88,8 +88,8 @@ msgstr ""
msgid "John Doe"
msgstr ""
-#: canaille/backends/ldap/backend.py:179 canaille/core/endpoints/forms.py:105
-#: canaille/core/endpoints/forms.py:372
+#: canaille/backends/ldap/backend.py:179 canaille/core/endpoints/forms.py:110
+#: canaille/core/endpoints/forms.py:377
msgid "jdoe"
msgstr ""
@@ -201,8 +201,8 @@ msgstr ""
msgid "You are already logged in, you cannot create an account."
msgstr ""
-#: canaille/core/endpoints/account.py:301 canaille/core/endpoints/forms.py:256
-#: canaille/core/endpoints/forms.py:390 canaille/templates/base.html:80
+#: canaille/core/endpoints/account.py:301 canaille/core/endpoints/forms.py:261
+#: canaille/core/endpoints/forms.py:395 canaille/templates/base.html:80
#: canaille/templates/core/groups.html:10
#: canaille/templates/core/groups.html:28
#: canaille/templates/core/partial/group-members.html:15
@@ -210,124 +210,129 @@ msgstr ""
msgid "Groups"
msgstr ""
-#: canaille/core/endpoints/account.py:337
-#: canaille/core/endpoints/account.py:426
+#: canaille/core/endpoints/account.py:326 canaille/core/endpoints/forms.py:85
+#: canaille/core/endpoints/forms.py:224
+msgid "Password and confirmation do not match."
+msgstr ""
+
+#: canaille/core/endpoints/account.py:340
+#: canaille/core/endpoints/account.py:429
msgid "User account creation failed."
msgstr ""
-#: canaille/core/endpoints/account.py:346
+#: canaille/core/endpoints/account.py:349
msgid "Your account has been created successfully."
msgstr ""
-#: canaille/core/endpoints/account.py:361
-#: canaille/core/endpoints/account.py:383
+#: canaille/core/endpoints/account.py:364
+#: canaille/core/endpoints/account.py:386
msgid "The email confirmation link that brought you here is invalid."
msgstr ""
-#: canaille/core/endpoints/account.py:368
+#: canaille/core/endpoints/account.py:371
msgid "The email confirmation link that brought you here has expired."
msgstr ""
-#: canaille/core/endpoints/account.py:375
+#: canaille/core/endpoints/account.py:378
msgid "The invitation link that brought you here was invalid."
msgstr ""
-#: canaille/core/endpoints/account.py:390
+#: canaille/core/endpoints/account.py:393
msgid "This address email have already been confirmed."
msgstr ""
-#: canaille/core/endpoints/account.py:397
+#: canaille/core/endpoints/account.py:400
msgid "This address email is already associated with another account."
msgstr ""
-#: canaille/core/endpoints/account.py:404
+#: canaille/core/endpoints/account.py:407
msgid "Your email address have been confirmed."
msgstr ""
-#: canaille/core/endpoints/account.py:434
+#: canaille/core/endpoints/account.py:437
msgid "User account creation succeed."
msgstr ""
-#: canaille/core/endpoints/account.py:610
-#: canaille/core/endpoints/account.py:798
+#: canaille/core/endpoints/account.py:613
+#: canaille/core/endpoints/account.py:801
msgid "Profile edition failed."
msgstr ""
-#: canaille/core/endpoints/account.py:620
-#: canaille/core/endpoints/account.py:816
+#: canaille/core/endpoints/account.py:623
+#: canaille/core/endpoints/account.py:819
msgid "Profile updated successfully."
msgstr ""
-#: canaille/core/endpoints/account.py:628
+#: canaille/core/endpoints/account.py:631
msgid "Email addition failed."
msgstr ""
-#: canaille/core/endpoints/account.py:633
+#: canaille/core/endpoints/account.py:636
msgid ""
"An email has been sent to the email address. Please check your inbox and "
"click on the verification link it contains"
msgstr ""
-#: canaille/core/endpoints/account.py:640
+#: canaille/core/endpoints/account.py:643
msgid "Could not send the verification email"
msgstr ""
-#: canaille/core/endpoints/account.py:650
+#: canaille/core/endpoints/account.py:653
msgid "Email deletion failed."
msgstr ""
-#: canaille/core/endpoints/account.py:653
+#: canaille/core/endpoints/account.py:656
msgid "The email have been successfully deleted."
msgstr ""
-#: canaille/core/endpoints/account.py:692
+#: canaille/core/endpoints/account.py:695
msgid ""
"A password initialization link has been sent at the user email address. "
"It should be received within a few minutes."
msgstr ""
-#: canaille/core/endpoints/account.py:699 canaille/core/endpoints/auth.py:183
+#: canaille/core/endpoints/account.py:702 canaille/core/endpoints/auth.py:183
msgid "Could not send the password initialization email"
msgstr ""
-#: canaille/core/endpoints/account.py:710
+#: canaille/core/endpoints/account.py:713
msgid ""
"A password reset link has been sent at the user email address. It should "
"be received within a few minutes."
msgstr ""
-#: canaille/core/endpoints/account.py:717
+#: canaille/core/endpoints/account.py:720
msgid "Could not send the password reset email"
msgstr ""
-#: canaille/core/endpoints/account.py:733
+#: canaille/core/endpoints/account.py:736
msgid "The account has been locked"
msgstr ""
-#: canaille/core/endpoints/account.py:744
+#: canaille/core/endpoints/account.py:747
msgid "The account has been unlocked"
msgstr ""
-#: canaille/core/endpoints/account.py:757
+#: canaille/core/endpoints/account.py:760
msgid "One-time password authentication has been reset"
msgstr ""
-#: canaille/core/endpoints/account.py:836
+#: canaille/core/endpoints/account.py:839
#, python-format
msgid "The user %(user)s has been successfully deleted"
msgstr ""
-#: canaille/core/endpoints/account.py:853
+#: canaille/core/endpoints/account.py:856
msgid "Locked users cannot be impersonated."
msgstr ""
-#: canaille/core/endpoints/account.py:857 canaille/core/endpoints/auth.py:134
+#: canaille/core/endpoints/account.py:860 canaille/core/endpoints/auth.py:134
#: canaille/core/endpoints/auth.py:367
#, python-format
msgid "Connection successful. Welcome %(user)s"
msgstr ""
-#: canaille/core/endpoints/account.py:895 canaille/core/endpoints/auth.py:269
+#: canaille/core/endpoints/account.py:898 canaille/core/endpoints/auth.py:269
msgid "Your password has been updated successfully"
msgstr ""
@@ -337,9 +342,9 @@ msgid "Email"
msgstr ""
#: canaille/core/endpoints/admin.py:29 canaille/core/endpoints/forms.py:38
-#: canaille/core/endpoints/forms.py:61 canaille/core/endpoints/forms.py:150
-#: canaille/core/endpoints/forms.py:356 canaille/core/endpoints/forms.py:384
-#: canaille/core/endpoints/forms.py:408 canaille/core/endpoints/forms.py:424
+#: canaille/core/endpoints/forms.py:61 canaille/core/endpoints/forms.py:155
+#: canaille/core/endpoints/forms.py:361 canaille/core/endpoints/forms.py:389
+#: canaille/core/endpoints/forms.py:413 canaille/core/endpoints/forms.py:429
msgid "jane.doe@example.com"
msgstr ""
@@ -413,29 +418,25 @@ msgid "Login"
msgstr ""
#: canaille/core/endpoints/forms.py:48 canaille/core/endpoints/forms.py:70
-#: canaille/core/endpoints/forms.py:204
+#: canaille/core/endpoints/forms.py:209
#: canaille/templates/core/profile_settings.html:74
msgid "Password"
msgstr ""
-#: canaille/core/endpoints/forms.py:77 canaille/core/endpoints/forms.py:216
+#: canaille/core/endpoints/forms.py:82 canaille/core/endpoints/forms.py:221
msgid "Password confirmation"
msgstr ""
-#: canaille/core/endpoints/forms.py:80 canaille/core/endpoints/forms.py:219
-msgid "Password and confirmation do not match."
-msgstr ""
-
-#: canaille/core/endpoints/forms.py:99
+#: canaille/core/endpoints/forms.py:104
msgid "Automatic"
msgstr ""
-#: canaille/core/endpoints/forms.py:104
+#: canaille/core/endpoints/forms.py:109
msgid "Username"
msgstr ""
-#: canaille/core/endpoints/forms.py:108 canaille/core/endpoints/forms.py:310
-#: canaille/core/endpoints/forms.py:326 canaille/oidc/endpoints/forms.py:29
+#: canaille/core/endpoints/forms.py:113 canaille/core/endpoints/forms.py:315
+#: canaille/core/endpoints/forms.py:331 canaille/oidc/endpoints/forms.py:29
#: canaille/templates/core/partial/group-members.html:12
#: canaille/templates/core/partial/groups.html:6
#: canaille/templates/core/partial/users.html:12
@@ -443,174 +444,174 @@ msgstr ""
msgid "Name"
msgstr ""
-#: canaille/core/endpoints/forms.py:110
+#: canaille/core/endpoints/forms.py:115
msgid "Title"
msgstr ""
-#: canaille/core/endpoints/forms.py:110
+#: canaille/core/endpoints/forms.py:115
msgid "Vice president"
msgstr ""
-#: canaille/core/endpoints/forms.py:113
+#: canaille/core/endpoints/forms.py:118
msgid "Given name"
msgstr ""
-#: canaille/core/endpoints/forms.py:115
+#: canaille/core/endpoints/forms.py:120
msgid "John"
msgstr ""
-#: canaille/core/endpoints/forms.py:121
+#: canaille/core/endpoints/forms.py:126
msgid "Family Name"
msgstr ""
-#: canaille/core/endpoints/forms.py:124
+#: canaille/core/endpoints/forms.py:129
msgid "Doe"
msgstr ""
-#: canaille/core/endpoints/forms.py:130
+#: canaille/core/endpoints/forms.py:135
msgid "Display Name"
msgstr ""
-#: canaille/core/endpoints/forms.py:133
+#: canaille/core/endpoints/forms.py:138
msgid "Johnny"
msgstr ""
-#: canaille/core/endpoints/forms.py:140 canaille/core/endpoints/forms.py:414
+#: canaille/core/endpoints/forms.py:145 canaille/core/endpoints/forms.py:419
#: canaille/templates/core/profile_edit.html:188
msgid "Email addresses"
msgstr ""
-#: canaille/core/endpoints/forms.py:146 canaille/core/endpoints/forms.py:404
+#: canaille/core/endpoints/forms.py:151 canaille/core/endpoints/forms.py:409
msgid ""
"This email will be used as a recovery address to reset the password if "
"needed"
msgstr ""
-#: canaille/core/endpoints/forms.py:160
+#: canaille/core/endpoints/forms.py:165
msgid "Phone numbers"
msgstr ""
-#: canaille/core/endpoints/forms.py:161
+#: canaille/core/endpoints/forms.py:166
msgid "555-000-555"
msgstr ""
-#: canaille/core/endpoints/forms.py:168
+#: canaille/core/endpoints/forms.py:173
msgid "Address"
msgstr ""
-#: canaille/core/endpoints/forms.py:170
+#: canaille/core/endpoints/forms.py:175
msgid "132, Foobar Street, Gotham City 12401, XX"
msgstr ""
-#: canaille/core/endpoints/forms.py:174
+#: canaille/core/endpoints/forms.py:179
msgid "Street"
msgstr ""
-#: canaille/core/endpoints/forms.py:176
+#: canaille/core/endpoints/forms.py:181
msgid "132, Foobar Street"
msgstr ""
-#: canaille/core/endpoints/forms.py:180
+#: canaille/core/endpoints/forms.py:185
msgid "Postal Code"
msgstr ""
-#: canaille/core/endpoints/forms.py:186
+#: canaille/core/endpoints/forms.py:191
msgid "Locality"
msgstr ""
-#: canaille/core/endpoints/forms.py:188
+#: canaille/core/endpoints/forms.py:193
msgid "Gotham City"
msgstr ""
-#: canaille/core/endpoints/forms.py:192
+#: canaille/core/endpoints/forms.py:197
msgid "Region"
msgstr ""
-#: canaille/core/endpoints/forms.py:194
+#: canaille/core/endpoints/forms.py:199
msgid "North Pole"
msgstr ""
-#: canaille/core/endpoints/forms.py:198
+#: canaille/core/endpoints/forms.py:203
msgid "Photo"
msgstr ""
-#: canaille/core/endpoints/forms.py:202
+#: canaille/core/endpoints/forms.py:207
#: canaille/templates/core/profile_add.html:64
#: canaille/templates/core/profile_edit.html:76
msgid "Delete the photo"
msgstr ""
-#: canaille/core/endpoints/forms.py:227
+#: canaille/core/endpoints/forms.py:232
msgid "User number"
msgstr ""
-#: canaille/core/endpoints/forms.py:229 canaille/core/endpoints/forms.py:235
+#: canaille/core/endpoints/forms.py:234 canaille/core/endpoints/forms.py:240
msgid "1234"
msgstr ""
-#: canaille/core/endpoints/forms.py:233
+#: canaille/core/endpoints/forms.py:238
msgid "Department"
msgstr ""
-#: canaille/core/endpoints/forms.py:239
+#: canaille/core/endpoints/forms.py:244
msgid "Organization"
msgstr ""
-#: canaille/core/endpoints/forms.py:241
+#: canaille/core/endpoints/forms.py:246
msgid "Cogip LTD."
msgstr ""
-#: canaille/core/endpoints/forms.py:245
+#: canaille/core/endpoints/forms.py:250
msgid "Website"
msgstr ""
-#: canaille/core/endpoints/forms.py:247
+#: canaille/core/endpoints/forms.py:252
msgid "https://mywebsite.tld"
msgstr ""
-#: canaille/core/endpoints/forms.py:252
+#: canaille/core/endpoints/forms.py:257
msgid "Preferred language"
msgstr ""
-#: canaille/core/endpoints/forms.py:262
+#: canaille/core/endpoints/forms.py:267
msgid "users, admins …"
msgstr ""
-#: canaille/core/endpoints/forms.py:287
+#: canaille/core/endpoints/forms.py:292
msgid "Account expiration"
msgstr ""
-#: canaille/core/endpoints/forms.py:313
+#: canaille/core/endpoints/forms.py:318
msgid "group"
msgstr ""
-#: canaille/core/endpoints/forms.py:317 canaille/core/endpoints/forms.py:336
+#: canaille/core/endpoints/forms.py:322 canaille/core/endpoints/forms.py:341
#: canaille/templates/core/partial/groups.html:7
msgid "Description"
msgstr ""
-#: canaille/core/endpoints/forms.py:350 canaille/core/endpoints/forms.py:377
+#: canaille/core/endpoints/forms.py:355 canaille/core/endpoints/forms.py:382
msgid "Email address"
msgstr ""
-#: canaille/core/endpoints/forms.py:371
+#: canaille/core/endpoints/forms.py:376
msgid "User name"
msgstr ""
-#: canaille/core/endpoints/forms.py:375
+#: canaille/core/endpoints/forms.py:380
msgid "Username editable by the invitee"
msgstr ""
-#: canaille/core/endpoints/forms.py:417
+#: canaille/core/endpoints/forms.py:422
msgid "New email address"
msgstr ""
-#: canaille/core/endpoints/forms.py:433
+#: canaille/core/endpoints/forms.py:438
#: canaille/templates/core/mails/email_otp.txt:5
msgid "One-time password"
msgstr ""
-#: canaille/core/endpoints/forms.py:439
+#: canaille/core/endpoints/forms.py:444
msgid "123456"
msgstr ""
@@ -777,11 +778,11 @@ msgstr ""
msgid "Pre-consent"
msgstr ""
-#: canaille/oidc/endpoints/oauth.py:379
+#: canaille/oidc/endpoints/oauth.py:388
msgid "You have been disconnected"
msgstr ""
-#: canaille/oidc/endpoints/oauth.py:396
+#: canaille/oidc/endpoints/oauth.py:405
msgid "You have not been disconnected"
msgstr ""
@@ -1101,13 +1102,13 @@ msgstr ""
#: canaille/templates/core/login.html:52
#: canaille/templates/core/mails/registration.txt:5
-#: canaille/templates/core/setup-2fa.html:64
+#: canaille/templates/core/setup-mfa.html:64
msgid "Continue"
msgstr ""
#: canaille/templates/core/password.html:21
-#: canaille/templates/core/setup-2fa.html:32
-#: canaille/templates/core/verify-2fa.html:32
+#: canaille/templates/core/setup-mfa.html:32
+#: canaille/templates/core/verify-mfa.html:32
#, python-format
msgid "Sign in as %(username)s"
msgstr ""
@@ -1117,7 +1118,7 @@ msgid "Please enter your password for this account."
msgstr ""
#: canaille/templates/core/password.html:39
-#: canaille/templates/core/verify-2fa.html:50
+#: canaille/templates/core/verify-mfa.html:50
#, python-format
msgid "I am not %(username)s"
msgstr ""
@@ -1189,7 +1190,7 @@ msgid "Send a verification email to validate this address."
msgstr ""
#: canaille/templates/core/profile_edit.html:233
-#: canaille/templates/core/verify-2fa.html:51
+#: canaille/templates/core/verify-mfa.html:51
msgid "Verify"
msgstr ""
@@ -1294,11 +1295,11 @@ msgstr ""
msgid "Impersonate"
msgstr ""
-#: canaille/templates/core/setup-2fa.html:34
+#: canaille/templates/core/setup-mfa.html:34
msgid "Set up multi-factor authentication."
msgstr ""
-#: canaille/templates/core/verify-2fa.html:34
+#: canaille/templates/core/verify-mfa.html:34
msgid "One-time password authentication."
msgstr ""
diff --git a/tests/core/test_password_reset.py b/tests/core/test_password_reset.py
index a624de06..eb20b5cb 100644
--- a/tests/core/test_password_reset.py
+++ b/tests/core/test_password_reset.py
@@ -7,6 +7,17 @@ def test_password_reset(testclient, user, backend):
res = testclient.get("/reset/user/" + hash, status=200)
+ res.form["password"] = "foobarbaz"
+ res.form["confirmation"] = "foobar"
+ res = res.form.submit()
+ res.mustcontain("Password and confirmation do not match.")
+ res.mustcontain('data-percent="50"')
+
+ res.form["password"] = "123"
+ res.form["confirmation"] = "123"
+ res = res.form.submit()
+ res.mustcontain("Field must be at least 8 characters long.")
+
res.form["password"] = "foobarbaz"
res.form["confirmation"] = "foobarbaz"
res = res.form.submit()
diff --git a/tests/core/test_registration.py b/tests/core/test_registration.py
index 01df1377..5b0589ac 100644
--- a/tests/core/test_registration.py
+++ b/tests/core/test_registration.py
@@ -29,6 +29,40 @@ def test_registration_without_email_validation(testclient, backend, foo_group):
backend.delete(user)
+def test_registration_failure_with_different_passwords_and_too_short_password(
+ testclient, backend, foo_group
+):
+ """Tests a nominal registration without email validation but with a wrong confirmation password and a too short password."""
+ testclient.app.config["CANAILLE"]["ENABLE_REGISTRATION"] = True
+ testclient.app.config["CANAILLE"]["EMAIL_CONFIRMATION"] = False
+
+ assert not backend.query(models.User, user_name="newuser")
+ res = testclient.get(url_for("core.account.registration"), status=200)
+ res.form["user_name"] = "newuser"
+ res.form["password1"] = "123"
+ res.form["password2"] = "123"
+ res.form["family_name"] = "newuser"
+ res.form["emails-0"] = "newuser@example.test"
+ res = res.form.submit()
+ assert ("error", "User account creation failed.") in res.flashes
+ res.mustcontain("Field must be at least 8 characters long.")
+
+ res.form["password1"] = "i'm a little pea"
+ res.form["password2"] = "i'm not a little pea"
+ res = res.form.submit()
+ res.mustcontain("Password and confirmation do not match.")
+ res.mustcontain('data-percent="100"')
+
+ res.form["password1"] = "i'm a little pea"
+ res.form["password2"] = "i'm a little pea"
+ res = res.form.submit()
+ assert ("success", "Your account has been created successfully.") in res.flashes
+
+ user = backend.get(models.User, user_name="newuser")
+ assert user
+ backend.delete(user)
+
+
def test_registration_with_email_validation(testclient, backend, smtpd, foo_group):
"""Tests a nominal registration with email validation."""
testclient.app.config["CANAILLE"]["ENABLE_REGISTRATION"] = True