Fix XSS, CSRF, input validation, and related security issues
This commit is contained in:
@@ -349,6 +349,18 @@
|
||||
navigator.serviceWorker.register('/sw.js');
|
||||
}
|
||||
|
||||
// Inject CSRF token into all POST forms
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var token = '{{ csrf_token() }}';
|
||||
document.querySelectorAll('form').forEach(function(form) {
|
||||
if (form.method.toLowerCase() === 'post') {
|
||||
var inp = document.createElement('input');
|
||||
inp.type = 'hidden'; inp.name = 'csrf_token'; inp.value = token;
|
||||
form.appendChild(inp);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
(function() {
|
||||
var modal = document.getElementById('confirm-modal');
|
||||
var msgEl = document.getElementById('confirm-modal-msg');
|
||||
|
||||
@@ -411,7 +411,7 @@ function metaSelectChanged(sel, inputId) {
|
||||
(function() {
|
||||
var canvas = document.getElementById('pct-chart');
|
||||
if (!canvas) return;
|
||||
var rawLogs = {{ pct_logs_json | safe }};
|
||||
var rawLogs = {{ pct_logs_data | tojson }};
|
||||
// pct_logs_json is ordered newest-first; chart wants oldest-first
|
||||
var logsAsc = rawLogs.slice().reverse();
|
||||
var vals = logsAsc.map(function(l) { return l.pct; });
|
||||
@@ -472,14 +472,15 @@ function metaSelectChanged(sel, inputId) {
|
||||
<style>
|
||||
.hist-modal { display:none; position:fixed; inset:0; z-index:1000; background:rgba(0,0,0,.45); align-items:center; justify-content:center; }
|
||||
.hist-modal.open { display:flex; }
|
||||
.hist-modal-box { background:#fff; border-radius:8px; width:min(700px,96vw); max-height:85vh; display:flex; flex-direction:column; overflow:hidden; box-shadow:0 8px 32px rgba(0,0,0,.2); }
|
||||
.hist-modal-box { background:var(--bg-card,#fff); color:var(--text-body,#222); border-radius:8px; width:min(700px,96vw); max-height:85vh; display:flex; flex-direction:column; overflow:hidden; box-shadow:0 8px 32px rgba(0,0,0,.2); }
|
||||
.hist-modal-header { display:flex; align-items:center; justify-content:space-between; padding:0.9rem 1.1rem 0.7rem; border-bottom:1px solid var(--border,#e2e8f0); }
|
||||
.hist-modal-header h3 { margin:0; font-size:1.05rem; }
|
||||
.hist-modal-header h3 { margin:0; font-size:1.05rem; color:var(--text-h2,#334155); }
|
||||
.hist-modal-close { background:none; border:none; font-size:1.2rem; cursor:pointer; color:var(--text-muted,#6b7280); line-height:1; padding:0.2rem 0.4rem; }
|
||||
.hist-modal-filters { padding:0.6rem 1.1rem; border-bottom:1px solid var(--border,#e2e8f0); display:flex; gap:0.75rem; align-items:center; flex-wrap:wrap; }
|
||||
.hist-modal-filters:empty { display:none; }
|
||||
.hist-modal-body { overflow-y:auto; flex:1; padding:0.5rem 0.5rem; }
|
||||
.hist-modal-footer { display:flex; align-items:center; justify-content:space-between; padding:0.6rem 1.1rem; border-top:1px solid var(--border,#e2e8f0); font-size:0.85rem; color:var(--text-muted,#6b7280); }
|
||||
.hist-modal-filters select { background:var(--bg-input,#fff); color:var(--text-body,#222); border:1px solid var(--border-input,#d1d5db); border-radius:4px; padding:0.3rem 0.5rem; font-size:0.875rem; font-family:inherit; }
|
||||
</style>
|
||||
|
||||
<!-- Capacity modal -->
|
||||
@@ -549,6 +550,14 @@ function metaSelectChanged(sel, inputId) {
|
||||
var HIST_PAGE = 20;
|
||||
var _batteryId = {{ battery.id }};
|
||||
|
||||
function escHtml(s) {
|
||||
return String(s == null ? '' : s)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"');
|
||||
}
|
||||
|
||||
function openHistModal(id) {
|
||||
document.getElementById(id).classList.add('open');
|
||||
}
|
||||
@@ -618,7 +627,7 @@ function makeModal(cfg) {
|
||||
}
|
||||
|
||||
// ── Capacity modal ────────────────────────────────────────────────────────
|
||||
var _capAll = {{ capacity_tests_json | safe }};
|
||||
var _capAll = {{ capacity_tests_data | tojson }};
|
||||
var capModal = makeModal({
|
||||
all: _capAll,
|
||||
bodyId: 'cap-modal-body', prevId: 'cap-prev', nextId: 'cap-next', pageInfoId: 'cap-page-info',
|
||||
@@ -626,9 +635,9 @@ var capModal = makeModal({
|
||||
thead: '<tr><th>Date</th><th>Capacity</th><th>Notes</th><th></th></tr>',
|
||||
renderRow: function(r) {
|
||||
return '<tr>' +
|
||||
'<td data-label="Date">' + r.date + '</td>' +
|
||||
'<td data-label="Date">' + escHtml(r.date) + '</td>' +
|
||||
'<td data-label="Capacity">' + r.mah + ' mAh</td>' +
|
||||
'<td data-label="Notes" class="text-muted">' + (r.notes || '—') + '</td>' +
|
||||
'<td data-label="Notes" class="text-muted">' + (escHtml(r.notes) || '—') + '</td>' +
|
||||
'<td data-label="">' +
|
||||
'<form class="inline" method="post" ' +
|
||||
'action="/battery/' + _batteryId + '/capacity-test/' + r.id + '/delete" ' +
|
||||
@@ -645,7 +654,7 @@ document.querySelector('[onclick="openHistModal(\'cap-modal\')"]') &&
|
||||
document.querySelector('[onclick="openHistModal(\'cap-modal\')"]').addEventListener('click', function() { capModal.init(); });
|
||||
|
||||
// ── Charge modal ──────────────────────────────────────────────────────────
|
||||
var _chgAll = {{ charge_logs_json | safe }};
|
||||
var _chgAll = {{ charge_logs_data | tojson }};
|
||||
var chgModal = makeModal({
|
||||
all: _chgAll,
|
||||
bodyId: 'chg-modal-body', prevId: 'chg-prev', nextId: 'chg-next', pageInfoId: 'chg-page-info',
|
||||
@@ -653,9 +662,9 @@ var chgModal = makeModal({
|
||||
thead: '<tr><th>Date</th><th>+Cycle</th><th>Notes</th><th></th></tr>',
|
||||
renderRow: function(r) {
|
||||
return '<tr>' +
|
||||
'<td data-label="Date">' + r.date + '</td>' +
|
||||
'<td data-label="Date">' + escHtml(r.date) + '</td>' +
|
||||
'<td data-label="+Cycle">' + (r.cycles ? '✓' : '—') + '</td>' +
|
||||
'<td data-label="Notes" class="text-muted">' + (r.notes || '—') + '</td>' +
|
||||
'<td data-label="Notes" class="text-muted">' + (escHtml(r.notes) || '—') + '</td>' +
|
||||
'<td data-label="">' +
|
||||
'<form class="inline" method="post" ' +
|
||||
'action="/battery/' + _batteryId + '/charge-log/' + r.id + '/delete" ' +
|
||||
@@ -669,7 +678,7 @@ document.querySelector('[onclick="openHistModal(\'chg-modal\')"]') &&
|
||||
document.querySelector('[onclick="openHistModal(\'chg-modal\')"]').addEventListener('click', function() { chgModal.init(); });
|
||||
|
||||
// ── Percentage modal ──────────────────────────────────────────────────────
|
||||
var _pctAll = {{ pct_logs_json | safe }};
|
||||
var _pctAll = {{ pct_logs_data | tojson }};
|
||||
var pctModal = makeModal({
|
||||
all: _pctAll,
|
||||
bodyId: 'pct-modal-body', prevId: 'pct-prev', nextId: 'pct-next', pageInfoId: 'pct-page-info',
|
||||
@@ -680,9 +689,9 @@ var pctModal = makeModal({
|
||||
? '<span class="badge badge-warning">⚠ ' + r.pct + '%</span>'
|
||||
: r.pct + '%';
|
||||
return '<tr>' +
|
||||
'<td data-label="Date / Time">' + r.recorded_at + '</td>' +
|
||||
'<td data-label="Date / Time">' + escHtml(r.recorded_at) + '</td>' +
|
||||
'<td data-label="%">' + pctHtml + '</td>' +
|
||||
'<td data-label="Source" class="text-muted">' + (r.source || '—') + '</td>' +
|
||||
'<td data-label="Source" class="text-muted">' + (escHtml(r.source) || '—') + '</td>' +
|
||||
'</tr>';
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user