/** * Email Domain Validation Utilities * * Validates email domain formats (the part after @ in email addresses) * Used for SSO domain allowlist feature to restrict auto-provisioning to specific domains. */ /** * Validates an email domain format (the part after @ in email addresses) * Valid: example.com, sub.domain.org, my-company.co.uk * Invalid: http://example.com, @example.com, example, .com * * @param domain - The domain string to validate * @returns true if the domain is a valid email domain format */ export function isValidEmailDomain(domain: string): boolean { if (!domain || typeof domain !== 'string') { return false; } const trimmed = domain.trim(); if (!trimmed) { return false; } // Domain regex: must have at least one dot, valid characters, and proper TLD // Allows subdomains, hyphens (not at start/end of labels), alphanumeric characters const domainRegex = /^(?!-)[A-Za-z0-9-]{1,63}(?(); for (const domain of domains) { const result = validateEmailDomain(domain); if (!result.valid) { return { valid: false, error: `Invalid domain "${domain}": ${result.error}` }; } // `EmailDomainValidationResult` is a flat interface, not a discriminated // union, so `valid: true` does not prove `cleanedDomain` is set. Treat a // missing one as a validation failure rather than pushing `undefined` into // the cleaned list (which is what the old assertion allowed downstream). const { cleanedDomain } = result; if (!cleanedDomain) { return { valid: false, error: `Invalid domain "${domain}": could not be normalized` }; } // Check for duplicates (case-insensitive) if (seenDomains.has(cleanedDomain)) { return { valid: false, error: `Duplicate domain: ${cleanedDomain}` }; } seenDomains.add(cleanedDomain); cleanedDomains.push(cleanedDomain); } return { valid: true, cleanedDomains }; } /** * Cleans a domain string by removing common user input mistakes * Does NOT validate - use validateEmailDomain for validation * * @param domain - The domain string to clean * @returns Cleaned domain string */ export function cleanEmailDomain(domain: string): string { if (!domain || typeof domain !== 'string') { return ''; } let cleaned = domain.trim().toLowerCase(); // Remove leading @ if (cleaned.startsWith('@')) { cleaned = cleaned.substring(1); } // Remove protocol if present cleaned = cleaned.replace(/^https?:\/\//, ''); // Remove trailing slash and path cleaned = cleaned.split('/')[0]; // Remove www. prefix cleaned = cleaned.replace(/^www\./, ''); return cleaned; }