security: CORS hardening, path traversal fix, WebSocket auth + cleanup

- Restrict CORS to localhost origins (was allow_origins=[*])
- Require valid JWT on WebSocket /ws (anonymous no longer gets admin view)
- Fix path traversal in delete_cell(): resolve() + parent check
- Validate cell_id format in /charts/download-noaa/{cell_id}
- Exclude charts/ and Cartas/ from git (keep US1GC09M world overview)
- Add NOAA ENC Portal external link in charts catalog tab
- Untrack __pycache__/, .db, .claude/ session files

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-07-03 12:45:43 -04:00
parent 3e04c4113f
commit cfd94f905a
47 changed files with 1847 additions and 427 deletions
+13 -5
View File
@@ -793,9 +793,11 @@ document.getElementById('btn-rec-search')?.addEventListener('click', async () =>
// ── MODAL LAMP CATALOG ────────────────────────────────────────────────────
window._lampCache = []; // exposed so the right panel can populate its dropdown
function _lampThresholds(vmin, vmax) {
function _lampThresholds(vmin, vmax, warn_pct, alarm_pct) {
const rng = vmax - vmin;
return { warn: +(vmin + rng * 0.20).toFixed(3), alarm: +(vmin + rng * 0.10).toFixed(3) };
const wp = ((warn_pct ?? 20) / 100);
const ap = ((alarm_pct ?? 10) / 100);
return { warn: +(vmin + rng * wp).toFixed(3), alarm: +(vmin + rng * ap).toFixed(3) };
}
async function loadLamps() {
@@ -844,20 +846,24 @@ window.editLamp = function(id) {
<td><input class="form-input" id="lp-edit-count" type="number" value="${l.lamp_count}" style="width:60px"></td>
<td><input class="form-input" id="lp-edit-vmin" type="number" step="0.1" value="${l.voltage_min}" style="width:70px"></td>
<td><input class="form-input" id="lp-edit-vmax" type="number" step="0.1" value="${l.voltage_max}" style="width:70px"></td>
<td colspan="2" style="font-size:0.7rem;color:var(--text-muted)" id="lp-edit-preview">${l.warn_v} V / ${l.alarm_v} V</td>
<td><input class="form-input" id="lp-edit-wpct" type="number" step="1" min="1" max="50" value="${l.warn_pct ?? 20}" style="width:55px" title="Warn %"></td>
<td><input class="form-input" id="lp-edit-apct" type="number" step="1" min="1" max="50" value="${l.alarm_pct ?? 10}" style="width:55px" title="Alarm %"></td>
<td style="font-size:0.7rem" id="lp-edit-preview">${l.warn_v} V / ${l.alarm_v} V</td>
<td><input class="form-input" id="lp-edit-notes" value="${l.notes || ''}"></td>
<td style="display:flex;gap:4px">
<button class="chart-row-btn" onclick="saveLamp('${id}')">SAVE</button>
<button class="chart-row-btn danger" onclick="renderLampsTable()">CANCEL</button>
</td>`;
// Live preview while editing vmin/vmax
['lp-edit-vmin','lp-edit-vmax'].forEach(i =>
['lp-edit-vmin','lp-edit-vmax','lp-edit-wpct','lp-edit-apct'].forEach(i =>
document.getElementById(i).addEventListener('input', () => {
const vmin = parseFloat(document.getElementById('lp-edit-vmin').value);
const vmax = parseFloat(document.getElementById('lp-edit-vmax').value);
const wpct = parseFloat(document.getElementById('lp-edit-wpct').value);
const apct = parseFloat(document.getElementById('lp-edit-apct').value);
const cell = document.getElementById('lp-edit-preview');
if (isNaN(vmin) || isNaN(vmax) || vmax <= vmin) { cell.textContent = '—'; return; }
const t = _lampThresholds(vmin, vmax);
const t = _lampThresholds(vmin, vmax, wpct, apct);
cell.innerHTML = `<span style="color:var(--yellow)">${t.warn} V</span> / <span style="color:var(--red)">${t.alarm} V</span>`;
}));
};
@@ -870,6 +876,8 @@ window.saveLamp = async function(id) {
lamp_count: parseInt(document.getElementById('lp-edit-count').value) || 1,
voltage_min: parseFloat(document.getElementById('lp-edit-vmin').value),
voltage_max: parseFloat(document.getElementById('lp-edit-vmax').value),
warn_pct: parseFloat(document.getElementById('lp-edit-wpct').value) || 20.0,
alarm_pct: parseFloat(document.getElementById('lp-edit-apct').value) || 10.0,
notes: document.getElementById('lp-edit-notes').value.trim() || null,
};
if (!payload.manufacturer || !payload.model || isNaN(payload.voltage_min) || isNaN(payload.voltage_max)) {