import type { PolicyConfig } from "./config.js";
import { ToolError } from "./errors.js";
import { normalizeAddress, validateEmailAddress } from "./utils/email.js";
export interface NormalizedRecipients {
readonly to: readonly string[];
readonly cc: readonly string[];
readonly bcc: readonly string[];
}
/**
* Normalize and validate recipient lists.
*/
export function normalizeRecipients(input: {
to: string | readonly string[];
cc?: string | readonly string[];
bcc?: string | readonly string[];
}): NormalizedRecipients {
const normalizeList = (value?: string | readonly string[]): string[] => {
if (!value) {
return [];
}
const list: readonly string[] = Array.isArray(value) ? value : [value];
return list.map((entry) => entry.trim()).filter((entry) => entry.length > 0);
};
const toList = normalizeList(input.to);
const ccList = normalizeList(input.cc);
const bccList = normalizeList(input.bcc);
const all = [...toList, ...ccList, ...bccList];
if (all.length === 0) {
throw new ToolError("VALIDATION_ERROR", "At least one recipient is required.");
}
all.forEach((address) => {
if (!validateEmailAddress(address)) {
throw new ToolError("VALIDATION_ERROR", `Invalid email address: ${address}`);
}
});
return {
to: toList.map(normalizeAddress),
cc: ccList.map(normalizeAddress),
bcc: bccList.map(normalizeAddress),
};
}
/**
* Enforce recipient limits and allowlist policy.
*/
export function enforceRecipientPolicy(
policy: PolicyConfig,
recipients: NormalizedRecipients,
): void {
const all = [...recipients.to, ...recipients.cc, ...recipients.bcc];
if (all.length > policy.maxRecipients) {
throw new ToolError(
"POLICY_VIOLATION",
`Recipient limit exceeded (max ${policy.maxRecipients}).`,
);
}
const hasAllowlist = policy.allowlistAddresses.length > 0 || policy.allowlistDomains.length > 0;
if (!hasAllowlist) {
return;
}
all.forEach((address) => {
const [localPart, domain] = address.split("@");
if (!localPart || !domain) {
throw new ToolError("POLICY_VIOLATION", `Invalid email address: ${address}`);
}
const allowedByAddress = policy.allowlistAddresses.includes(address);
const allowedByDomain = policy.allowlistDomains.includes(domain);
if (!allowedByAddress && !allowedByDomain) {
throw new ToolError("POLICY_VIOLATION", `Recipient blocked by allowlist: ${address}`);
}
});
}