fix: code review — security, dead code, performance, consistency
Critical fixes: - XSS: escapeHtml() on all user-supplied text in email notifications - Budget PATCH: added mutex lock + availability validation (prevents corruption) - batchResolveNames: fixed wrong signature for budget request earmark names Dead code cleanup: - Deleted 8 unused PostComposition* files (replaced by PostDetail full page) Performance: - budget-helpers: single-fetch with computeFromEntries(), optional prefetch param - post-composition: parallelized text + thumbnail fetches with Promise.all Consistency: - PostDetail.jsx: native <select> → PortalSelect (matches all panels) - Finance.jsx: 11 hardcoded English table headers → t() with i18n keys - PostCalendar.jsx: day names, Month/Week labels → t() with i18n keys - App.jsx Suspense: "Loading..." → brand spinner (can't use i18n in fallback) - UploadZone: proper useRef pattern, no vanilla JS document.createElement - All file inputs: className="hidden" → absolute w-0 h-0 opacity-0 - ArtefactDetailPanel: removed campaign/project selects (inherited from post) - TranslationDetailPanel: removed brand/linked post selects (inherited from post) - ApproverMultiSelect: portal-based dropdown (fixes clipping in modals) - Thumbnail fix: post-composition constructs URL from filename (was undefined) - Upload fix: UploadZone with drag-and-drop for design + video artefacts Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+12
-7
@@ -3,6 +3,11 @@ const { sendMail } = require('./mail');
|
||||
const nocodb = require('./nocodb');
|
||||
const { parseApproverIds } = require('./helpers');
|
||||
|
||||
function escapeHtml(str) {
|
||||
if (!str) return '';
|
||||
return String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||
}
|
||||
|
||||
const APP_URL = process.env.APP_URL || process.env.CORS_ORIGIN || 'http://localhost:3001';
|
||||
const APP_NAME_EN = 'Rawaj';
|
||||
const APP_NAME_AR = 'رواج';
|
||||
@@ -239,7 +244,7 @@ function notifyRejected({ type, record, approverName, feedback }) {
|
||||
heading: tr('rejectedHeading', l)(typeLabel),
|
||||
bodyHtml: `
|
||||
<p>${tr('rejectedBody', l)(title, approverName || (l === 'ar' ? 'مراجع' : 'a reviewer'))}</p>
|
||||
${feedback ? `<blockquote ${BLOCKQUOTE}>${feedback}</blockquote>` : ''}`,
|
||||
${feedback ? `<blockquote ${BLOCKQUOTE}>${escapeHtml(feedback)}</blockquote>` : ''}`,
|
||||
ctaText: `${tr('view', l)} ${typeLabel}`,
|
||||
ctaUrl: `${APP_URL}/${type === 'post' ? 'posts' : type === 'translation' ? 'translations' : 'artefacts'}`,
|
||||
});
|
||||
@@ -263,7 +268,7 @@ function notifyRevisionRequested({ type, record, approverName, feedback }) {
|
||||
heading: tr('revisionRequested', l),
|
||||
bodyHtml: `
|
||||
<p>${tr('revisionRequestedBody', l)(title, approverName || (l === 'ar' ? 'مراجع' : 'a reviewer'))}</p>
|
||||
${feedback ? `<blockquote ${BLOCKQUOTE}>${feedback}</blockquote>` : ''}`,
|
||||
${feedback ? `<blockquote ${BLOCKQUOTE}>${escapeHtml(feedback)}</blockquote>` : ''}`,
|
||||
ctaText: `${tr('view', l)} ${tr(entityType, l)}`,
|
||||
ctaUrl: `${APP_URL}/${entityPath}`,
|
||||
});
|
||||
@@ -286,7 +291,7 @@ function notifyTaskAssigned({ task, assignerName }) {
|
||||
bodyHtml: `
|
||||
<p>${tr('taskAssignedBody', l)(assignerName || (l === 'ar' ? 'أحدهم' : 'Someone'))}</p>
|
||||
<p style="font-size:16px;font-weight:600;color:#1e293b">${title}</p>
|
||||
${task.description ? `<p style="color:#64748b">${task.description.substring(0, 200)}</p>` : ''}
|
||||
${task.description ? `<p style="color:#64748b">${escapeHtml(task.description.substring(0, 200))}</p>` : ''}
|
||||
${task.priority ? `<p>${tr('priority', l)}: <strong>${task.priority}</strong></p>` : ''}
|
||||
${task.due_date ? `<p>${tr('dueDate', l)}: <strong>${task.due_date}</strong></p>` : ''}`,
|
||||
ctaText: tr('viewTask', l),
|
||||
@@ -351,7 +356,7 @@ function notifyIssueStatusUpdate({ issue, oldStatus, newStatus }) {
|
||||
bodyHtml: `
|
||||
<p>${tr('issueUpdateBody', 'en')(title)}</p>
|
||||
<p><span style="color:#94a3b8">${oldStatus || 'new'}</span> → <strong style="color:#3b82f6">${newStatus}</strong></p>
|
||||
${issue.resolution_summary ? `<p style="margin-top:12px"><strong>${tr('resolution', 'en')}:</strong> ${issue.resolution_summary}</p>` : ''}`,
|
||||
${issue.resolution_summary ? `<p style="margin-top:12px"><strong>${tr('resolution', 'en')}:</strong> ${escapeHtml(issue.resolution_summary)}</p>` : ''}`,
|
||||
ctaText: issue.tracking_token ? tr('trackIssue', 'en') : null,
|
||||
ctaUrl: issue.tracking_token ? `${APP_URL}/track/${issue.tracking_token}` : null,
|
||||
});
|
||||
@@ -413,7 +418,7 @@ function notifyBudgetRequest({ ceoEmail, amount, requesterName, justification, e
|
||||
heading: tr('budgetRequestHeading', 'en'),
|
||||
bodyHtml: `
|
||||
<p>${tr('budgetRequestBody', 'en')(requesterName, amount)}</p>
|
||||
<p><strong>${tr('budgetJustification', 'en')}:</strong> ${justification}</p>
|
||||
<p><strong>${tr('budgetJustification', 'en')}:</strong> ${escapeHtml(justification)}</p>
|
||||
${earmarkHtml}`,
|
||||
ctaText: tr('reviewRequest', 'en'),
|
||||
ctaUrl: approvalUrl,
|
||||
@@ -429,7 +434,7 @@ function notifyBudgetApproved({ request, requesterEmail, requesterLang }) {
|
||||
heading: tr('budgetApproved', l),
|
||||
bodyHtml: `
|
||||
<p>${tr('budgetApprovedBody', l)(String(request.amount))}</p>
|
||||
${request.response_note ? `<blockquote ${BLOCKQUOTE}>${request.response_note}</blockquote>` : ''}`,
|
||||
${request.response_note ? `<blockquote ${BLOCKQUOTE}>${escapeHtml(request.response_note)}</blockquote>` : ''}`,
|
||||
ctaText: null, ctaUrl: null,
|
||||
});
|
||||
}
|
||||
@@ -443,7 +448,7 @@ function notifyBudgetRejected({ request, requesterEmail, requesterLang }) {
|
||||
heading: tr('budgetRejected', l),
|
||||
bodyHtml: `
|
||||
<p>${tr('budgetRejectedBody', l)(String(request.amount))}</p>
|
||||
${request.response_note ? `<blockquote ${BLOCKQUOTE}>${request.response_note}</blockquote>` : ''}`,
|
||||
${request.response_note ? `<blockquote ${BLOCKQUOTE}>${escapeHtml(request.response_note)}</blockquote>` : ''}`,
|
||||
ctaText: null, ctaUrl: null,
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user