implemented
This commit is contained in:
parent
40ca82bb45
commit
409e0df9a6
16 changed files with 2451 additions and 4 deletions
135
backend/validation.py
Normal file
135
backend/validation.py
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
"""Validation utilities for user profile fields."""
|
||||
import re
|
||||
from email_validator import validate_email, EmailNotValidError
|
||||
from bech32 import bech32_decode
|
||||
|
||||
|
||||
def validate_contact_email(value: str | None) -> str | None:
|
||||
"""
|
||||
Validate contact email format.
|
||||
|
||||
Returns None if valid, error message if invalid.
|
||||
Empty/None values are valid (field is optional).
|
||||
"""
|
||||
if not value:
|
||||
return None
|
||||
|
||||
try:
|
||||
validate_email(value, check_deliverability=False)
|
||||
return None
|
||||
except EmailNotValidError as e:
|
||||
return str(e)
|
||||
|
||||
|
||||
def validate_telegram(value: str | None) -> str | None:
|
||||
"""
|
||||
Validate Telegram handle.
|
||||
|
||||
Must start with @ if provided.
|
||||
Returns None if valid, error message if invalid.
|
||||
Empty/None values are valid (field is optional).
|
||||
"""
|
||||
if not value:
|
||||
return None
|
||||
|
||||
if not value.startswith("@"):
|
||||
return "Telegram handle must start with @"
|
||||
|
||||
if len(value) < 2:
|
||||
return "Telegram handle must have at least one character after @"
|
||||
|
||||
# Telegram usernames: 5-32 characters, alphanumeric and underscores
|
||||
# But we store with @, so check 6-33 total
|
||||
handle = value[1:] # Remove @
|
||||
if len(handle) < 5:
|
||||
return "Telegram handle must be at least 5 characters (after @)"
|
||||
|
||||
if len(handle) > 32:
|
||||
return "Telegram handle must be at most 32 characters (after @)"
|
||||
|
||||
if not re.match(r'^[a-zA-Z][a-zA-Z0-9_]*$', handle):
|
||||
return "Telegram handle must start with a letter and contain only letters, numbers, and underscores"
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def validate_signal(value: str | None) -> str | None:
|
||||
"""
|
||||
Validate Signal username.
|
||||
|
||||
Any non-empty string is valid.
|
||||
Returns None if valid, error message if invalid.
|
||||
Empty/None values are valid (field is optional).
|
||||
"""
|
||||
if not value:
|
||||
return None
|
||||
|
||||
# Signal usernames are fairly permissive, just check it's not empty
|
||||
if len(value.strip()) == 0:
|
||||
return "Signal username cannot be empty"
|
||||
|
||||
if len(value) > 64:
|
||||
return "Signal username must be at most 64 characters"
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def validate_nostr_npub(value: str | None) -> str | None:
|
||||
"""
|
||||
Validate Nostr npub (public key in bech32 format).
|
||||
|
||||
Must be valid bech32 with 'npub' prefix.
|
||||
Returns None if valid, error message if invalid.
|
||||
Empty/None values are valid (field is optional).
|
||||
"""
|
||||
if not value:
|
||||
return None
|
||||
|
||||
if not value.startswith("npub1"):
|
||||
return "Nostr npub must start with 'npub1'"
|
||||
|
||||
# Decode bech32 to validate checksum
|
||||
hrp, data = bech32_decode(value)
|
||||
|
||||
if hrp is None or data is None:
|
||||
return "Invalid Nostr npub: bech32 checksum failed"
|
||||
|
||||
if hrp != "npub":
|
||||
return "Nostr npub must have 'npub' prefix"
|
||||
|
||||
# npub should decode to 32 bytes (256 bits) for a public key
|
||||
# In bech32, each character encodes 5 bits, so 32 bytes = 52 characters of data
|
||||
if len(data) != 52:
|
||||
return "Invalid Nostr npub: incorrect length"
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def validate_profile_fields(
|
||||
contact_email: str | None = None,
|
||||
telegram: str | None = None,
|
||||
signal: str | None = None,
|
||||
nostr_npub: str | None = None,
|
||||
) -> dict[str, str]:
|
||||
"""
|
||||
Validate all profile fields at once.
|
||||
|
||||
Returns a dict of field_name -> error_message for any invalid fields.
|
||||
Empty dict means all fields are valid.
|
||||
"""
|
||||
errors: dict[str, str] = {}
|
||||
|
||||
if err := validate_contact_email(contact_email):
|
||||
errors["contact_email"] = err
|
||||
|
||||
if err := validate_telegram(telegram):
|
||||
errors["telegram"] = err
|
||||
|
||||
if err := validate_signal(signal):
|
||||
errors["signal"] = err
|
||||
|
||||
if err := validate_nostr_npub(nostr_npub):
|
||||
errors["nostr_npub"] = err
|
||||
|
||||
return errors
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue