diff --git a/backend/tests/utils/test_validation.py b/backend/tests/utils/test_validation.py index 6e12f4c5b..73923cbd5 100644 --- a/backend/tests/utils/test_validation.py +++ b/backend/tests/utils/test_validation.py @@ -118,6 +118,12 @@ class TestValidatePassword: validate_password("12345") assert "at least 6 characters" in exc_info.value.message + def test_invalid_non_ascii_password(self): + """Test that passwords with non-ASCII characters fail validation.""" + with pytest.raises(ValidationError) as exc_info: + validate_password("résumé") + assert "ASCII characters" in exc_info.value.message + class TestValidateEmail: """Test email validation.""" @@ -145,3 +151,9 @@ class TestValidateEmail: with pytest.raises(ValidationError) as exc_info: validate_email("@domain.com") assert True + + def test_invalid_non_ascii_email(self): + """Test that emails with non-ASCII characters fail validation.""" + with pytest.raises(ValidationError) as exc_info: + validate_email("résumé@example.com") + assert "ASCII characters" in exc_info.value.message diff --git a/backend/utils/validation.py b/backend/utils/validation.py index 20b14b396..1ccdc2541 100644 --- a/backend/utils/validation.py +++ b/backend/utils/validation.py @@ -15,7 +15,7 @@ class ValidationError(Exception): # Pre-compiled regex patterns for better performance USERNAME_PATTERN = re.compile(r"^[a-zA-Z0-9_-]+$") -EMAIL_PATTERN = re.compile(r"^.+@.+\..+$") +EMAIL_PATTERN = re.compile(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$") def validate_ascii_only(value: str, field_name: str = "field") -> None: @@ -84,6 +84,8 @@ def validate_password(password: str) -> None: log.error(msg) raise ValidationError(msg, "Password") + validate_ascii_only(password, "Password") + if len(password) < 6: msg = "Password must be at least 6 characters long" log.error(msg) diff --git a/frontend/src/stores/users.ts b/frontend/src/stores/users.ts index 1759fe3f7..4394ca208 100644 --- a/frontend/src/stores/users.ts +++ b/frontend/src/stores/users.ts @@ -28,12 +28,14 @@ export default defineStore("users", { ], passwordRules: [ (v: string) => !!v || i18n.global.t("common.required"), + asciiOnly, passwordLength, ], emailRules: [ (v: string) => !!v || i18n.global.t("common.required"), (v: string) => /.+@.+\..+/.test(v) || i18n.global.t("common.invalid-email"), + asciiOnly, ], }),