diff options
| author | kj_sh604 | 2026-04-03 02:42:36 -0400 |
|---|---|---|
| committer | kj_sh604 | 2026-04-03 02:42:36 -0400 |
| commit | 97d55ccdc97ef7b0cc28ded5ebbb46c1879291ce (patch) | |
| tree | 51eb613c9a8b8bbff9f78940b70dea65817464a4 /auth_backend.py | |
| parent | f22c51507eb59cf843f1baca2ad7626fd351f33d (diff) | |
refactor: better email validation
Diffstat (limited to 'auth_backend.py')
| -rw-r--r-- | auth_backend.py | 31 |
1 files changed, 12 insertions, 19 deletions
diff --git a/auth_backend.py b/auth_backend.py index 00a5dbc..817f8d4 100644 --- a/auth_backend.py +++ b/auth_backend.py @@ -9,13 +9,13 @@ import uuid from pathlib import Path from typing import Callable, Optional, Protocol +from email_validator import EmailNotValidError, validate_email + # fixed challenge text used for passphrase verification. # we store encrypted challenge output instead of storing passwords. AUTH_CHALLENGE = "SHIM_AUTH_VALID" HANDLE_RE = re.compile(r"^[a-z0-9_.-]{2,64}$") -EMAIL_LOCAL_RE = re.compile(r"^[a-z0-9!#$%&'*+/=?^_`{|}~.-]{1,64}$") -EMAIL_DOMAIN_LABEL_RE = re.compile(r"^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$") UUID_RE = re.compile( r"^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" ) @@ -54,25 +54,18 @@ def normalize_uuid(value: str) -> Optional[str]: def is_valid_email_username(value: str) -> bool: if len(value) > 254: return False - if "@" not in value or value.count("@") != 1: - return False - - local_part, domain = value.split("@", 1) - if not local_part or not domain: - return False - if local_part.startswith(".") or local_part.endswith(".") or ".." in local_part: - return False - if not EMAIL_LOCAL_RE.fullmatch(local_part): + try: + validated = validate_email( + value, + check_deliverability=False, + allow_smtputf8=False, + allow_display_name=False, + ) + except EmailNotValidError: return False - labels = domain.split(".") - if len(labels) < 2: - return False - if any(not label for label in labels): - return False - if len(labels[-1]) < 2: - return False - return all(EMAIL_DOMAIN_LABEL_RE.fullmatch(label) for label in labels) + # keep stored usernames predictable - only accept already normalized forms. + return validated.normalized == value def looks_like_python_script(path: Path) -> bool: |
