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