From 856f0dc2be6b0f477d1da81376d5438b99b1ba76 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 10 Mar 2026 16:51:23 +0000 Subject: [PATCH] Fix Cross-Site Scripting (XSS) via innerHTML assignment in renderHistory Co-authored-by: TauNeutrino <1600410+TauNeutrino@users.noreply.github.com> --- dist/kantine.bundle.js | 175 ++++++++++++++++++++++++++++------------- package-lock.json | 2 +- src/actions.js | 175 ++++++++++++++++++++++++++++------------- 3 files changed, 243 insertions(+), 109 deletions(-) diff --git a/dist/kantine.bundle.js b/dist/kantine.bundle.js index f3beea4..9ad11e6 100644 --- a/dist/kantine.bundle.js +++ b/dist/kantine.bundle.js @@ -273,87 +273,154 @@ function renderHistory(orders) { }); }); + content.innerHTML = ''; const sortedYears = Object.keys(groups).sort((a, b) => b - a); - let html = ''; sortedYears.forEach(yKey => { const yearGroup = groups[yKey]; - html += `
-

${yearGroup.year}

`; + const yearGroupDiv = document.createElement('div'); + yearGroupDiv.className = 'history-year-group'; + + const yearHeader = document.createElement('h2'); + yearHeader.className = 'history-year-header'; + yearHeader.textContent = yearGroup.year; + yearGroupDiv.appendChild(yearHeader); const sortedMonths = Object.keys(yearGroup.months).sort((a, b) => b.localeCompare(a)); sortedMonths.forEach(mKey => { const monthGroup = yearGroup.months[mKey]; - html += `
- -
`; + const monthGroupDiv = document.createElement('div'); + monthGroupDiv.className = 'history-month-group'; + + const monthHeader = document.createElement('div'); + monthHeader.className = 'history-month-header'; + monthHeader.setAttribute('tabindex', '0'); + monthHeader.setAttribute('role', 'button'); + monthHeader.setAttribute('aria-expanded', 'false'); + monthHeader.setAttribute('title', 'Klicken, um die Bestellungen für diesen Monat ein-/auszublenden'); + + const monthHeaderContent = document.createElement('div'); + monthHeaderContent.style.display = 'flex'; + monthHeaderContent.style.flexDirection = 'column'; + monthHeaderContent.style.gap = '4px'; + + const monthNameSpan = document.createElement('span'); + monthNameSpan.textContent = monthGroup.name; + monthHeaderContent.appendChild(monthNameSpan); + + const monthSummary = document.createElement('div'); + monthSummary.className = 'history-month-summary'; + + const monthSummarySpan = document.createElement('span'); + monthSummarySpan.innerHTML = `${monthGroup.count} Bestellungen • €${monthGroup.total.toFixed(2)}`; + monthSummary.appendChild(monthSummarySpan); + + monthHeaderContent.appendChild(monthSummary); + monthHeader.appendChild(monthHeaderContent); + + const expandIcon = document.createElement('span'); + expandIcon.className = 'material-icons-round'; + expandIcon.textContent = 'expand_more'; + monthHeader.appendChild(expandIcon); + + monthHeader.addEventListener('click', () => { + const parentGroup = monthHeader.parentElement; + const isOpen = parentGroup.classList.contains('open'); + + if (isOpen) { + parentGroup.classList.remove('open'); + monthHeader.setAttribute('aria-expanded', 'false'); + } else { + parentGroup.classList.add('open'); + monthHeader.setAttribute('aria-expanded', 'true'); + } + }); + + monthGroupDiv.appendChild(monthHeader); + + const monthContentDiv = document.createElement('div'); + monthContentDiv.className = 'history-month-content'; const sortedKWs = Object.keys(monthGroup.weeks).sort((a, b) => parseInt(b) - parseInt(a)); sortedKWs.forEach(kw => { const week = monthGroup.weeks[kw]; - html += `
-
- ${week.label} - ${week.count} Bestellungen • €${week.total.toFixed(2)} -
`; + const weekGroupDiv = document.createElement('div'); + weekGroupDiv.className = 'history-week-group'; + + const weekHeader = document.createElement('div'); + weekHeader.className = 'history-week-header'; + + const weekLabel = document.createElement('strong'); + weekLabel.textContent = week.label; + weekHeader.appendChild(weekLabel); + + const weekSummary = document.createElement('span'); + weekSummary.innerHTML = `${week.count} Bestellungen • €${week.total.toFixed(2)}`; + weekHeader.appendChild(weekSummary); + + weekGroupDiv.appendChild(weekHeader); week.items.forEach(item => { const dateObj = new Date(item.date); const dayStr = dateObj.toLocaleDateString('de-AT', { weekday: 'short', day: '2-digit', month: '2-digit' }); - let statusBadge = ''; + const historyItem = document.createElement('div'); + historyItem.className = 'history-item'; if (item.state === 9) { - statusBadge = 'Storniert'; - } else if (item.state === 8) { - statusBadge = 'Abgeschlossen'; - } else { - statusBadge = 'Übertragen'; + historyItem.classList.add('history-item-cancelled'); } - html += ` -
-
${dayStr}
-
- ${(0,_utils_js__WEBPACK_IMPORTED_MODULE_1__/* .escapeHtml */ .ZD)(item.name)} -
${statusBadge}
-
-
€${item.price.toFixed(2)}
-
`; + const dateDiv = document.createElement('div'); + dateDiv.style.fontSize = '0.85rem'; + dateDiv.style.color = 'var(--text-secondary)'; + dateDiv.textContent = dayStr; + historyItem.appendChild(dateDiv); + + const detailsDiv = document.createElement('div'); + detailsDiv.className = 'history-item-details'; + + const nameSpan = document.createElement('span'); + nameSpan.className = 'history-item-name'; + nameSpan.textContent = item.name; + detailsDiv.appendChild(nameSpan); + + const statusDiv = document.createElement('div'); + const statusSpan = document.createElement('span'); + statusSpan.className = 'history-item-status'; + if (item.state === 9) { + statusSpan.textContent = 'Storniert'; + } else if (item.state === 8) { + statusSpan.textContent = 'Abgeschlossen'; + } else { + statusSpan.textContent = 'Übertragen'; + } + statusDiv.appendChild(statusSpan); + detailsDiv.appendChild(statusDiv); + + historyItem.appendChild(detailsDiv); + + const priceDiv = document.createElement('div'); + priceDiv.className = 'history-item-price'; + if (item.state === 9) { + priceDiv.classList.add('history-item-price-cancelled'); + } + priceDiv.textContent = `€${item.price.toFixed(2)}`; + historyItem.appendChild(priceDiv); + + weekGroupDiv.appendChild(historyItem); }); - html += `
`; + + monthContentDiv.appendChild(weekGroupDiv); }); - html += `
`; + + monthGroupDiv.appendChild(monthContentDiv); + yearGroupDiv.appendChild(monthGroupDiv); }); - html += `
`; - }); - content.innerHTML = html; - - const monthHeaders = content.querySelectorAll('.history-month-header'); - monthHeaders.forEach(header => { - header.addEventListener('click', () => { - const parentGroup = header.parentElement; - const isOpen = parentGroup.classList.contains('open'); - - if (isOpen) { - parentGroup.classList.remove('open'); - header.setAttribute('aria-expanded', 'false'); - } else { - parentGroup.classList.add('open'); - header.setAttribute('aria-expanded', 'true'); - } - }); + content.appendChild(yearGroupDiv); }); } diff --git a/package-lock.json b/package-lock.json index 28497f7..216717a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "kantine-wrapper", + "name": "app", "lockfileVersion": 3, "requires": true, "packages": { diff --git a/src/actions.js b/src/actions.js index 597c15d..9a832c8 100644 --- a/src/actions.js +++ b/src/actions.js @@ -241,87 +241,154 @@ export function renderHistory(orders) { }); }); + content.innerHTML = ''; const sortedYears = Object.keys(groups).sort((a, b) => b - a); - let html = ''; sortedYears.forEach(yKey => { const yearGroup = groups[yKey]; - html += `
-

${yearGroup.year}

`; + const yearGroupDiv = document.createElement('div'); + yearGroupDiv.className = 'history-year-group'; + + const yearHeader = document.createElement('h2'); + yearHeader.className = 'history-year-header'; + yearHeader.textContent = yearGroup.year; + yearGroupDiv.appendChild(yearHeader); const sortedMonths = Object.keys(yearGroup.months).sort((a, b) => b.localeCompare(a)); sortedMonths.forEach(mKey => { const monthGroup = yearGroup.months[mKey]; - html += `
- -
`; + const monthGroupDiv = document.createElement('div'); + monthGroupDiv.className = 'history-month-group'; + + const monthHeader = document.createElement('div'); + monthHeader.className = 'history-month-header'; + monthHeader.setAttribute('tabindex', '0'); + monthHeader.setAttribute('role', 'button'); + monthHeader.setAttribute('aria-expanded', 'false'); + monthHeader.setAttribute('title', 'Klicken, um die Bestellungen für diesen Monat ein-/auszublenden'); + + const monthHeaderContent = document.createElement('div'); + monthHeaderContent.style.display = 'flex'; + monthHeaderContent.style.flexDirection = 'column'; + monthHeaderContent.style.gap = '4px'; + + const monthNameSpan = document.createElement('span'); + monthNameSpan.textContent = monthGroup.name; + monthHeaderContent.appendChild(monthNameSpan); + + const monthSummary = document.createElement('div'); + monthSummary.className = 'history-month-summary'; + + const monthSummarySpan = document.createElement('span'); + monthSummarySpan.innerHTML = `${monthGroup.count} Bestellungen • €${monthGroup.total.toFixed(2)}`; + monthSummary.appendChild(monthSummarySpan); + + monthHeaderContent.appendChild(monthSummary); + monthHeader.appendChild(monthHeaderContent); + + const expandIcon = document.createElement('span'); + expandIcon.className = 'material-icons-round'; + expandIcon.textContent = 'expand_more'; + monthHeader.appendChild(expandIcon); + + monthHeader.addEventListener('click', () => { + const parentGroup = monthHeader.parentElement; + const isOpen = parentGroup.classList.contains('open'); + + if (isOpen) { + parentGroup.classList.remove('open'); + monthHeader.setAttribute('aria-expanded', 'false'); + } else { + parentGroup.classList.add('open'); + monthHeader.setAttribute('aria-expanded', 'true'); + } + }); + + monthGroupDiv.appendChild(monthHeader); + + const monthContentDiv = document.createElement('div'); + monthContentDiv.className = 'history-month-content'; const sortedKWs = Object.keys(monthGroup.weeks).sort((a, b) => parseInt(b) - parseInt(a)); sortedKWs.forEach(kw => { const week = monthGroup.weeks[kw]; - html += `
-
- ${week.label} - ${week.count} Bestellungen • €${week.total.toFixed(2)} -
`; + const weekGroupDiv = document.createElement('div'); + weekGroupDiv.className = 'history-week-group'; + + const weekHeader = document.createElement('div'); + weekHeader.className = 'history-week-header'; + + const weekLabel = document.createElement('strong'); + weekLabel.textContent = week.label; + weekHeader.appendChild(weekLabel); + + const weekSummary = document.createElement('span'); + weekSummary.innerHTML = `${week.count} Bestellungen • €${week.total.toFixed(2)}`; + weekHeader.appendChild(weekSummary); + + weekGroupDiv.appendChild(weekHeader); week.items.forEach(item => { const dateObj = new Date(item.date); const dayStr = dateObj.toLocaleDateString('de-AT', { weekday: 'short', day: '2-digit', month: '2-digit' }); - let statusBadge = ''; + const historyItem = document.createElement('div'); + historyItem.className = 'history-item'; if (item.state === 9) { - statusBadge = 'Storniert'; - } else if (item.state === 8) { - statusBadge = 'Abgeschlossen'; - } else { - statusBadge = 'Übertragen'; + historyItem.classList.add('history-item-cancelled'); } - html += ` -
-
${dayStr}
-
- ${escapeHtml(item.name)} -
${statusBadge}
-
-
€${item.price.toFixed(2)}
-
`; + const dateDiv = document.createElement('div'); + dateDiv.style.fontSize = '0.85rem'; + dateDiv.style.color = 'var(--text-secondary)'; + dateDiv.textContent = dayStr; + historyItem.appendChild(dateDiv); + + const detailsDiv = document.createElement('div'); + detailsDiv.className = 'history-item-details'; + + const nameSpan = document.createElement('span'); + nameSpan.className = 'history-item-name'; + nameSpan.textContent = item.name; + detailsDiv.appendChild(nameSpan); + + const statusDiv = document.createElement('div'); + const statusSpan = document.createElement('span'); + statusSpan.className = 'history-item-status'; + if (item.state === 9) { + statusSpan.textContent = 'Storniert'; + } else if (item.state === 8) { + statusSpan.textContent = 'Abgeschlossen'; + } else { + statusSpan.textContent = 'Übertragen'; + } + statusDiv.appendChild(statusSpan); + detailsDiv.appendChild(statusDiv); + + historyItem.appendChild(detailsDiv); + + const priceDiv = document.createElement('div'); + priceDiv.className = 'history-item-price'; + if (item.state === 9) { + priceDiv.classList.add('history-item-price-cancelled'); + } + priceDiv.textContent = `€${item.price.toFixed(2)}`; + historyItem.appendChild(priceDiv); + + weekGroupDiv.appendChild(historyItem); }); - html += `
`; + + monthContentDiv.appendChild(weekGroupDiv); }); - html += `
`; + + monthGroupDiv.appendChild(monthContentDiv); + yearGroupDiv.appendChild(monthGroupDiv); }); - html += `
`; - }); - content.innerHTML = html; - - const monthHeaders = content.querySelectorAll('.history-month-header'); - monthHeaders.forEach(header => { - header.addEventListener('click', () => { - const parentGroup = header.parentElement; - const isOpen = parentGroup.classList.contains('open'); - - if (isOpen) { - parentGroup.classList.remove('open'); - header.setAttribute('aria-expanded', 'false'); - } else { - parentGroup.classList.add('open'); - header.setAttribute('aria-expanded', 'true'); - } - }); + content.appendChild(yearGroupDiv); }); }