141 lines
10 KiB
JavaScript
141 lines
10 KiB
JavaScript
|
|
import * as plugins from '../plugins.js';
|
||
|
|
import * as paths from '../paths.js';
|
||
|
|
import { logger } from '../logger.js';
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
// RustSecurityBridge — singleton wrapper around smartrust.RustBridge
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
/**
|
||
|
|
* 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 class RustSecurityBridge {
|
||
|
|
static instance = null;
|
||
|
|
bridge;
|
||
|
|
_running = false;
|
||
|
|
constructor() {
|
||
|
|
this.bridge = new plugins.smartrust.RustBridge({
|
||
|
|
binaryName: 'mailer-bin',
|
||
|
|
cliArgs: ['--management'],
|
||
|
|
requestTimeoutMs: 30_000,
|
||
|
|
readyTimeoutMs: 10_000,
|
||
|
|
localPaths: [
|
||
|
|
plugins.path.join(paths.packageDir, 'dist_rust', 'mailer-bin'),
|
||
|
|
plugins.path.join(paths.packageDir, 'rust', 'target', 'release', 'mailer-bin'),
|
||
|
|
plugins.path.join(paths.packageDir, 'rust', 'target', 'debug', 'mailer-bin'),
|
||
|
|
],
|
||
|
|
searchSystemPath: false,
|
||
|
|
});
|
||
|
|
// Forward lifecycle events
|
||
|
|
this.bridge.on('ready', () => {
|
||
|
|
this._running = true;
|
||
|
|
logger.log('info', 'Rust security bridge is ready');
|
||
|
|
});
|
||
|
|
this.bridge.on('exit', (code, signal) => {
|
||
|
|
this._running = false;
|
||
|
|
logger.log('warn', `Rust security bridge exited (code=${code}, signal=${signal})`);
|
||
|
|
});
|
||
|
|
this.bridge.on('stderr', (line) => {
|
||
|
|
logger.log('debug', `[rust-bridge] ${line}`);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
/** Get or create the singleton instance. */
|
||
|
|
static getInstance() {
|
||
|
|
if (!RustSecurityBridge.instance) {
|
||
|
|
RustSecurityBridge.instance = new RustSecurityBridge();
|
||
|
|
}
|
||
|
|
return RustSecurityBridge.instance;
|
||
|
|
}
|
||
|
|
/** Whether the Rust process is currently running and accepting commands. */
|
||
|
|
get running() {
|
||
|
|
return this._running;
|
||
|
|
}
|
||
|
|
// -----------------------------------------------------------------------
|
||
|
|
// Lifecycle
|
||
|
|
// -----------------------------------------------------------------------
|
||
|
|
/**
|
||
|
|
* Spawn the Rust binary and wait for the ready signal.
|
||
|
|
* @returns `true` if the binary started successfully, `false` otherwise.
|
||
|
|
*/
|
||
|
|
async start() {
|
||
|
|
if (this._running) {
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
try {
|
||
|
|
const ok = await this.bridge.spawn();
|
||
|
|
this._running = ok;
|
||
|
|
if (ok) {
|
||
|
|
logger.log('info', 'Rust security bridge started');
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
logger.log('warn', 'Rust security bridge failed to start (binary not found or timeout)');
|
||
|
|
}
|
||
|
|
return ok;
|
||
|
|
}
|
||
|
|
catch (err) {
|
||
|
|
logger.log('error', `Failed to start Rust security bridge: ${err.message}`);
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
/** Kill the Rust process. */
|
||
|
|
async stop() {
|
||
|
|
if (!this._running) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
try {
|
||
|
|
this.bridge.kill();
|
||
|
|
this._running = false;
|
||
|
|
logger.log('info', 'Rust security bridge stopped');
|
||
|
|
}
|
||
|
|
catch (err) {
|
||
|
|
logger.log('error', `Error stopping Rust security bridge: ${err.message}`);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
// -----------------------------------------------------------------------
|
||
|
|
// Commands — thin typed wrappers over sendCommand
|
||
|
|
// -----------------------------------------------------------------------
|
||
|
|
/** Ping the Rust process. */
|
||
|
|
async ping() {
|
||
|
|
const res = await this.bridge.sendCommand('ping', {});
|
||
|
|
return res?.pong === true;
|
||
|
|
}
|
||
|
|
/** Get version information for all Rust crates. */
|
||
|
|
async getVersion() {
|
||
|
|
return this.bridge.sendCommand('version', {});
|
||
|
|
}
|
||
|
|
/** Validate an email address. */
|
||
|
|
async validateEmail(email) {
|
||
|
|
return this.bridge.sendCommand('validateEmail', { email });
|
||
|
|
}
|
||
|
|
/** Detect bounce type from SMTP response / diagnostic code. */
|
||
|
|
async detectBounce(opts) {
|
||
|
|
return this.bridge.sendCommand('detectBounce', opts);
|
||
|
|
}
|
||
|
|
/** Check IP reputation via DNSBL. */
|
||
|
|
async checkIpReputation(ip) {
|
||
|
|
return this.bridge.sendCommand('checkIpReputation', { ip });
|
||
|
|
}
|
||
|
|
/** Verify DKIM signatures on a raw email message. */
|
||
|
|
async verifyDkim(rawMessage) {
|
||
|
|
return this.bridge.sendCommand('verifyDkim', { rawMessage });
|
||
|
|
}
|
||
|
|
/** Sign an email with DKIM. */
|
||
|
|
async signDkim(opts) {
|
||
|
|
return this.bridge.sendCommand('signDkim', opts);
|
||
|
|
}
|
||
|
|
/** Check SPF for a sender. */
|
||
|
|
async checkSpf(opts) {
|
||
|
|
return this.bridge.sendCommand('checkSpf', opts);
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* 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.
|
||
|
|
*/
|
||
|
|
async verifyEmail(opts) {
|
||
|
|
return this.bridge.sendCommand('verifyEmail', opts);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5ydXN0c2VjdXJpdHlicmlkZ2UuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9zZWN1cml0eS9jbGFzc2VzLnJ1c3RzZWN1cml0eWJyaWRnZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGVBQWUsQ0FBQztBQUN6QyxPQUFPLEtBQUssS0FBSyxNQUFNLGFBQWEsQ0FBQztBQUNyQyxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sY0FBYyxDQUFDO0FBa0h0Qyw4RUFBOEU7QUFDOUUscUVBQXFFO0FBQ3JFLDhFQUE4RTtBQUU5RTs7Ozs7R0FLRztBQUNILE1BQU0sT0FBTyxrQkFBa0I7SUFDckIsTUFBTSxDQUFDLFFBQVEsR0FBOEIsSUFBSSxDQUFDO0lBRWxELE1BQU0sQ0FBcUU7SUFDM0UsUUFBUSxHQUFHLEtBQUssQ0FBQztJQUV6QjtRQUNFLElBQUksQ0FBQyxNQUFNLEdBQUcsSUFBSSxPQUFPLENBQUMsU0FBUyxDQUFDLFVBQVUsQ0FBa0I7WUFDOUQsVUFBVSxFQUFFLFlBQVk7WUFDeEIsT0FBTyxFQUFFLENBQUMsY0FBYyxDQUFDO1lBQ3pCLGdCQUFnQixFQUFFLE1BQU07WUFDeEIsY0FBYyxFQUFFLE1BQU07WUFDdEIsVUFBVSxFQUFFO2dCQUNWLE9BQU8sQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxVQUFVLEVBQUUsV0FBVyxFQUFFLFlBQVksQ0FBQztnQkFDOUQsT0FBTyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLFVBQVUsRUFBRSxNQUFNLEVBQUUsUUFBUSxFQUFFLFNBQVMsRUFBRSxZQUFZLENBQUM7Z0JBQzlFLE9BQU8sQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxVQUFVLEVBQUUsTUFBTSxFQUFFLFFBQVEsRUFBRSxPQUFPLEVBQUUsWUFBWSxDQUFDO2FBQzdFO1lBQ0QsZ0JBQWdCLEVBQUUsS0FBSztT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
|