refactor(runtime): prefer env-backed secrets at runtime
This commit is contained in:
@@ -4,6 +4,8 @@
|
||||
*/
|
||||
import nodemailer from "nodemailer";
|
||||
import { prisma as db } from "@capakraken/db";
|
||||
import { logger } from "./logger.js";
|
||||
import { resolveSystemSettingsRuntime } from "./system-settings-runtime.js";
|
||||
|
||||
interface EmailPayload {
|
||||
to: string | string[];
|
||||
@@ -12,9 +14,43 @@ interface EmailPayload {
|
||||
html?: string;
|
||||
}
|
||||
|
||||
function sanitizeSmtpDiagnostic(err: unknown): string {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
return message
|
||||
.replace(/https?:\/\/[^\s)\]}]+/gi, "<redacted-url>")
|
||||
.replace(/\b[^\s@]+@[^\s@]+\.[^\s@]+\b/g, "<redacted-email>")
|
||||
.replace(/\b(?:[A-Za-z0-9-]+\.)+[A-Za-z]{2,}\b/g, "<redacted-host>")
|
||||
.replace(/^Error:\s*/, "")
|
||||
.slice(0, 300);
|
||||
}
|
||||
|
||||
export function parseSmtpError(err: unknown): string {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
const lower = message.toLowerCase();
|
||||
|
||||
if (lower.includes("auth") || lower.includes("invalid login") || lower.includes("535")) {
|
||||
return "SMTP authentication failed — check username and password.";
|
||||
}
|
||||
if (lower.includes("certificate") || lower.includes("tls") || lower.includes("ssl")) {
|
||||
return "SMTP TLS negotiation failed — verify port and TLS settings.";
|
||||
}
|
||||
if (
|
||||
lower.includes("econnrefused")
|
||||
|| lower.includes("enotfound")
|
||||
|| lower.includes("etimedout")
|
||||
|| lower.includes("timeout")
|
||||
) {
|
||||
return "Cannot reach the SMTP server — check host, port, and network access.";
|
||||
}
|
||||
|
||||
return "SMTP connection failed — review host, port, TLS, and credentials.";
|
||||
}
|
||||
|
||||
async function getSmtpConfig() {
|
||||
const settings = await db.systemSettings.findUnique({ where: { id: "singleton" } });
|
||||
if (!settings?.smtpHost) return null;
|
||||
const settings = resolveSystemSettingsRuntime(
|
||||
await db.systemSettings.findUnique({ where: { id: "singleton" } }),
|
||||
);
|
||||
if (!settings.smtpHost) return null;
|
||||
return {
|
||||
host: settings.smtpHost,
|
||||
port: settings.smtpPort ?? 587,
|
||||
@@ -53,7 +89,7 @@ export async function sendEmail(payload: EmailPayload): Promise<boolean> {
|
||||
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.error("[email] Failed to send email:", err);
|
||||
logger.warn({ diagnostic: sanitizeSmtpDiagnostic(err) }, "Email send failed");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -76,6 +112,7 @@ export async function testSmtpConnection(): Promise<{ ok: boolean; error?: strin
|
||||
await transporter.verify();
|
||||
return { ok: true };
|
||||
} catch (err) {
|
||||
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
||||
logger.warn({ diagnostic: sanitizeSmtpDiagnostic(err) }, "SMTP connection test failed");
|
||||
return { ok: false, error: parseSmtpError(err) };
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user