fix(rust-bridge): correct Email.addHeader() calls and IBounceDetection interface
Use addHeader() instead of non-existent setHeader() for security result headers, and align IBounceDetection with actual Rust struct fields (bounce_type + category only).
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@push.rocks/smartmta',
|
||||
version: '1.3.1',
|
||||
version: '2.0.0',
|
||||
description: 'A high-performance, enterprise-grade Mail Transfer Agent (MTA) built from scratch in TypeScript with Rust acceleration.'
|
||||
};
|
||||
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvMDBfY29tbWl0aW5mb19kYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHO0lBQ3hCLElBQUksRUFBRSxvQkFBb0I7SUFDMUIsT0FBTyxFQUFFLE9BQU87SUFDaEIsV0FBVyxFQUFFLHNHQUFzRztDQUNwSCxDQUFBIn0=
|
||||
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvMDBfY29tbWl0aW5mb19kYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHO0lBQ3hCLElBQUksRUFBRSxzQkFBc0I7SUFDNUIsT0FBTyxFQUFFLE9BQU87SUFDaEIsV0FBVyxFQUFFLHlIQUF5SDtDQUN2SSxDQUFBIn0=
|
||||
@@ -134,6 +134,7 @@ export declare class UnifiedEmailServer extends EventEmitter {
|
||||
private servers;
|
||||
private stats;
|
||||
dkimCreator: DKIMCreator;
|
||||
private rustBridge;
|
||||
private ipReputationChecker;
|
||||
private bounceManager;
|
||||
private ipWarmupManager;
|
||||
@@ -163,6 +164,11 @@ export declare class UnifiedEmailServer extends EventEmitter {
|
||||
* Stop the unified email server
|
||||
*/
|
||||
stop(): Promise<void>;
|
||||
/**
|
||||
* Verify inbound email security (DKIM/SPF/DMARC) using the Rust bridge.
|
||||
* Falls back gracefully if the bridge is not running.
|
||||
*/
|
||||
private verifyInboundSecurity;
|
||||
/**
|
||||
* Process email based on routing rules
|
||||
*/
|
||||
|
||||
File diff suppressed because one or more lines are too long
3
dist_ts/plugins.d.ts
vendored
3
dist_ts/plugins.d.ts
vendored
@@ -32,10 +32,11 @@ import * as smartproxy from '@push.rocks/smartproxy';
|
||||
import * as smartpromise from '@push.rocks/smartpromise';
|
||||
import * as smartrequest from '@push.rocks/smartrequest';
|
||||
import * as smartrule from '@push.rocks/smartrule';
|
||||
import * as smartrust from '@push.rocks/smartrust';
|
||||
import * as smartrx from '@push.rocks/smartrx';
|
||||
import * as smartunique from '@push.rocks/smartunique';
|
||||
export declare const smartfs: SmartFs;
|
||||
export { projectinfo, qenv, smartacme, smartdata, smartdns, smartfile, SmartFs, smartguard, smartjwt, smartlog, smartmail, smartmetrics, smartnetwork, smartpath, smartproxy, smartpromise, smartrequest, smartrule, smartrx, smartunique };
|
||||
export { projectinfo, qenv, smartacme, smartdata, smartdns, smartfile, SmartFs, smartguard, smartjwt, smartlog, smartmail, smartmetrics, smartnetwork, smartpath, smartproxy, smartpromise, smartrequest, smartrule, smartrust, smartrx, smartunique };
|
||||
export type TLogLevel = 'error' | 'warn' | 'info' | 'success' | 'debug';
|
||||
import * as cloudflare from '@apiclient.xyz/cloudflare';
|
||||
export { cloudflare, };
|
||||
|
||||
@@ -36,10 +36,11 @@ import * as smartproxy from '@push.rocks/smartproxy';
|
||||
import * as smartpromise from '@push.rocks/smartpromise';
|
||||
import * as smartrequest from '@push.rocks/smartrequest';
|
||||
import * as smartrule from '@push.rocks/smartrule';
|
||||
import * as smartrust from '@push.rocks/smartrust';
|
||||
import * as smartrx from '@push.rocks/smartrx';
|
||||
import * as smartunique from '@push.rocks/smartunique';
|
||||
export const smartfs = new SmartFs(new SmartFsProviderNode());
|
||||
export { projectinfo, qenv, smartacme, smartdata, smartdns, smartfile, SmartFs, smartguard, smartjwt, smartlog, smartmail, smartmetrics, smartnetwork, smartpath, smartproxy, smartpromise, smartrequest, smartrule, smartrx, smartunique };
|
||||
export { projectinfo, qenv, smartacme, smartdata, smartdns, smartfile, SmartFs, smartguard, smartjwt, smartlog, smartmail, smartmetrics, smartnetwork, smartpath, smartproxy, smartpromise, smartrequest, smartrule, smartrust, smartrx, smartunique };
|
||||
// apiclient.xyz scope
|
||||
import * as cloudflare from '@apiclient.xyz/cloudflare';
|
||||
export { cloudflare, };
|
||||
@@ -53,4 +54,4 @@ import mailparser from 'mailparser';
|
||||
import * as uuid from 'uuid';
|
||||
import * as ip from 'ip';
|
||||
export { mailauth, dkimSign, mailparser, uuid, ip, };
|
||||
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGx1Z2lucy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL3BsdWdpbnMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsY0FBYztBQUNkLE9BQU8sS0FBSyxHQUFHLE1BQU0sS0FBSyxDQUFDO0FBQzNCLE9BQU8sS0FBSyxFQUFFLE1BQU0sSUFBSSxDQUFDO0FBQ3pCLE9BQU8sS0FBSyxNQUFNLE1BQU0sUUFBUSxDQUFDO0FBQ2pDLE9BQU8sS0FBSyxJQUFJLE1BQU0sTUFBTSxDQUFDO0FBQzdCLE9BQU8sS0FBSyxHQUFHLE1BQU0sS0FBSyxDQUFDO0FBQzNCLE9BQU8sS0FBSyxFQUFFLE1BQU0sSUFBSSxDQUFDO0FBQ3pCLE9BQU8sS0FBSyxJQUFJLE1BQU0sTUFBTSxDQUFDO0FBQzdCLE9BQU8sS0FBSyxHQUFHLE1BQU0sS0FBSyxDQUFDO0FBQzNCLE9BQU8sS0FBSyxJQUFJLE1BQU0sTUFBTSxDQUFDO0FBRTdCLE9BQU8sRUFDTCxHQUFHLEVBQ0gsRUFBRSxFQUNGLE1BQU0sRUFDTixJQUFJLEVBQ0osR0FBRyxFQUNILEVBQUUsRUFDRixJQUFJLEVBQ0osR0FBRyxFQUNILElBQUksR0FDTCxDQUFBO0FBRUQsb0JBQW9CO0FBQ3BCLE9BQU8sS0FBSyxtQkFBbUIsTUFBTSx3QkFBd0IsQ0FBQztBQUU5RCxPQUFPLEVBQ0wsbUJBQW1CLEVBQ3BCLENBQUE7QUFFRCxvQkFBb0I7QUFDcEIsT0FBTyxLQUFLLFlBQVksTUFBTSwwQkFBMEIsQ0FBQztBQUN6RCxPQUFPLEtBQUssV0FBVyxNQUFNLHlCQUF5QixDQUFDO0FBQ3ZELE9BQU8sS0FBSyxXQUFXLE1BQU0seUJBQXlCLENBQUM7QUFFdkQsT0FBTyxFQUNMLFlBQVksRUFDWixXQUFXLEVBQ1gsV0FBVyxHQUNaLENBQUE7QUFFRCxvQkFBb0I7QUFDcEIsT0FBTyxLQUFLLFdBQVcsTUFBTSx5QkFBeUIsQ0FBQztBQUN2RCxPQUFPLEtBQUssSUFBSSxNQUFNLGtCQUFrQixDQUFDO0FBQ3pDLE9BQU8sS0FBSyxTQUFTLE1BQU0sdUJBQXVCLENBQUM7QUFDbkQsT0FBTyxLQUFLLFNBQVMsTUFBTSx1QkFBdUIsQ0FBQztBQUNuRCxPQUFPLEtBQUssUUFBUSxNQUFNLHNCQUFzQixDQUFDO0FBQ2pELE9BQU8sS0FBSyxTQUFTLE1BQU0sdUJBQXVCLENBQUM7QUFDbkQsT0FBTyxFQUFFLE9BQU8sRUFBRSxtQkFBbUIsRUFBRSxNQUFNLHFCQUFxQixDQUFDO0FBQ25FLE9BQU8sS0FBSyxVQUFVLE1BQU0sd0JBQXdCLENBQUM7QUFDckQsT0FBTyxLQUFLLFFBQVEsTUFBTSxzQkFBc0IsQ0FBQztBQUNqRCxPQUFPLEtBQUssUUFBUSxNQUFNLHNCQUFzQixDQUFDO0FBQ2pELE9BQU8sS0FBSyxTQUFTLE1BQU0sdUJBQXVCLENBQUM7QUFDbkQsT0FBTyxLQUFLLFlBQVksTUFBTSwwQkFBMEIsQ0FBQztBQUN6RCxPQUFPLEtBQUssWUFBWSxNQUFNLDBCQUEwQixDQUFDO0FBQ3pELE9BQU8sS0FBSyxTQUFTLE1BQU0sdUJBQXVCLENBQUM7QUFDbkQsT0FBTyxLQUFLLFVBQVUsTUFBTSx3QkFBd0IsQ0FBQztBQUNyRCxPQUFPLEtBQUssWUFBWSxNQUFNLDBCQUEwQixDQUFDO0FBQ3pELE9BQU8sS0FBSyxZQUFZLE1BQU0sMEJBQTBCLENBQUM7QUFDekQsT0FBTyxLQUFLLFNBQVMsTUFBTSx1QkFBdUIsQ0FBQztBQUNuRCxPQUFPLEtBQUssT0FBTyxNQUFNLHFCQUFxQixDQUFDO0FBQy9DLE9BQU8sS0FBSyxXQUFXLE1BQU0seUJBQXlCLENBQUM7QUFFdkQsTUFBTSxDQUFDLE1BQU0sT0FBTyxHQUFHLElBQUksT0FBTyxDQUFDLElBQUksbUJBQW1CLEVBQUUsQ0FBQyxDQUFDO0FBRTlELE9BQU8sRUFBRSxXQUFXLEVBQUUsSUFBSSxFQUFFLFNBQVMsRUFBRSxTQUFTLEVBQUUsUUFBUSxFQUFFLFNBQVMsRUFBRSxPQUFPLEVBQUUsVUFBVSxFQUFFLFFBQVEsRUFBRSxRQUFRLEVBQUUsU0FBUyxFQUFFLFlBQVksRUFBRSxZQUFZLEVBQUUsU0FBUyxFQUFFLFVBQVUsRUFBRSxZQUFZLEVBQUUsWUFBWSxFQUFFLFNBQVMsRUFBRSxPQUFPLEVBQUUsV0FBVyxFQUFFLENBQUM7QUFLNU8sc0JBQXNCO0FBQ3RCLE9BQU8sS0FBSyxVQUFVLE1BQU0sMkJBQTJCLENBQUM7QUFFeEQsT0FBTyxFQUNMLFVBQVUsR0FDWCxDQUFBO0FBRUQsZ0JBQWdCO0FBQ2hCLE9BQU8sS0FBSyxPQUFPLE1BQU0sa0JBQWtCLENBQUM7QUFFNUMsT0FBTyxFQUNMLE9BQU8sR0FDUixDQUFBO0FBRUQsY0FBYztBQUNkLE9BQU8sS0FBSyxRQUFRLE1BQU0sVUFBVSxDQUFDO0FBQ3JDLE9BQU8sRUFBRSxRQUFRLEVBQUUsTUFBTSwyQkFBMkIsQ0FBQztBQUNyRCxPQUFPLFVBQVUsTUFBTSxZQUFZLENBQUM7QUFDcEMsT0FBTyxLQUFLLElBQUksTUFBTSxNQUFNLENBQUM7QUFDN0IsT0FBTyxLQUFLLEVBQUUsTUFBTSxJQUFJLENBQUM7QUFFekIsT0FBTyxFQUNMLFFBQVEsRUFDUixRQUFRLEVBQ1IsVUFBVSxFQUNWLElBQUksRUFDSixFQUFFLEdBQ0gsQ0FBQSJ9
|
||||
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGx1Z2lucy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL3BsdWdpbnMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsY0FBYztBQUNkLE9BQU8sS0FBSyxHQUFHLE1BQU0sS0FBSyxDQUFDO0FBQzNCLE9BQU8sS0FBSyxFQUFFLE1BQU0sSUFBSSxDQUFDO0FBQ3pCLE9BQU8sS0FBSyxNQUFNLE1BQU0sUUFBUSxDQUFDO0FBQ2pDLE9BQU8sS0FBSyxJQUFJLE1BQU0sTUFBTSxDQUFDO0FBQzdCLE9BQU8sS0FBSyxHQUFHLE1BQU0sS0FBSyxDQUFDO0FBQzNCLE9BQU8sS0FBSyxFQUFFLE1BQU0sSUFBSSxDQUFDO0FBQ3pCLE9BQU8sS0FBSyxJQUFJLE1BQU0sTUFBTSxDQUFDO0FBQzdCLE9BQU8sS0FBSyxHQUFHLE1BQU0sS0FBSyxDQUFDO0FBQzNCLE9BQU8sS0FBSyxJQUFJLE1BQU0sTUFBTSxDQUFDO0FBRTdCLE9BQU8sRUFDTCxHQUFHLEVBQ0gsRUFBRSxFQUNGLE1BQU0sRUFDTixJQUFJLEVBQ0osR0FBRyxFQUNILEVBQUUsRUFDRixJQUFJLEVBQ0osR0FBRyxFQUNILElBQUksR0FDTCxDQUFBO0FBRUQsb0JBQW9CO0FBQ3BCLE9BQU8sS0FBSyxtQkFBbUIsTUFBTSx3QkFBd0IsQ0FBQztBQUU5RCxPQUFPLEVBQ0wsbUJBQW1CLEVBQ3BCLENBQUE7QUFFRCxvQkFBb0I7QUFDcEIsT0FBTyxLQUFLLFlBQVksTUFBTSwwQkFBMEIsQ0FBQztBQUN6RCxPQUFPLEtBQUssV0FBVyxNQUFNLHlCQUF5QixDQUFDO0FBQ3ZELE9BQU8sS0FBSyxXQUFXLE1BQU0seUJBQXlCLENBQUM7QUFFdkQsT0FBTyxFQUNMLFlBQVksRUFDWixXQUFXLEVBQ1gsV0FBVyxHQUNaLENBQUE7QUFFRCxvQkFBb0I7QUFDcEIsT0FBTyxLQUFLLFdBQVcsTUFBTSx5QkFBeUIsQ0FBQztBQUN2RCxPQUFPLEtBQUssSUFBSSxNQUFNLGtCQUFrQixDQUFDO0FBQ3pDLE9BQU8sS0FBSyxTQUFTLE1BQU0sdUJBQXVCLENBQUM7QUFDbkQsT0FBTyxLQUFLLFNBQVMsTUFBTSx1QkFBdUIsQ0FBQztBQUNuRCxPQUFPLEtBQUssUUFBUSxNQUFNLHNCQUFzQixDQUFDO0FBQ2pELE9BQU8sS0FBSyxTQUFTLE1BQU0sdUJBQXVCLENBQUM7QUFDbkQsT0FBTyxFQUFFLE9BQU8sRUFBRSxtQkFBbUIsRUFBRSxNQUFNLHFCQUFxQixDQUFDO0FBQ25FLE9BQU8sS0FBSyxVQUFVLE1BQU0sd0JBQXdCLENBQUM7QUFDckQsT0FBTyxLQUFLLFFBQVEsTUFBTSxzQkFBc0IsQ0FBQztBQUNqRCxPQUFPLEtBQUssUUFBUSxNQUFNLHNCQUFzQixDQUFDO0FBQ2pELE9BQU8sS0FBSyxTQUFTLE1BQU0sdUJBQXVCLENBQUM7QUFDbkQsT0FBTyxLQUFLLFlBQVksTUFBTSwwQkFBMEIsQ0FBQztBQUN6RCxPQUFPLEtBQUssWUFBWSxNQUFNLDBCQUEwQixDQUFDO0FBQ3pELE9BQU8sS0FBSyxTQUFTLE1BQU0sdUJBQXVCLENBQUM7QUFDbkQsT0FBTyxLQUFLLFVBQVUsTUFBTSx3QkFBd0IsQ0FBQztBQUNyRCxPQUFPLEtBQUssWUFBWSxNQUFNLDBCQUEwQixDQUFDO0FBQ3pELE9BQU8sS0FBSyxZQUFZLE1BQU0sMEJBQTBCLENBQUM7QUFDekQsT0FBTyxLQUFLLFNBQVMsTUFBTSx1QkFBdUIsQ0FBQztBQUNuRCxPQUFPLEtBQUssU0FBUyxNQUFNLHVCQUF1QixDQUFDO0FBQ25ELE9BQU8sS0FBSyxPQUFPLE1BQU0scUJBQXFCLENBQUM7QUFDL0MsT0FBTyxLQUFLLFdBQVcsTUFBTSx5QkFBeUIsQ0FBQztBQUV2RCxNQUFNLENBQUMsTUFBTSxPQUFPLEdBQUcsSUFBSSxPQUFPLENBQUMsSUFBSSxtQkFBbUIsRUFBRSxDQUFDLENBQUM7QUFFOUQsT0FBTyxFQUFFLFdBQVcsRUFBRSxJQUFJLEVBQUUsU0FBUyxFQUFFLFNBQVMsRUFBRSxRQUFRLEVBQUUsU0FBUyxFQUFFLE9BQU8sRUFBRSxVQUFVLEVBQUUsUUFBUSxFQUFFLFFBQVEsRUFBRSxTQUFTLEVBQUUsWUFBWSxFQUFFLFlBQVksRUFBRSxTQUFTLEVBQUUsVUFBVSxFQUFFLFlBQVksRUFBRSxZQUFZLEVBQUUsU0FBUyxFQUFFLFNBQVMsRUFBRSxPQUFPLEVBQUUsV0FBVyxFQUFFLENBQUM7QUFLdlAsc0JBQXNCO0FBQ3RCLE9BQU8sS0FBSyxVQUFVLE1BQU0sMkJBQTJCLENBQUM7QUFFeEQsT0FBTyxFQUNMLFVBQVUsR0FDWCxDQUFBO0FBRUQsZ0JBQWdCO0FBQ2hCLE9BQU8sS0FBSyxPQUFPLE1BQU0sa0JBQWtCLENBQUM7QUFFNUMsT0FBTyxFQUNMLE9BQU8sR0FDUixDQUFBO0FBRUQsY0FBYztBQUNkLE9BQU8sS0FBSyxRQUFRLE1BQU0sVUFBVSxDQUFDO0FBQ3JDLE9BQU8sRUFBRSxRQUFRLEVBQUUsTUFBTSwyQkFBMkIsQ0FBQztBQUNyRCxPQUFPLFVBQVUsTUFBTSxZQUFZLENBQUM7QUFDcEMsT0FBTyxLQUFLLElBQUksTUFBTSxNQUFNLENBQUM7QUFDN0IsT0FBTyxLQUFLLEVBQUUsTUFBTSxJQUFJLENBQUM7QUFFekIsT0FBTyxFQUNMLFFBQVEsRUFDUixRQUFRLEVBQ1IsVUFBVSxFQUNWLElBQUksRUFDSixFQUFFLEdBQ0gsQ0FBQSJ9
|
||||
File diff suppressed because one or more lines are too long
126
dist_ts/security/classes.rustsecuritybridge.d.ts
vendored
Normal file
126
dist_ts/security/classes.rustsecuritybridge.d.ts
vendored
Normal file
@@ -0,0 +1,126 @@
|
||||
interface IDkimVerificationResult {
|
||||
is_valid: boolean;
|
||||
domain: string | null;
|
||||
selector: string | null;
|
||||
status: string;
|
||||
details: string | null;
|
||||
}
|
||||
interface ISpfResult {
|
||||
result: string;
|
||||
domain: string;
|
||||
ip: string;
|
||||
explanation: string | null;
|
||||
}
|
||||
interface IDmarcResult {
|
||||
passed: boolean;
|
||||
policy: string;
|
||||
domain: string;
|
||||
dkim_result: string;
|
||||
spf_result: string;
|
||||
action: string;
|
||||
details: string | null;
|
||||
}
|
||||
interface IEmailSecurityResult {
|
||||
dkim: IDkimVerificationResult[];
|
||||
spf: ISpfResult | null;
|
||||
dmarc: IDmarcResult | null;
|
||||
}
|
||||
interface IValidationResult {
|
||||
valid: boolean;
|
||||
formatValid: boolean;
|
||||
score: number;
|
||||
error: string | null;
|
||||
}
|
||||
interface IBounceDetection {
|
||||
bounce_type: string;
|
||||
category: string;
|
||||
}
|
||||
interface IReputationResult {
|
||||
ip: string;
|
||||
score: number;
|
||||
risk_level: string;
|
||||
ip_type: string;
|
||||
dnsbl_results: Array<{
|
||||
server: string;
|
||||
listed: boolean;
|
||||
response: string | null;
|
||||
}>;
|
||||
listed_count: number;
|
||||
total_checked: number;
|
||||
}
|
||||
interface IVersionInfo {
|
||||
bin: string;
|
||||
core: string;
|
||||
security: string;
|
||||
smtp: string;
|
||||
}
|
||||
/**
|
||||
* Bridge between TypeScript and the Rust `mailer-bin` binary.
|
||||
*
|
||||
* Uses `@push.rocks/smartrust` for JSON-over-stdin/stdout IPC.
|
||||
* Singleton — access via `RustSecurityBridge.getInstance()`.
|
||||
*/
|
||||
export declare class RustSecurityBridge {
|
||||
private static instance;
|
||||
private bridge;
|
||||
private _running;
|
||||
private constructor();
|
||||
/** Get or create the singleton instance. */
|
||||
static getInstance(): RustSecurityBridge;
|
||||
/** Whether the Rust process is currently running and accepting commands. */
|
||||
get running(): boolean;
|
||||
/**
|
||||
* Spawn the Rust binary and wait for the ready signal.
|
||||
* @returns `true` if the binary started successfully, `false` otherwise.
|
||||
*/
|
||||
start(): Promise<boolean>;
|
||||
/** Kill the Rust process. */
|
||||
stop(): Promise<void>;
|
||||
/** Ping the Rust process. */
|
||||
ping(): Promise<boolean>;
|
||||
/** Get version information for all Rust crates. */
|
||||
getVersion(): Promise<IVersionInfo>;
|
||||
/** Validate an email address. */
|
||||
validateEmail(email: string): Promise<IValidationResult>;
|
||||
/** Detect bounce type from SMTP response / diagnostic code. */
|
||||
detectBounce(opts: {
|
||||
smtpResponse?: string;
|
||||
diagnosticCode?: string;
|
||||
statusCode?: string;
|
||||
}): Promise<IBounceDetection>;
|
||||
/** Check IP reputation via DNSBL. */
|
||||
checkIpReputation(ip: string): Promise<IReputationResult>;
|
||||
/** Verify DKIM signatures on a raw email message. */
|
||||
verifyDkim(rawMessage: string): Promise<IDkimVerificationResult[]>;
|
||||
/** Sign an email with DKIM. */
|
||||
signDkim(opts: {
|
||||
rawMessage: string;
|
||||
domain: string;
|
||||
selector?: string;
|
||||
privateKey: string;
|
||||
}): Promise<{
|
||||
header: string;
|
||||
signedMessage: string;
|
||||
}>;
|
||||
/** Check SPF for a sender. */
|
||||
checkSpf(opts: {
|
||||
ip: string;
|
||||
heloDomain: string;
|
||||
hostname?: string;
|
||||
mailFrom: string;
|
||||
}): Promise<ISpfResult>;
|
||||
/**
|
||||
* Compound email security verification: DKIM + SPF + DMARC in one IPC call.
|
||||
*
|
||||
* This is the preferred method for inbound email verification — it avoids
|
||||
* 3 sequential round-trips and correctly passes raw mail-auth types internally.
|
||||
*/
|
||||
verifyEmail(opts: {
|
||||
rawMessage: string;
|
||||
ip: string;
|
||||
heloDomain: string;
|
||||
hostname?: string;
|
||||
mailFrom: string;
|
||||
}): Promise<IEmailSecurityResult>;
|
||||
}
|
||||
export type { IDkimVerificationResult, ISpfResult, IDmarcResult, IEmailSecurityResult, IValidationResult, IBounceDetection, IReputationResult as IRustReputationResult, IVersionInfo, };
|
||||
141
dist_ts/security/classes.rustsecuritybridge.js
Normal file
141
dist_ts/security/classes.rustsecuritybridge.js
Normal file
File diff suppressed because one or more lines are too long
1
dist_ts/security/index.d.ts
vendored
1
dist_ts/security/index.d.ts
vendored
@@ -1,3 +1,4 @@
|
||||
export { SecurityLogger, SecurityLogLevel, SecurityEventType, type ISecurityEvent } from './classes.securitylogger.js';
|
||||
export { IPReputationChecker, ReputationThreshold, IPType, type IReputationResult, type IIPReputationOptions } from './classes.ipreputationchecker.js';
|
||||
export { ContentScanner, ThreatCategory, type IScanResult, type IContentScannerOptions } from './classes.contentscanner.js';
|
||||
export { RustSecurityBridge, type IDkimVerificationResult, type ISpfResult, type IDmarcResult, type IEmailSecurityResult, type IValidationResult, type IBounceDetection, type IRustReputationResult, type IVersionInfo, } from './classes.rustsecuritybridge.js';
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export { SecurityLogger, SecurityLogLevel, SecurityEventType } from './classes.securitylogger.js';
|
||||
export { IPReputationChecker, ReputationThreshold, IPType } from './classes.ipreputationchecker.js';
|
||||
export { ContentScanner, ThreatCategory } from './classes.contentscanner.js';
|
||||
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9zZWN1cml0eS9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQ0wsY0FBYyxFQUNkLGdCQUFnQixFQUNoQixpQkFBaUIsRUFFbEIsTUFBTSw2QkFBNkIsQ0FBQztBQUVyQyxPQUFPLEVBQ0wsbUJBQW1CLEVBQ25CLG1CQUFtQixFQUNuQixNQUFNLEVBR1AsTUFBTSxrQ0FBa0MsQ0FBQztBQUUxQyxPQUFPLEVBQ0wsY0FBYyxFQUNkLGNBQWMsRUFHZixNQUFNLDZCQUE2QixDQUFDIn0=
|
||||
export { RustSecurityBridge, } from './classes.rustsecuritybridge.js';
|
||||
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9zZWN1cml0eS9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQ0wsY0FBYyxFQUNkLGdCQUFnQixFQUNoQixpQkFBaUIsRUFFbEIsTUFBTSw2QkFBNkIsQ0FBQztBQUVyQyxPQUFPLEVBQ0wsbUJBQW1CLEVBQ25CLG1CQUFtQixFQUNuQixNQUFNLEVBR1AsTUFBTSxrQ0FBa0MsQ0FBQztBQUUxQyxPQUFPLEVBQ0wsY0FBYyxFQUNkLGNBQWMsRUFHZixNQUFNLDZCQUE2QixDQUFDO0FBRXJDLE9BQU8sRUFDTCxrQkFBa0IsR0FTbkIsTUFBTSxpQ0FBaUMsQ0FBQyJ9
|
||||
136
test/test.rustsecuritybridge.node.ts
Normal file
136
test/test.rustsecuritybridge.node.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||
import { RustSecurityBridge } from '../ts/security/classes.rustsecuritybridge.js';
|
||||
|
||||
let bridge: RustSecurityBridge;
|
||||
|
||||
tap.test('RustSecurityBridge - should get singleton instance', async () => {
|
||||
bridge = RustSecurityBridge.getInstance();
|
||||
expect(bridge).toBeTruthy();
|
||||
expect(bridge).toEqual(RustSecurityBridge.getInstance()); // same instance
|
||||
});
|
||||
|
||||
tap.test('RustSecurityBridge - should start the Rust binary', async () => {
|
||||
const ok = await bridge.start();
|
||||
if (!ok) {
|
||||
console.log('WARNING: Rust binary not available — skipping bridge tests');
|
||||
console.log('Build it with: cd rust && cargo build --release');
|
||||
}
|
||||
// We accept both true and false — the binary may not be built yet
|
||||
expect(typeof ok).toEqual('boolean');
|
||||
});
|
||||
|
||||
tap.test('RustSecurityBridge - ping should return pong', async () => {
|
||||
if (!bridge.running) {
|
||||
console.log('SKIP: bridge not running');
|
||||
return;
|
||||
}
|
||||
const pong = await bridge.ping();
|
||||
expect(pong).toBeTrue();
|
||||
});
|
||||
|
||||
tap.test('RustSecurityBridge - version should return crate versions', async () => {
|
||||
if (!bridge.running) {
|
||||
console.log('SKIP: bridge not running');
|
||||
return;
|
||||
}
|
||||
const version = await bridge.getVersion();
|
||||
expect(version.bin).toBeTruthy();
|
||||
expect(version.core).toBeTruthy();
|
||||
expect(version.security).toBeTruthy();
|
||||
});
|
||||
|
||||
tap.test('RustSecurityBridge - validateEmail with valid address', async () => {
|
||||
if (!bridge.running) {
|
||||
console.log('SKIP: bridge not running');
|
||||
return;
|
||||
}
|
||||
const result = await bridge.validateEmail('test@example.com');
|
||||
expect(result.valid).toBeTrue();
|
||||
expect(result.formatValid).toBeTrue();
|
||||
});
|
||||
|
||||
tap.test('RustSecurityBridge - validateEmail with invalid address', async () => {
|
||||
if (!bridge.running) {
|
||||
console.log('SKIP: bridge not running');
|
||||
return;
|
||||
}
|
||||
const result = await bridge.validateEmail('not-an-email');
|
||||
expect(result.formatValid).toBeFalse();
|
||||
});
|
||||
|
||||
tap.test('RustSecurityBridge - detectBounce with known SMTP response', async () => {
|
||||
if (!bridge.running) {
|
||||
console.log('SKIP: bridge not running');
|
||||
return;
|
||||
}
|
||||
const result = await bridge.detectBounce({
|
||||
smtpResponse: '550 5.1.1 User unknown',
|
||||
});
|
||||
expect(result.bounce_type).toBeTruthy();
|
||||
expect(result.category).toBeTruthy();
|
||||
});
|
||||
|
||||
tap.test('RustSecurityBridge - checkIpReputation', async () => {
|
||||
if (!bridge.running) {
|
||||
console.log('SKIP: bridge not running');
|
||||
return;
|
||||
}
|
||||
// Use a well-known IP that should NOT be on blacklists
|
||||
const result = await bridge.checkIpReputation('8.8.8.8');
|
||||
expect(result.ip).toEqual('8.8.8.8');
|
||||
expect(typeof result.score).toEqual('number');
|
||||
expect(result.score).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
tap.test('RustSecurityBridge - verifyDkim with unsigned message', async () => {
|
||||
if (!bridge.running) {
|
||||
console.log('SKIP: bridge not running');
|
||||
return;
|
||||
}
|
||||
const rawMessage = 'From: test@example.com\r\nTo: receiver@example.com\r\nSubject: Test\r\n\r\nHello';
|
||||
const results = await bridge.verifyDkim(rawMessage);
|
||||
expect(results.length).toBeGreaterThan(0);
|
||||
expect(results[0].status).toEqual('none'); // no DKIM signature
|
||||
});
|
||||
|
||||
tap.test('RustSecurityBridge - verifyEmail compound call', async () => {
|
||||
if (!bridge.running) {
|
||||
console.log('SKIP: bridge not running');
|
||||
return;
|
||||
}
|
||||
const rawMessage = 'From: test@example.com\r\nTo: receiver@example.com\r\nSubject: Test\r\n\r\nHello';
|
||||
const result = await bridge.verifyEmail({
|
||||
rawMessage,
|
||||
ip: '93.184.216.34', // example.com IP
|
||||
heloDomain: 'example.com',
|
||||
hostname: 'mail.test.local',
|
||||
mailFrom: 'test@example.com',
|
||||
});
|
||||
expect(result.dkim).toBeTruthy();
|
||||
expect(result.dkim.length).toBeGreaterThan(0);
|
||||
expect(result.spf).toBeTruthy();
|
||||
// DMARC may or may not be present depending on DNS
|
||||
});
|
||||
|
||||
tap.test('RustSecurityBridge - should stop gracefully', async () => {
|
||||
if (!bridge.running) {
|
||||
console.log('SKIP: bridge not running');
|
||||
return;
|
||||
}
|
||||
await bridge.stop();
|
||||
expect(bridge.running).toBeFalse();
|
||||
});
|
||||
|
||||
tap.test('RustSecurityBridge - commands should fail when bridge is stopped', async () => {
|
||||
// Bridge should not be running now
|
||||
expect(bridge.running).toBeFalse();
|
||||
try {
|
||||
await bridge.ping();
|
||||
// If we get here, the bridge auto-restarted or something unexpected
|
||||
expect(true).toBeFalse(); // Should not reach here
|
||||
} catch (err) {
|
||||
expect(err).toBeTruthy(); // Expected: bridge not running error
|
||||
}
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
@@ -656,12 +656,12 @@ export class UnifiedEmailServer extends EventEmitter {
|
||||
const dkimSummary = result.dkim
|
||||
.map(d => `${d.status}${d.domain ? ` (${d.domain})` : ''}`)
|
||||
.join(', ');
|
||||
email.setHeader('X-DKIM-Result', dkimSummary);
|
||||
email.addHeader('X-DKIM-Result', dkimSummary);
|
||||
}
|
||||
|
||||
// Apply SPF result header
|
||||
if (result.spf) {
|
||||
email.setHeader('Received-SPF', `${result.spf.result} (domain: ${result.spf.domain}, ip: ${result.spf.ip})`);
|
||||
email.addHeader('Received-SPF', `${result.spf.result} (domain: ${result.spf.domain}, ip: ${result.spf.ip})`);
|
||||
|
||||
// Mark as spam on SPF hard fail
|
||||
if (result.spf.result === 'fail') {
|
||||
@@ -672,7 +672,7 @@ export class UnifiedEmailServer extends EventEmitter {
|
||||
|
||||
// Apply DMARC result header and policy
|
||||
if (result.dmarc) {
|
||||
email.setHeader('X-DMARC-Result', `${result.dmarc.action} (policy=${result.dmarc.policy}, dkim=${result.dmarc.dkim_result}, spf=${result.dmarc.spf_result})`);
|
||||
email.addHeader('X-DMARC-Result', `${result.dmarc.action} (policy=${result.dmarc.policy}, dkim=${result.dmarc.dkim_result}, spf=${result.dmarc.spf_result})`);
|
||||
|
||||
if (result.dmarc.action === 'reject') {
|
||||
email.mightBeSpam = true;
|
||||
|
||||
@@ -46,11 +46,7 @@ interface IValidationResult {
|
||||
|
||||
interface IBounceDetection {
|
||||
bounce_type: string;
|
||||
severity: string;
|
||||
category: string;
|
||||
should_retry: boolean;
|
||||
recommended_action: string;
|
||||
details: string;
|
||||
}
|
||||
|
||||
interface IReputationResult {
|
||||
|
||||
Reference in New Issue
Block a user