import "dotenv/config"; import { prisma } from "../src/server/db"; import { writeAuditLog } from "../src/server/audit"; import { tenantSender } from "../src/server/mail/deliver"; import type { MailConfig } from "../src/server/mail/config"; /** * Foundation fixes: * (a) writeAuditLog outside a request scope stores ip/userAgent = null (no crash) * (b) mail sender uses the tenant display name + reply-to, keeps the platform address, * strips header-injection characters, and falls back for platform mails. */ let failures = 0; function check(name: string, cond: boolean, detail?: unknown) { if (cond) console.log(` ✓ ${name}`); else { failures++; console.log(` ✗ ${name}`, detail ?? ""); } } const config: MailConfig = { host: "localhost", port: 1025, secure: false, from: "no-reply@craftvia.test", fromName: "Craftvia", replyTo: "support@craftvia.test", baseUrl: "http://localhost:3000", }; async function main() { const slug = `zz-amc-${Date.now()}`; const tenant = await prisma.tenant.create({ data: { name: "ZZ Audit Mail", slug } }); try { // (a) audit outside request await writeAuditLog({ tenantId: tenant.id, action: "update", entity: "zz_test", entityId: "x1" }); const row = await prisma.auditLog.findFirst({ where: { tenantId: tenant.id, entity: "zz_test" } }); check("audit row written outside request", !!row); check("ipAddress null outside request", row?.ipAddress === null, row?.ipAddress); check("userAgent null outside request", row?.userAgent === null, row?.userAgent); // (b) tenant sender await prisma.tenantSettings.create({ data: { tenantId: tenant.id, orgName: "ZZ", mailFromName: 'Musterbau "Evil"\r\nBcc: x@y.z ', mailReplyTo: "buero@musterbau.test" }, }); const log = await prisma.mailLog.create({ data: { tenantId: tenant.id, to: "a@b.test", template: "test" } }); const s = await tenantSender(log.id, config); check("platform address kept", s.from.endsWith(""), s.from); check("tenant display name used", s.from.startsWith("Musterbau Evil"), s.from); check("no CR/LF or quotes in From", !/[\r\n"]/.test(s.from), JSON.stringify(s.from)); check("only one angle-bracket address", (s.from.match(/", p.from); check("platform mail falls back to default reply-to", p.replyTo === "support@craftvia.test", p.replyTo); await prisma.mailLog.delete({ where: { id: platformLog.id } }); } finally { await prisma.mailLog.deleteMany({ where: { tenantId: tenant.id } }); await prisma.auditLog.deleteMany({ where: { tenantId: tenant.id } }); await prisma.tenantSettings.deleteMany({ where: { tenantId: tenant.id } }); await prisma.tenant.delete({ where: { id: tenant.id } }); await prisma.$disconnect(); } console.log(failures ? `\n${failures} Fehler` : "\nOK"); process.exit(failures ? 1 : 0); } main().catch((err) => { console.error(err); process.exit(1); });