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'