feat(mailer-smtp): add SCRAM-SHA-256 auth, Ed25519 DKIM, opportunistic TLS, SNI cert selection, pipelining and delivery/bridge improvements

This commit is contained in:
2026-02-11 10:11:43 +00:00
parent 7908cbaefa
commit b10597fd5e
28 changed files with 1849 additions and 153 deletions

View File

@@ -95,11 +95,12 @@ interface ISmtpSendOptions {
domain?: string;
auth?: { user: string; pass: string; method?: string };
email: IOutboundEmail;
dkim?: { domain: string; selector: string; privateKey: string };
dkim?: { domain: string; selector: string; privateKey: string; keyType?: string };
connectionTimeoutSecs?: number;
socketTimeoutSecs?: number;
poolKey?: string;
maxPoolConnections?: number;
tlsOpportunistic?: boolean;
}
interface ISmtpSendRawOptions {
@@ -147,6 +148,7 @@ interface ISmtpServerConfig {
securePort?: number;
tlsCertPem?: string;
tlsKeyPem?: string;
additionalTlsCerts?: Array<{ domains: string[]; certPem: string; keyPem: string }>;
maxMessageSize?: number;
maxConnections?: number;
maxRecipients?: number;
@@ -193,6 +195,13 @@ interface IAuthRequestEvent {
remoteAddr: string;
}
interface IScramCredentialRequestEvent {
correlationId: string;
sessionId: string;
username: string;
remoteAddr: string;
}
/**
* Type-safe command map for the mailer-bin IPC bridge.
*/
@@ -222,7 +231,7 @@ type TMailerCommands = {
result: IDkimVerificationResult[];
};
signDkim: {
params: { rawMessage: string; domain: string; selector?: string; privateKey: string };
params: { rawMessage: string; domain: string; selector?: string; privateKey: string; keyType?: string };
result: { header: string; signedMessage: string };
};
checkSpf: {
@@ -273,6 +282,17 @@ type TMailerCommands = {
};
result: { resolved: boolean };
};
scramCredentialResult: {
params: {
correlationId: string;
found: boolean;
salt?: string;
iterations?: number;
storedKey?: string;
serverKey?: string;
};
result: { resolved: boolean };
};
configureRateLimits: {
params: IRateLimitConfig;
result: { configured: boolean };
@@ -706,12 +726,13 @@ export class RustSecurityBridge extends EventEmitter {
return this.bridge.sendCommand('verifyDkim', { rawMessage });
}
/** Sign an email with DKIM. */
/** Sign an email with DKIM (RSA or Ed25519). */
public async signDkim(opts: {
rawMessage: string;
domain: string;
selector?: string;
privateKey: string;
keyType?: string;
}): Promise<{ header: string; signedMessage: string }> {
this.ensureRunning();
return this.bridge.sendCommand('signDkim', opts);
@@ -829,6 +850,22 @@ export class RustSecurityBridge extends EventEmitter {
await this.bridge.sendCommand('authResult', opts);
}
/**
* Send SCRAM credentials back to the Rust SMTP server.
* Values (salt, storedKey, serverKey) must be base64-encoded.
*/
public async sendScramCredentialResult(opts: {
correlationId: string;
found: boolean;
salt?: string;
iterations?: number;
storedKey?: string;
serverKey?: string;
}): Promise<void> {
this.ensureRunning();
await this.bridge.sendCommand('scramCredentialResult', opts);
}
/** Update rate limit configuration at runtime. */
public async configureRateLimits(config: IRateLimitConfig): Promise<void> {
this.ensureRunning();
@@ -855,6 +892,14 @@ export class RustSecurityBridge extends EventEmitter {
this.bridge.on('management:authRequest', handler);
}
/**
* Register a handler for scramCredentialRequest events from the Rust SMTP server.
* The handler must call sendScramCredentialResult() with the correlationId.
*/
public onScramCredentialRequest(handler: (data: IScramCredentialRequestEvent) => void): void {
this.bridge.on('management:scramCredentialRequest', handler);
}
/** Remove an emailReceived event handler. */
public offEmailReceived(handler: (data: IEmailReceivedEvent) => void): void {
this.bridge.off('management:emailReceived', handler);
@@ -864,6 +909,11 @@ export class RustSecurityBridge extends EventEmitter {
public offAuthRequest(handler: (data: IAuthRequestEvent) => void): void {
this.bridge.off('management:authRequest', handler);
}
/** Remove a scramCredentialRequest event handler. */
public offScramCredentialRequest(handler: (data: IScramCredentialRequestEvent) => void): void {
this.bridge.off('management:scramCredentialRequest', handler);
}
}
// Re-export interfaces for consumers
@@ -882,6 +932,7 @@ export type {
IEmailData,
IEmailReceivedEvent,
IAuthRequestEvent,
IScramCredentialRequestEvent,
IOutboundEmail,
ISmtpSendResult,
ISmtpSendOptions,