统一 Node 22 与后端安全运行基线(07-15-production-trust-closeout-cleanup)
这个提交包含在:
@@ -17,7 +17,7 @@ function resolveEmailBaseUrl(config) {
|
||||
let cachedTransportKey = null;
|
||||
let cachedTransporter = null;
|
||||
|
||||
function getTransporter(config) {
|
||||
function getTransporter(config, createTransport = nodemailer.createTransport) {
|
||||
if (!config.emailEnabled) {
|
||||
return null;
|
||||
}
|
||||
@@ -41,7 +41,7 @@ function getTransporter(config) {
|
||||
}
|
||||
|
||||
cachedTransportKey = key;
|
||||
cachedTransporter = nodemailer.createTransport({
|
||||
cachedTransporter = createTransport({
|
||||
host: config.smtpHost,
|
||||
port: config.smtpPort,
|
||||
secure: config.smtpSecure,
|
||||
@@ -86,6 +86,36 @@ function uniq(values) {
|
||||
return Array.from(new Set((values ?? []).filter(Boolean)));
|
||||
}
|
||||
|
||||
const SAFE_SMTP_COMMANDS = new Set([
|
||||
'CONN',
|
||||
'EHLO',
|
||||
'HELO',
|
||||
'MAIL FROM',
|
||||
'RCPT TO',
|
||||
'DATA',
|
||||
'AUTH',
|
||||
]);
|
||||
|
||||
function buildEmailErrorLog(error) {
|
||||
const result = { errorCode: 'email_delivery_failed' };
|
||||
const providerCode = String(error?.code ?? '').trim();
|
||||
if (/^[A-Za-z0-9_-]{1,40}$/.test(providerCode)) {
|
||||
result.providerCode = providerCode;
|
||||
}
|
||||
|
||||
const smtpCommand = String(error?.command ?? '').trim().toUpperCase();
|
||||
if (SAFE_SMTP_COMMANDS.has(smtpCommand)) {
|
||||
result.smtpCommand = smtpCommand;
|
||||
}
|
||||
|
||||
const smtpResponseCode = Number(error?.responseCode);
|
||||
if (Number.isInteger(smtpResponseCode) && smtpResponseCode >= 100 && smtpResponseCode <= 999) {
|
||||
result.smtpResponseCode = smtpResponseCode;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async function sendNotificationEmailSafe(params) {
|
||||
try {
|
||||
const emailResult = await sendNotificationEmail(params);
|
||||
@@ -95,7 +125,7 @@ async function sendNotificationEmailSafe(params) {
|
||||
} catch (error) {
|
||||
params.logger?.error?.(
|
||||
{
|
||||
err: error,
|
||||
...buildEmailErrorLog(error),
|
||||
requestId: params.requestId,
|
||||
notificationType: params.type,
|
||||
recipients: params.recipientEmployeeIds?.length ?? 0,
|
||||
@@ -118,12 +148,13 @@ async function sendNotificationEmail({
|
||||
body,
|
||||
type = 'notification',
|
||||
targetPath,
|
||||
createTransport,
|
||||
}) {
|
||||
if (!config.emailEnabled) {
|
||||
return { skipped: true, sent: 0, failed: 0 };
|
||||
}
|
||||
|
||||
const transporter = getTransporter(config);
|
||||
const transporter = getTransporter(config, createTransport);
|
||||
if (!transporter) {
|
||||
logger?.warn?.({ requestId }, 'Email enabled but transporter not configured');
|
||||
return { skipped: true, sent: 0, failed: 0 };
|
||||
@@ -184,10 +215,9 @@ async function sendNotificationEmail({
|
||||
failed += 1;
|
||||
logger?.error?.(
|
||||
{
|
||||
err: error,
|
||||
...buildEmailErrorLog(error),
|
||||
requestId,
|
||||
employeeId: employee.id,
|
||||
email: employee.email,
|
||||
notificationType: type,
|
||||
},
|
||||
'Email send failed',
|
||||
|
||||
在新工单中引用
屏蔽一个用户