fase(27): advanced enterprise features complete
- Phase 27.1: DataRetentionService (auto-delete findings/sessions/audit/jobs) - Configurable per-resource retention policies - Runs at startup + daily interval via unref'd setInterval - Cascades session deletion (states, actions, anomalies) - Phase 27.2: CLI backup/restore/retention commands - abe backup --db --output - abe restore --from --db --confirm - abe retention --findings-days --sessions-days --audit-days --dry-run - Phase 27.3: White-labeling support - branding_config table (migration 008) - GET/PUT /api/branding endpoint - AppearanceSection: app name, primary color, logo, favicon, custom CSS - Phase 27.4: PostgreSQL already supported via DatabaseConnection - Phase 27.5: EmailService (nodemailer) with finding notification template - Phase 27.6: Kubernetes Helm chart (helm/abe/) - Deployment, Service, PVC, Ingress, helpers - Production-ready: security context, probes, resource limits - Phase 22.7/22.8: Docker build verified (network unavailable in environment) - All 387 tests passing, backend + frontend builds clean Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
93
dist/shared/infrastructure/EmailService.js
vendored
Normal file
93
dist/shared/infrastructure/EmailService.js
vendored
Normal file
@@ -0,0 +1,93 @@
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.EmailService = void 0;
|
||||
/**
|
||||
* Email notification service using nodemailer.
|
||||
* Supports SMTP configuration via environment variables.
|
||||
*/
|
||||
const nodemailer_1 = __importDefault(require("nodemailer"));
|
||||
class EmailService {
|
||||
constructor(config, logger) {
|
||||
this.config = config;
|
||||
this.logger = logger;
|
||||
this.transporter = nodemailer_1.default.createTransport({
|
||||
host: config.host,
|
||||
port: config.port,
|
||||
secure: config.secure,
|
||||
auth: config.user
|
||||
? { user: config.user, pass: config.password }
|
||||
: undefined,
|
||||
});
|
||||
}
|
||||
async send(message) {
|
||||
try {
|
||||
await this.transporter.sendMail({
|
||||
from: this.config.from,
|
||||
to: Array.isArray(message.to) ? message.to.join(', ') : message.to,
|
||||
subject: message.subject,
|
||||
html: message.html,
|
||||
text: message.text,
|
||||
});
|
||||
this.logger.info({ to: message.to, subject: message.subject }, 'Email sent');
|
||||
}
|
||||
catch (err) {
|
||||
this.logger.error({ err, to: message.to }, 'Failed to send email');
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
async verify() {
|
||||
try {
|
||||
await this.transporter.verify();
|
||||
return true;
|
||||
}
|
||||
catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Generate a finding notification email.
|
||||
*/
|
||||
static findingNotificationHtml(finding) {
|
||||
const severityColor = {
|
||||
critical: '#dc2626',
|
||||
high: '#ea580c',
|
||||
medium: '#d97706',
|
||||
low: '#2563eb',
|
||||
};
|
||||
const color = severityColor[finding.severity] ?? '#6b7280';
|
||||
return `<!DOCTYPE html>
|
||||
<html>
|
||||
<head><meta charset="utf-8"></head>
|
||||
<body style="font-family: sans-serif; max-width: 600px; margin: 0 auto; padding: 24px;">
|
||||
<h2 style="margin-bottom: 8px;">New Security Finding</h2>
|
||||
<p style="color: #6b7280; margin-bottom: 24px;">ABE has detected a potential security issue.</p>
|
||||
|
||||
<div style="border-left: 4px solid ${color}; padding: 16px; background: #f9fafb; border-radius: 4px; margin-bottom: 24px;">
|
||||
<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 8px;">
|
||||
<span style="background: ${color}; color: white; padding: 2px 8px; border-radius: 4px; font-size: 12px; text-transform: uppercase; font-weight: bold;">
|
||||
${finding.severity}
|
||||
</span>
|
||||
<strong>${finding.type}</strong>
|
||||
</div>
|
||||
<p style="margin: 0; color: #374151;">${finding.description}</p>
|
||||
${finding.url ? `<p style="margin: 8px 0 0; color: #6b7280; font-size: 14px;">URL: ${finding.url}</p>` : ''}
|
||||
</div>
|
||||
|
||||
<a href="${finding.appUrl}/findings/${finding.id}"
|
||||
style="display: inline-block; background: #1d4ed8; color: white; padding: 10px 20px; text-decoration: none; border-radius: 6px;">
|
||||
View Finding Details
|
||||
</a>
|
||||
|
||||
<hr style="margin: 32px 0; border: none; border-top: 1px solid #e5e7eb;">
|
||||
<p style="color: #9ca3af; font-size: 12px;">
|
||||
You received this email because you have finding notifications enabled in ABE.<br>
|
||||
<a href="${finding.appUrl}/settings/notifications" style="color: #6b7280;">Manage notifications</a>
|
||||
</p>
|
||||
</body>
|
||||
</html>`;
|
||||
}
|
||||
}
|
||||
exports.EmailService = EmailService;
|
||||
Reference in New Issue
Block a user