audit
This commit is contained in:
@@ -5,6 +5,10 @@ const { google } = require('googleapis');
|
||||
const { CONFIG } = require('../config');
|
||||
const { extractRawEmail, escapeHtml } = require('../utils');
|
||||
const { getStaffSignatureBlocks } = require('./staffSignature');
|
||||
const { logError } = require('./debugLog');
|
||||
|
||||
function sanitizeHeaderValue(v) { return String(v || '').replace(/[\r\n]+/g, ' ').trim(); }
|
||||
const EMAIL_RE = /^[^@\s]+@[^@\s]+$/;
|
||||
|
||||
function getGmailClient() {
|
||||
const auth = new google.auth.OAuth2(
|
||||
@@ -20,8 +24,12 @@ async function sendTicketClosedEmail(ticket, discordDisplayName) {
|
||||
const gmail = getGmailClient();
|
||||
|
||||
// Send to the ticket sender (customer), not derived from thread (which can be support)
|
||||
const recipientEmail = extractRawEmail(ticket.senderEmail || '').toLowerCase();
|
||||
const recipientEmail = sanitizeHeaderValue(extractRawEmail(ticket.senderEmail || '')).toLowerCase();
|
||||
if (!recipientEmail || recipientEmail === CONFIG.MY_EMAIL) return;
|
||||
if (!EMAIL_RE.test(recipientEmail)) {
|
||||
logError('sendTicketClosedEmail: invalid recipient', new Error(`Rejected: ${recipientEmail}`)).catch(() => {});
|
||||
return;
|
||||
}
|
||||
|
||||
let subjectHeader = ticket.subject || 'Support';
|
||||
let msgId = null;
|
||||
@@ -35,13 +43,13 @@ async function sendTicketClosedEmail(ticket, discordDisplayName) {
|
||||
if (lastMsg?.payload?.headers) {
|
||||
const subj = lastMsg.payload.headers.find(h => h.name === 'Subject')?.value;
|
||||
if (subj) subjectHeader = subj;
|
||||
msgId = lastMsg.payload.headers.find(h => h.name === 'Message-ID')?.value;
|
||||
msgId = sanitizeHeaderValue(lastMsg.payload.headers.find(h => h.name === 'Message-ID')?.value);
|
||||
}
|
||||
} catch (_) {
|
||||
/* use ticket.subject and no In-Reply-To if thread fetch fails */
|
||||
}
|
||||
|
||||
const finalSubject = `${CONFIG.TICKET_CLOSE_SUBJECT_PREFIX} ${subjectHeader}`;
|
||||
const finalSubject = sanitizeHeaderValue(`${CONFIG.TICKET_CLOSE_SUBJECT_PREFIX} ${subjectHeader}`);
|
||||
const utf8Subject = `=?utf-8?B?${Buffer.from(
|
||||
finalSubject
|
||||
).toString('base64')}?=`;
|
||||
@@ -72,7 +80,7 @@ async function sendTicketClosedEmail(ticket, discordDisplayName) {
|
||||
</div>`;
|
||||
|
||||
const rawHeaders = [
|
||||
`From: ${CONFIG.MY_EMAIL}`,
|
||||
`From: ${sanitizeHeaderValue(CONFIG.MY_EMAIL)}`,
|
||||
`To: ${recipientEmail}`,
|
||||
`Subject: ${utf8Subject}`,
|
||||
msgId ? `In-Reply-To: ${msgId}` : '',
|
||||
@@ -113,8 +121,12 @@ const StaffSignature = mongoose.model('StaffSignature');
|
||||
async function sendTicketNotificationEmail(ticket, subjectLine, messageBody, fromLabel, userId = null) {
|
||||
try {
|
||||
const gmail = getGmailClient();
|
||||
const recipientEmail = extractRawEmail(ticket.senderEmail || '').toLowerCase();
|
||||
const recipientEmail = sanitizeHeaderValue(extractRawEmail(ticket.senderEmail || '')).toLowerCase();
|
||||
if (!recipientEmail || recipientEmail === CONFIG.MY_EMAIL) return;
|
||||
if (!EMAIL_RE.test(recipientEmail)) {
|
||||
logError('sendTicketNotificationEmail: invalid recipient', new Error(`Rejected: ${recipientEmail}`)).catch(() => {});
|
||||
return;
|
||||
}
|
||||
|
||||
let subjectHeader = ticket.subject || 'Support';
|
||||
let msgId = null;
|
||||
@@ -128,11 +140,11 @@ async function sendTicketNotificationEmail(ticket, subjectLine, messageBody, fro
|
||||
if (lastMsg?.payload?.headers) {
|
||||
const subj = lastMsg.payload.headers.find(h => h.name === 'Subject')?.value;
|
||||
if (subj) subjectHeader = subj;
|
||||
msgId = lastMsg.payload.headers.find(h => h.name === 'Message-ID')?.value;
|
||||
msgId = sanitizeHeaderValue(lastMsg.payload.headers.find(h => h.name === 'Message-ID')?.value);
|
||||
}
|
||||
} catch (_) {}
|
||||
|
||||
const finalSubject = subjectLine || subjectHeader;
|
||||
const finalSubject = sanitizeHeaderValue(subjectLine || subjectHeader);
|
||||
const utf8Subject = `=?utf-8?B?${Buffer.from(finalSubject).toString('base64')}?=`;
|
||||
const label = escapeHtml(fromLabel || CONFIG.SUPPORT_NAME || 'Support');
|
||||
const safeBody = escapeHtml(messageBody || '').replace(/\n/g, '<br>');
|
||||
@@ -169,7 +181,7 @@ async function sendTicketNotificationEmail(ticket, subjectLine, messageBody, fro
|
||||
</div>`;
|
||||
|
||||
const rawHeaders = [
|
||||
`From: ${CONFIG.MY_EMAIL}`,
|
||||
`From: ${sanitizeHeaderValue(CONFIG.MY_EMAIL)}`,
|
||||
`To: ${recipientEmail}`,
|
||||
`Subject: ${utf8Subject}`,
|
||||
msgId ? `In-Reply-To: ${msgId}` : '',
|
||||
@@ -216,8 +228,16 @@ async function sendGmailReply(
|
||||
) {
|
||||
const gmail = getGmailClient();
|
||||
|
||||
const safeRecipient = sanitizeHeaderValue(extractRawEmail(recipientEmail || '')).toLowerCase();
|
||||
if (!EMAIL_RE.test(safeRecipient)) {
|
||||
logError('sendGmailReply: invalid recipient', new Error(`Rejected: ${safeRecipient}`)).catch(() => {});
|
||||
return null;
|
||||
}
|
||||
const safeMessageId = sanitizeHeaderValue(messageId);
|
||||
const safeSubject = sanitizeHeaderValue(`Re: ${subject}`);
|
||||
|
||||
const utf8Subject = `=?utf-8?B?${Buffer.from(
|
||||
`Re: ${subject}`
|
||||
safeSubject
|
||||
).toString('base64')}?=`;
|
||||
const safeUser = escapeHtml(discordUser);
|
||||
const safeLogoUrl = escapeHtml(CONFIG.LOGO_URL || '');
|
||||
@@ -229,6 +249,7 @@ async function sendGmailReply(
|
||||
signatureBlocks = await getStaffSignatureBlocks(userId);
|
||||
}
|
||||
|
||||
// signatureBlocks.html must arrive pre-escaped; do not inject raw HTML here.
|
||||
const safeStaffSigHtml = signatureBlocks.html ? signatureBlocks.html.replace(/\n/g, '<br>') : '';
|
||||
const safeStaffSigText = signatureBlocks.text;
|
||||
const safeCompanySigHtml = escapeHtml(CONFIG.SIGNATURE || '').replace(/\n/g, '<br>');
|
||||
@@ -264,11 +285,11 @@ async function sendGmailReply(
|
||||
plainBody.push(companySignatureText);
|
||||
|
||||
const raw = Buffer.from([
|
||||
`From: ${CONFIG.MY_EMAIL}`,
|
||||
`To: ${recipientEmail}`,
|
||||
`From: ${sanitizeHeaderValue(CONFIG.MY_EMAIL)}`,
|
||||
`To: ${safeRecipient}`,
|
||||
`Subject: ${utf8Subject}`,
|
||||
messageId ? `In-Reply-To: ${messageId}` : '',
|
||||
messageId ? `References: ${messageId}` : '',
|
||||
safeMessageId ? `In-Reply-To: ${safeMessageId}` : '',
|
||||
safeMessageId ? `References: ${safeMessageId}` : '',
|
||||
'MIME-Version: 1.0',
|
||||
'Content-Type: multipart/alternative; boundary="' + boundary + '"',
|
||||
'',
|
||||
|
||||
Reference in New Issue
Block a user