aboutsummaryrefslogtreecommitdiffstats
path: root/auth_backend.py
diff options
context:
space:
mode:
authorkj_sh6042026-04-03 02:42:36 -0400
committerkj_sh6042026-04-03 02:42:36 -0400
commit97d55ccdc97ef7b0cc28ded5ebbb46c1879291ce (patch)
tree51eb613c9a8b8bbff9f78940b70dea65817464a4 /auth_backend.py
parentf22c51507eb59cf843f1baca2ad7626fd351f33d (diff)
refactor: better email validation
Diffstat (limited to 'auth_backend.py')
-rw-r--r--auth_backend.py31
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: