security hardening

This commit is contained in:
2026-04-18 11:10:41 +00:00
parent a409203025
commit 21618efbad
36 changed files with 1455 additions and 283 deletions

View File

@@ -23,20 +23,25 @@ const { getNextTicketNumber, checkTicketLimits, getOrCreateTicketCategory, creat
const { getEmailRouting } = require('./services/guildSettings');
const { logError, logGmail, logAutomation } = require('./services/debugLog');
const { increment } = require('./services/patternStore');
const { enqueueSend } = require('./services/channelQueue');
const Ticket = mongoose.model('Ticket');
const Transcript = mongoose.model('Transcript');
let isPolling = false;
let authErrorNotified = false;
let pollSuspended = false;
let pollCount = 0, totalProcessed = 0, totalSkipped = 0, totalErrors = 0;
function setPollSuspended(val) { pollSuspended = !!val; }
function isPollSuspended() { return pollSuspended; }
/**
* Poll Gmail for unread primary-inbox messages and route them to Discord.
* @param {import('discord.js').Client} client
*/
async function poll(client) {
if (isPolling) return;
if (isPolling || pollSuspended) return;
isPolling = true;
try {
pollCount++;
@@ -155,7 +160,8 @@ async function poll(client) {
if (ticketChan) {
const truncatedFollowup = followupBody.slice(0, 1800);
await ticketChan.send(
await enqueueSend(
ticketChan,
`<@&${CONFIG.ROLE_ID_TO_PING}>\n**New Follow-up from ${sEmail}:**\n${truncatedFollowup}`
);
} else {
@@ -247,7 +253,7 @@ async function poll(client) {
);
enforceEmbedLimit([ticketInfoEmbed]);
const welcomeMsg = await ticketChan.send({
const welcomeMsg = await enqueueSend(ticketChan, {
content: `<@&${CONFIG.ROLE_ID_TO_PING}>`,
embeds: [ticketInfoEmbed],
components: [buttons]
@@ -275,7 +281,8 @@ async function poll(client) {
.catch(() => null);
if (transcriptChan) {
await ticketChan.send(
await enqueueSend(
ticketChan,
`This email thread has ${transcriptRows.length} previous transcript(s):`
);
@@ -286,11 +293,11 @@ async function poll(client) {
if (!transcriptMsg) continue;
await ticketChan.send(`Transcript: ${transcriptMsg.url}`);
await enqueueSend(ticketChan, `Transcript: ${transcriptMsg.url}`);
const originalAttachment = transcriptMsg.attachments.first();
if (originalAttachment) {
await ticketChan.send({
await enqueueSend(ticketChan, {
content: 'Transcript file:',
files: [originalAttachment.url]
});
@@ -304,7 +311,7 @@ async function poll(client) {
}
const truncated = firstBody.slice(0, 1900);
await ticketChan.send(`**Message:**\n${truncated}`);
await enqueueSend(ticketChan, `**Message:**\n${truncated}`);
// Welcome message skipped for email tickets the email body speaks for itself.
// Panel-created (Discord) tickets still send the welcome message in handlers/buttons.js.
@@ -359,10 +366,14 @@ async function poll(client) {
e.code === 401;
if (isAuthError) {
logError('Gmail OAuth', { message: 'Gmail OAuth token invalid or expired. Re-authentication required.', stack: e.stack || e.message || String(e) }, null, client);
pollSuspended = true;
const suspendMsg = 'Gmail OAuth token invalid or expired. Polling SUSPENDED — will not retry automatically. Re-authenticate to resume.';
console.error('[gmail-poll]', suspendMsg);
logError('Gmail OAuth', { message: suspendMsg, stack: e.stack || e.message || String(e) }, null, client);
try { require('./broccolini-discord').clearGmailPollInterval?.(); } catch (_) {}
if (CONFIG.ADMIN_ID && !authErrorNotified) {
authErrorNotified = true;
client.users.fetch(CONFIG.ADMIN_ID).then(u => u.send('Gmail OAuth token invalid or expired. Re-authentication required.')).catch(() => {});
client.users.fetch(CONFIG.ADMIN_ID).then(u => u.send(suspendMsg)).catch(() => {});
}
}
@@ -375,4 +386,4 @@ async function poll(client) {
}
}
module.exports = { poll };
module.exports = { poll, setPollSuspended, isPollSuspended };