authentik.lib.utils.email
Email utility functions
1"""Email utility functions""" 2 3 4def normalize_addresses(addr: str | list[str] | None) -> list[tuple[str, str]] | None: 5 """Normalize email address parameter to list of (name, email) tuples. 6 7 Args: 8 addr: Email address(es). Can be: 9 - None: Returns None 10 - Single email string: "user@example.com" 11 - List of email strings: ["user1@example.com", "user2@example.com"] 12 13 Returns: 14 List of (name, email) tuples suitable for TemplateEmailMessage, or None 15 16 Raises: 17 ValueError: If address list is empty or addr is not a valid type 18 """ 19 if addr is None: 20 return None 21 if isinstance(addr, str): 22 return [("", addr)] 23 if isinstance(addr, list): 24 if not addr: 25 raise ValueError("Address list cannot be empty") 26 return [("", email) for email in addr] 27 raise ValueError("Address must be a string or list of strings") 28 29 30def mask_email(email: str | None) -> str | None: 31 """Mask email address for privacy 32 33 Args: 34 email: Email address to mask 35 Returns: 36 Masked email address or None if input is None 37 Example: 38 mask_email("myname@company.org") 39 'm*****@c******.org' 40 """ 41 if not email: 42 return None 43 44 # Basic email format validation 45 if email.count("@") != 1: 46 raise ValueError("Invalid email format: Must contain exactly one '@' symbol") 47 48 local, domain = email.split("@") 49 if not local or not domain: 50 raise ValueError("Invalid email format: Local and domain parts cannot be empty") 51 52 domain_parts = domain.split(".") 53 if len(domain_parts) < 2: # noqa: PLR2004 54 raise ValueError("Invalid email format: Domain must contain at least one dot") 55 56 limit = 2 57 58 # Mask local part (keep first char) 59 if len(local) <= limit: 60 masked_local = "*" * len(local) 61 else: 62 masked_local = local[0] + "*" * (len(local) - 1) 63 64 # Mask each domain part except the last one (TLD) 65 masked_domain_parts = [] 66 for _i, part in enumerate(domain_parts[:-1]): # Process all parts except TLD 67 if not part: # Check for empty parts (consecutive dots) 68 raise ValueError("Invalid email format: Domain parts cannot be empty") 69 if len(part) <= limit: 70 masked_part = "*" * len(part) 71 else: 72 masked_part = part[0] + "*" * (len(part) - 1) 73 masked_domain_parts.append(masked_part) 74 75 # Add TLD unchanged 76 if not domain_parts[-1]: # Check if TLD is empty 77 raise ValueError("Invalid email format: TLD cannot be empty") 78 masked_domain_parts.append(domain_parts[-1]) 79 80 return f"{masked_local}@{'.'.join(masked_domain_parts)}"
def
normalize_addresses(addr: str | list[str] | None) -> list[tuple[str, str]] | None:
5def normalize_addresses(addr: str | list[str] | None) -> list[tuple[str, str]] | None: 6 """Normalize email address parameter to list of (name, email) tuples. 7 8 Args: 9 addr: Email address(es). Can be: 10 - None: Returns None 11 - Single email string: "user@example.com" 12 - List of email strings: ["user1@example.com", "user2@example.com"] 13 14 Returns: 15 List of (name, email) tuples suitable for TemplateEmailMessage, or None 16 17 Raises: 18 ValueError: If address list is empty or addr is not a valid type 19 """ 20 if addr is None: 21 return None 22 if isinstance(addr, str): 23 return [("", addr)] 24 if isinstance(addr, list): 25 if not addr: 26 raise ValueError("Address list cannot be empty") 27 return [("", email) for email in addr] 28 raise ValueError("Address must be a string or list of strings")
Normalize email address parameter to list of (name, email) tuples.
Args: addr: Email address(es). Can be: - None: Returns None - Single email string: "user@example.com" - List of email strings: ["user1@example.com", "user2@example.com"]
Returns: List of (name, email) tuples suitable for TemplateEmailMessage, or None
Raises: ValueError: If address list is empty or addr is not a valid type
def
mask_email(email: str | None) -> str | None:
31def mask_email(email: str | None) -> str | None: 32 """Mask email address for privacy 33 34 Args: 35 email: Email address to mask 36 Returns: 37 Masked email address or None if input is None 38 Example: 39 mask_email("myname@company.org") 40 'm*****@c******.org' 41 """ 42 if not email: 43 return None 44 45 # Basic email format validation 46 if email.count("@") != 1: 47 raise ValueError("Invalid email format: Must contain exactly one '@' symbol") 48 49 local, domain = email.split("@") 50 if not local or not domain: 51 raise ValueError("Invalid email format: Local and domain parts cannot be empty") 52 53 domain_parts = domain.split(".") 54 if len(domain_parts) < 2: # noqa: PLR2004 55 raise ValueError("Invalid email format: Domain must contain at least one dot") 56 57 limit = 2 58 59 # Mask local part (keep first char) 60 if len(local) <= limit: 61 masked_local = "*" * len(local) 62 else: 63 masked_local = local[0] + "*" * (len(local) - 1) 64 65 # Mask each domain part except the last one (TLD) 66 masked_domain_parts = [] 67 for _i, part in enumerate(domain_parts[:-1]): # Process all parts except TLD 68 if not part: # Check for empty parts (consecutive dots) 69 raise ValueError("Invalid email format: Domain parts cannot be empty") 70 if len(part) <= limit: 71 masked_part = "*" * len(part) 72 else: 73 masked_part = part[0] + "*" * (len(part) - 1) 74 masked_domain_parts.append(masked_part) 75 76 # Add TLD unchanged 77 if not domain_parts[-1]: # Check if TLD is empty 78 raise ValueError("Invalid email format: TLD cannot be empty") 79 masked_domain_parts.append(domain_parts[-1]) 80 81 return f"{masked_local}@{'.'.join(masked_domain_parts)}"
Mask email address for privacy
Args: email: Email address to mask Returns: Masked email address or None if input is None Example: mask_email("myname@company.org") 'm@c*.org'