security hardening
This commit is contained in:
@@ -6,6 +6,7 @@ const { EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle } = require('
|
||||
const { CONFIG } = require('../config');
|
||||
const { mongoose } = require('../db-connection');
|
||||
const { logSecurity } = require('../services/debugLog');
|
||||
const { enqueueSend } = require('../services/channelQueue');
|
||||
|
||||
const User = mongoose.model('User');
|
||||
|
||||
@@ -167,7 +168,7 @@ async function handleSendAccountInfoToChannel(interaction) {
|
||||
}
|
||||
|
||||
const embed = buildAccountInfoEmbed(user, `${interaction.user.tag} (from ticket)`);
|
||||
await channel.send({ embeds: [embed] });
|
||||
await enqueueSend(channel, { embeds: [embed] });
|
||||
|
||||
await interaction.update({
|
||||
content: 'Account info sent to account transcript channel.',
|
||||
|
||||
@@ -21,7 +21,7 @@ const { sendTicketClosedEmail } = require('../services/gmail');
|
||||
const { getTicketActionRow } = require('../utils/ticketComponents');
|
||||
const { sanitizeEmbedText, truncateEmbedDescription, truncateEmbedField, enforceEmbedLimit } = require('../utils');
|
||||
const { setEmailRouting } = require('../services/guildSettings');
|
||||
const { enqueueRename } = require('../services/channelQueue');
|
||||
const { enqueueRename, enqueueSend } = require('../services/channelQueue');
|
||||
const { runEscalation, runDeescalation } = require('./commands');
|
||||
const { trackInteraction, trackError } = require('./analytics');
|
||||
const { pendingCloses } = require('./pendingCloses');
|
||||
@@ -356,7 +356,7 @@ async function handleClaim(interaction, ticket) {
|
||||
} else {
|
||||
const unlockAtMs = Date.now() + renameInfo.waitMs;
|
||||
const unlockAtUnix = Math.floor(unlockAtMs / 1000);
|
||||
await interaction.channel.send(
|
||||
await enqueueSend(interaction.channel,
|
||||
`Channel renamed too quickly. Try again <t:${unlockAtUnix}:R>.`
|
||||
);
|
||||
}
|
||||
@@ -410,7 +410,7 @@ async function handleClaim(interaction, ticket) {
|
||||
} else {
|
||||
const unlockAtMs = Date.now() + renameInfo.waitMs;
|
||||
const unlockAtUnix = Math.floor(unlockAtMs / 1000);
|
||||
await interaction.channel.send(
|
||||
await enqueueSend(interaction.channel,
|
||||
`Channel renamed too quickly. Try again <t:${unlockAtUnix}:R>.`
|
||||
);
|
||||
}
|
||||
@@ -496,7 +496,7 @@ async function handleConfirmClose(interaction, ticket) {
|
||||
|
||||
// In-ticket message before transcript is posted (Discord close message)
|
||||
const discordCloseContent = CONFIG.DISCORD_CLOSE_MESSAGE;
|
||||
await interaction.channel.send(discordCloseContent);
|
||||
await enqueueSend(interaction.channel, discordCloseContent);
|
||||
|
||||
const transcriptChan = await interaction.client.channels
|
||||
.fetch(CONFIG.TRANSCRIPT_CHAN)
|
||||
@@ -511,7 +511,7 @@ async function handleConfirmClose(interaction, ticket) {
|
||||
+ `\n\nDate Opened: ${openedStr}\nDate Closed: ${closedStr}`;
|
||||
|
||||
if (transcriptChan) {
|
||||
transcriptMsg = await transcriptChan.send({
|
||||
transcriptMsg = await enqueueSend(transcriptChan, {
|
||||
content: transcriptContent,
|
||||
files: [file]
|
||||
});
|
||||
@@ -559,7 +559,7 @@ async function handleConfirmClose(interaction, ticket) {
|
||||
} else {
|
||||
logMsg = `Closed **${channelName}** (${ticket.senderEmail}) by ${closerMention} (${closerDisplayName})`;
|
||||
}
|
||||
await logChan.send(logMsg);
|
||||
await enqueueSend(logChan, logMsg);
|
||||
}
|
||||
|
||||
const closerDisplayName =
|
||||
@@ -732,7 +732,7 @@ async function handleTicketModal(interaction) {
|
||||
|
||||
enforceEmbedLimit([welcomeEmbed, infoEmbed, resourcesEmbed]);
|
||||
try {
|
||||
const welcomeMsg = await channel.send({
|
||||
const welcomeMsg = await enqueueSend(channel, {
|
||||
content: `Hey There ${interaction.user} 🥦`,
|
||||
embeds: [welcomeEmbed, infoEmbed, resourcesEmbed],
|
||||
components: [actionRow]
|
||||
@@ -765,7 +765,7 @@ async function handleTicketModal(interaction) {
|
||||
|
||||
const logChan = await interaction.client.channels.fetch(CONFIG.LOG_CHAN).catch(() => null);
|
||||
if (logChan) {
|
||||
await logChan.send(
|
||||
await enqueueSend(logChan,
|
||||
`📝 ${channel.name} created by ${interaction.user.tag}`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ const { canRename, makeTicketName, resolveCreatorNickname, getSenderLocal, toDis
|
||||
const { sendTicketNotificationEmail } = require('../services/gmail');
|
||||
const { getTicketActionRow } = require('../utils/ticketComponents');
|
||||
const { getEmailRouting } = require('../services/guildSettings');
|
||||
const { enqueueRename, enqueueMove } = require('../services/channelQueue');
|
||||
const { enqueueRename, enqueueMove, enqueueSend } = require('../services/channelQueue');
|
||||
const { setNotifyDm } = require('../services/staffSettings');
|
||||
const { trackInteraction, trackError, getAnalyticsSummary } = require('./analytics');
|
||||
const { logTicketEvent, logSecurity } = require('../services/debugLog');
|
||||
@@ -74,7 +74,7 @@ async function runEscalation(interaction, ticket, nextTier, reason) {
|
||||
// Clear claim on escalation
|
||||
await Ticket.updateOne(
|
||||
{ gmailThreadId: ticket.gmailThreadId },
|
||||
{ $set: { escalated: true, escalationTier: nextTier, claimedBy: null, claimerId: null, unclaimedReminderssent: [] } }
|
||||
{ $set: { escalated: true, escalationTier: nextTier, claimedBy: null, claimerId: null, unclaimedRemindersSent: [] } }
|
||||
);
|
||||
ticket.escalated = true;
|
||||
ticket.escalationTier = nextTier;
|
||||
@@ -94,7 +94,7 @@ async function runEscalation(interaction, ticket, nextTier, reason) {
|
||||
} else {
|
||||
const unlockAtMs = Date.now() + renameInfo.waitMs;
|
||||
const unlockAtUnix = Math.floor(unlockAtMs / 1000);
|
||||
await interaction.channel.send(
|
||||
await enqueueSend(interaction.channel,
|
||||
`Channel renamed too quickly. Try again <t:${unlockAtUnix}:R>.`
|
||||
);
|
||||
}
|
||||
@@ -116,7 +116,7 @@ async function runEscalation(interaction, ticket, nextTier, reason) {
|
||||
const heyLine = creatorMention
|
||||
? `Hey There ${creatorMention} 🥦`
|
||||
: 'Hey There 🥦';
|
||||
await interaction.channel.send(
|
||||
await enqueueSend(interaction.channel,
|
||||
`${heyLine}\n**Getting the senior ${roleMention} for you.**`
|
||||
);
|
||||
|
||||
@@ -130,7 +130,7 @@ async function runEscalation(interaction, ticket, nextTier, reason) {
|
||||
.setFooter({ text: `Escalated by ${interaction.member?.displayName || interaction.user.username}` });
|
||||
const updatedTicketForRow = { ...ticket, escalationTier: nextTier, escalated: true };
|
||||
const escalationRow = getTicketActionRow(updatedTicketForRow);
|
||||
const escalationMsg = await interaction.channel.send({
|
||||
const escalationMsg = await enqueueSend(interaction.channel, {
|
||||
content: null,
|
||||
embeds: [escalatedEmbed],
|
||||
components: [escalationRow]
|
||||
@@ -174,7 +174,7 @@ async function runEscalation(interaction, ticket, nextTier, reason) {
|
||||
if (logChan) {
|
||||
const ticketType = isDiscordTicket ? 'Discord' : 'Email';
|
||||
const tierLabel = nextTier === 1 ? 'tier 2' : 'tier 3';
|
||||
await logChan.send(
|
||||
await enqueueSend(logChan,
|
||||
`${ticketType} ticket ${interaction.channel} escalated to ${tierLabel} by ${interaction.user.tag}.\nReason: ${reason}`
|
||||
);
|
||||
}
|
||||
@@ -204,7 +204,7 @@ async function runDeescalation(interaction, ticket) {
|
||||
} else {
|
||||
const unlockAtMs = Date.now() + renameInfo.waitMs;
|
||||
const unlockAtUnix = Math.floor(unlockAtMs / 1000);
|
||||
await interaction.channel.send(
|
||||
await enqueueSend(interaction.channel,
|
||||
`Channel renamed too quickly. Try again <t:${unlockAtUnix}:R>.`
|
||||
);
|
||||
}
|
||||
@@ -235,7 +235,7 @@ async function runDeescalation(interaction, ticket) {
|
||||
const logChan = await interaction.client.channels.fetch(CONFIG.LOG_CHAN).catch(() => null);
|
||||
if (logChan) {
|
||||
const ticketType = isDiscordTicket ? 'Discord' : 'Email';
|
||||
await logChan.send(
|
||||
await enqueueSend(logChan,
|
||||
`${ticketType} ticket ${interaction.channel} de‑escalated to ${tierLabel} by ${interaction.user.tag}.`
|
||||
);
|
||||
}
|
||||
@@ -517,7 +517,7 @@ async function handleCommand(interaction) {
|
||||
|
||||
const logChan = await interaction.client.channels.fetch(CONFIG.LOG_CHAN).catch(() => null);
|
||||
if (logChan) {
|
||||
await logChan.send(
|
||||
await enqueueSend(logChan,
|
||||
`Ticket ${interaction.channel} transferred from ${interaction.user.tag} to ${member.tag}.\nReason: ${reason}`
|
||||
);
|
||||
}
|
||||
@@ -542,7 +542,7 @@ async function handleCommand(interaction) {
|
||||
|
||||
const logChan = await interaction.client.channels.fetch(CONFIG.LOG_CHAN).catch(() => null);
|
||||
if (logChan) {
|
||||
await logChan.send(
|
||||
await enqueueSend(logChan,
|
||||
`Ticket ${interaction.channel} moved to category **${category.name}** by ${interaction.user.tag}`
|
||||
);
|
||||
}
|
||||
@@ -652,10 +652,10 @@ async function handleCommand(interaction) {
|
||||
{ $set: { status: 'closed' } }
|
||||
);
|
||||
|
||||
await channelRef.send('Ticket force-closed. Archiving...');
|
||||
await enqueueSend(channelRef, 'Ticket force-closed. Archiving...');
|
||||
|
||||
try {
|
||||
await channelRef.send(CONFIG.DISCORD_CLOSE_MESSAGE);
|
||||
await enqueueSend(channelRef, CONFIG.DISCORD_CLOSE_MESSAGE);
|
||||
|
||||
const messages = await channelRef.messages.fetch({ limit: 100 });
|
||||
const log =
|
||||
@@ -691,7 +691,7 @@ async function handleCommand(interaction) {
|
||||
.replace(/\{date_opened\}/g, openedStr)
|
||||
.replace(/\{date_closed\}/g, closedStr)
|
||||
+ `\n\nDate Opened: ${openedStr}\nDate Closed: ${closedStr}`;
|
||||
await transcriptChan.send({
|
||||
await enqueueSend(transcriptChan, {
|
||||
content: transcriptContent,
|
||||
files: [file]
|
||||
});
|
||||
@@ -1080,7 +1080,7 @@ async function handleCommand(interaction) {
|
||||
}
|
||||
|
||||
try {
|
||||
await channel.send({ embeds: [embed], components: [row] });
|
||||
await enqueueSend(channel, { embeds: [embed], components: [row] });
|
||||
await interaction.reply({ content: `Panel created in ${channel}!`, ephemeral: true });
|
||||
} catch (err) {
|
||||
console.error('Panel creation error:', err);
|
||||
@@ -1104,7 +1104,7 @@ async function handleCommand(interaction) {
|
||||
}
|
||||
const buf = Buffer.from(lines.join('\n'), 'utf8');
|
||||
const channel = await interaction.client.channels.fetch(CONFIG.BACKUP_EXPORT_CHANNEL_ID);
|
||||
await channel.send({
|
||||
await enqueueSend(channel, {
|
||||
content: `Ticket backup by ${interaction.user.tag} (${tickets.length} tickets)`,
|
||||
files: [new AttachmentBuilder(buf, { name: `ticket-backup-${Date.now()}.txt` })]
|
||||
});
|
||||
@@ -1134,7 +1134,7 @@ async function handleCommand(interaction) {
|
||||
}
|
||||
const buf = Buffer.from(lines.join('\n'), 'utf8');
|
||||
const channel = await interaction.client.channels.fetch(CONFIG.BACKUP_EXPORT_CHANNEL_ID);
|
||||
await channel.send({
|
||||
await enqueueSend(channel, {
|
||||
content: `Ticket export by ${interaction.user.tag} (${tickets.length} tickets${status ? ` status=${status}` : ''})`,
|
||||
files: [new AttachmentBuilder(buf, { name: `ticket-export-${Date.now()}.txt` })]
|
||||
});
|
||||
@@ -1362,7 +1362,7 @@ async function handleContextMenu(interaction) {
|
||||
const row = getTicketActionRow({ escalationTier: 0 });
|
||||
|
||||
try {
|
||||
const welcomeMsg = await channel.send({
|
||||
const welcomeMsg = await enqueueSend(channel, {
|
||||
content: `<@&${CONFIG.ROLE_ID_TO_PING}>\nHey There ${message.author} 🥦`,
|
||||
embeds: [welcomeEmbed, infoEmbed],
|
||||
components: [row]
|
||||
|
||||
@@ -15,6 +15,7 @@ const {
|
||||
ChannelSelectMenuBuilder
|
||||
} = require('discord.js');
|
||||
const { CONFIG } = require('../config');
|
||||
const { enqueueSend } = require('../services/channelQueue');
|
||||
|
||||
const TOTAL_STEPS = 5;
|
||||
const WIZARD_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes
|
||||
@@ -505,7 +506,7 @@ async function handleSetupButton(interaction) {
|
||||
);
|
||||
}
|
||||
|
||||
await channel.send({ embeds: [embed], components: [row] });
|
||||
await enqueueSend(channel, { embeds: [embed], components: [row] });
|
||||
|
||||
const envLines = state.ticketType === 'both'
|
||||
? [`DISCORD_THREAD_CHANNEL_ID=${state.threadChannelId}`, `DISCORD_TICKET_CATEGORY_ID=${state.categoryId}`]
|
||||
|
||||
Reference in New Issue
Block a user