139 lines
9.9 KiB
JavaScript
139 lines
9.9 KiB
JavaScript
|
|
/**
|
||
|
|
* SMTP Client Validation Utilities
|
||
|
|
* Input validation functions for SMTP client operations
|
||
|
|
*/
|
||
|
|
import { REGEX_PATTERNS } from '../constants.js';
|
||
|
|
/**
|
||
|
|
* Validate email address format
|
||
|
|
* Supports RFC-compliant addresses including empty return paths for bounces
|
||
|
|
*/
|
||
|
|
export function validateEmailAddress(email) {
|
||
|
|
if (typeof email !== 'string') {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
const trimmed = email.trim();
|
||
|
|
// Handle empty return path for bounce messages (RFC 5321)
|
||
|
|
if (trimmed === '' || trimmed === '<>') {
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
// Handle display name formats
|
||
|
|
const angleMatch = trimmed.match(/<([^>]+)>/);
|
||
|
|
if (angleMatch) {
|
||
|
|
return REGEX_PATTERNS.EMAIL_ADDRESS.test(angleMatch[1]);
|
||
|
|
}
|
||
|
|
// Regular email validation
|
||
|
|
return REGEX_PATTERNS.EMAIL_ADDRESS.test(trimmed);
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Validate SMTP client options
|
||
|
|
*/
|
||
|
|
export function validateClientOptions(options) {
|
||
|
|
const errors = [];
|
||
|
|
// Required fields
|
||
|
|
if (!options.host || typeof options.host !== 'string') {
|
||
|
|
errors.push('Host is required and must be a string');
|
||
|
|
}
|
||
|
|
if (!options.port || typeof options.port !== 'number' || options.port < 1 || options.port > 65535) {
|
||
|
|
errors.push('Port must be a number between 1 and 65535');
|
||
|
|
}
|
||
|
|
// Optional field validation
|
||
|
|
if (options.connectionTimeout !== undefined) {
|
||
|
|
if (typeof options.connectionTimeout !== 'number' || options.connectionTimeout < 1000) {
|
||
|
|
errors.push('Connection timeout must be a number >= 1000ms');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (options.socketTimeout !== undefined) {
|
||
|
|
if (typeof options.socketTimeout !== 'number' || options.socketTimeout < 1000) {
|
||
|
|
errors.push('Socket timeout must be a number >= 1000ms');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (options.maxConnections !== undefined) {
|
||
|
|
if (typeof options.maxConnections !== 'number' || options.maxConnections < 1) {
|
||
|
|
errors.push('Max connections must be a positive number');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (options.maxMessages !== undefined) {
|
||
|
|
if (typeof options.maxMessages !== 'number' || options.maxMessages < 1) {
|
||
|
|
errors.push('Max messages must be a positive number');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
// Validate authentication options
|
||
|
|
if (options.auth) {
|
||
|
|
errors.push(...validateAuthOptions(options.auth));
|
||
|
|
}
|
||
|
|
return errors;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Validate authentication options
|
||
|
|
*/
|
||
|
|
export function validateAuthOptions(auth) {
|
||
|
|
const errors = [];
|
||
|
|
if (auth.method && !['PLAIN', 'LOGIN', 'OAUTH2', 'AUTO'].includes(auth.method)) {
|
||
|
|
errors.push('Invalid authentication method');
|
||
|
|
}
|
||
|
|
// For basic auth, require user and pass
|
||
|
|
if ((auth.user || auth.pass) && (!auth.user || !auth.pass)) {
|
||
|
|
errors.push('Both user and pass are required for basic authentication');
|
||
|
|
}
|
||
|
|
// For OAuth2, validate required fields
|
||
|
|
if (auth.oauth2) {
|
||
|
|
const oauth = auth.oauth2;
|
||
|
|
if (!oauth.user || !oauth.clientId || !oauth.clientSecret || !oauth.refreshToken) {
|
||
|
|
errors.push('OAuth2 requires user, clientId, clientSecret, and refreshToken');
|
||
|
|
}
|
||
|
|
if (oauth.user && !validateEmailAddress(oauth.user)) {
|
||
|
|
errors.push('OAuth2 user must be a valid email address');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return errors;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Validate hostname format
|
||
|
|
*/
|
||
|
|
export function validateHostname(hostname) {
|
||
|
|
if (!hostname || typeof hostname !== 'string') {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
// Basic hostname validation (allow IP addresses and domain names)
|
||
|
|
const hostnameRegex = /^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)*[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$|^(?:\d{1,3}\.){3}\d{1,3}$|^\[(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}\]$/;
|
||
|
|
return hostnameRegex.test(hostname);
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Validate port number
|
||
|
|
*/
|
||
|
|
export function validatePort(port) {
|
||
|
|
return typeof port === 'number' && port >= 1 && port <= 65535;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Sanitize and validate domain name for EHLO
|
||
|
|
*/
|
||
|
|
export function validateAndSanitizeDomain(domain) {
|
||
|
|
if (!domain || typeof domain !== 'string') {
|
||
|
|
return 'localhost';
|
||
|
|
}
|
||
|
|
const sanitized = domain.trim().toLowerCase();
|
||
|
|
if (validateHostname(sanitized)) {
|
||
|
|
return sanitized;
|
||
|
|
}
|
||
|
|
return 'localhost';
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Validate recipient list
|
||
|
|
*/
|
||
|
|
export function validateRecipients(recipients) {
|
||
|
|
const errors = [];
|
||
|
|
const recipientList = Array.isArray(recipients) ? recipients : [recipients];
|
||
|
|
for (const recipient of recipientList) {
|
||
|
|
if (!validateEmailAddress(recipient)) {
|
||
|
|
errors.push(`Invalid email address: ${recipient}`);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return errors;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Validate sender address
|
||
|
|
*/
|
||
|
|
export function validateSender(sender) {
|
||
|
|
return validateEmailAddress(sender);
|
||
|
|
}
|
||
|
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidmFsaWRhdGlvbi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uL3RzL21haWwvZGVsaXZlcnkvc210cGNsaWVudC91dGlscy92YWxpZGF0aW9uLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7R0FHRztBQUVILE9BQU8sRUFBRSxjQUFjLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQUdqRDs7O0dBR0c7QUFDSCxNQUFNLFVBQVUsb0JBQW9CLENBQUMsS0FBYTtJQUNoRCxJQUFJLE9BQU8sS0FBSyxLQUFLLFFBQVEsRUFBRSxDQUFDO1FBQzlCLE9BQU8sS0FBSyxDQUFDO0lBQ2YsQ0FBQztJQUVELE1BQU0sT0FBTyxHQUFHLEtBQUssQ0FBQyxJQUFJLEVBQUUsQ0FBQztJQUU3QiwwREFBMEQ7SUFDMUQsSUFBSSxPQUFPLEtBQUssRUFBRSxJQUFJLE9BQU8sS0FBSyxJQUFJLEVBQUUsQ0FBQztRQUN2QyxPQUFPLElBQUksQ0FBQztJQUNkLENBQUM7SUFFRCw4QkFBOEI7SUFDOUIsTUFBTSxVQUFVLEdBQUcsT0FBTyxDQUFDLEtBQUssQ0FBQyxXQUFXLENBQUMsQ0FBQztJQUM5QyxJQUFJLFVBQVUsRUFBRSxDQUFDO1FBQ2YsT0FBTyxjQUFjLENBQUMsYUFBYSxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUMxRCxDQUFDO0lBRUQsMkJBQTJCO0lBQzNCLE9BQU8sY0FBYyxDQUFDLGFBQWEsQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLENBQUM7QUFDcEQsQ0FBQztBQUVEOztHQUVHO0FBQ0gsTUFBTSxVQUFVLHFCQUFxQixDQUFDLE9BQTJCO0lBQy9ELE1BQU0sTUFBTSxHQUFhLEVBQUUsQ0FBQztJQUU1QixrQkFBa0I7SUFDbEIsSUFBSSxDQUFDLE9BQU8sQ0FBQyxJQUFJLElBQUksT0FBTyxPQUFPLENBQUMsSUFBSSxLQUFLLFFBQVEsRUFBRSxDQUFDO1FBQ3RELE1BQU0sQ0FB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
|