fix(reports): code-review findings — XSS, SMTP, blocking, unit link, email guard
- #1 XSS: escape user-controlled values (location name, baseline values, recent- report fields, SMTP status message) in the modals via the existing _mergeEsc helper — they were concatenated raw into innerHTML (stored XSS via location name). - #2 SMTP: an unrecognized REPORT_SMTP_SECURITY no longer silently downgrades to a plaintext connection while still calling login() — it falls back to starttls and warns; warn on intentional security=none + auth. - #3 scheduler: run the (blocking smtplib + Excel) nightly report in a worker thread (asyncio.to_thread + its own DB session) so it can't stall the loop that drives time-sensitive device cycles. New _run_one_report helper. - #4 cycle ingest: set unit_id on the ingested data session (ingest_nrl_zip leaves it None) before dropping the empty placeholder, preserving the unit<->session link; repoint old_session_id at the real row. - #7 robustness: wrap send_report_email in the orchestrator and run_nightly_report in /view + /run so a render/SMTP error returns a clean error instead of a raw 500 after artifacts are written. Verified: SMTP paths (typo->starttls, none, starttls, ssl), off-thread tick stamps last_run_date + writes the file, /view 200, escaping wired, app imports. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+22
-10
@@ -200,11 +200,17 @@ async def view_nightly_report(
|
||||
):
|
||||
"""Render the night report and return the HTML inline (preview — no write, no email)."""
|
||||
nd, bmode, bs, be, metric_keys = _resolve_params(project_id, db, night_date, baseline_start, baseline_end, metrics)
|
||||
result = run_nightly_report(
|
||||
db, project_id, nd,
|
||||
metric_keys=metric_keys, baseline_mode=bmode, baseline_start=bs, baseline_end=be,
|
||||
send=False, # preview: no email
|
||||
)
|
||||
try:
|
||||
result = run_nightly_report(
|
||||
db, project_id, nd,
|
||||
metric_keys=metric_keys, baseline_mode=bmode, baseline_start=bs, baseline_end=be,
|
||||
send=False, # preview: no email
|
||||
)
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e: # noqa: BLE001
|
||||
logger.error("nightly/view failed for %s (%s): %s", project_id, nd, e, exc_info=True)
|
||||
raise HTTPException(status_code=500, detail=f"Report generation failed: {e}")
|
||||
return HTMLResponse(result["html"])
|
||||
|
||||
|
||||
@@ -224,11 +230,17 @@ async def run_nightly_report_endpoint(
|
||||
is omitted from the JSON response (it's large and on disk); use /view to see it.
|
||||
"""
|
||||
nd, bmode, bs, be, metric_keys = _resolve_params(project_id, db, night_date, baseline_start, baseline_end, metrics)
|
||||
result = run_nightly_report(
|
||||
db, project_id, nd,
|
||||
metric_keys=metric_keys, baseline_mode=bmode, baseline_start=bs, baseline_end=be,
|
||||
send=send,
|
||||
)
|
||||
try:
|
||||
result = run_nightly_report(
|
||||
db, project_id, nd,
|
||||
metric_keys=metric_keys, baseline_mode=bmode, baseline_start=bs, baseline_end=be,
|
||||
send=send,
|
||||
)
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e: # noqa: BLE001
|
||||
logger.error("nightly/run failed for %s (%s): %s", project_id, nd, e, exc_info=True)
|
||||
raise HTTPException(status_code=500, detail=f"Report generation failed: {e}")
|
||||
result.pop("html", None) # keep the JSON response lean — view it via /view or the file
|
||||
result["view_url"] = (
|
||||
f"/api/projects/{project_id}/reports/nightly/view"
|
||||
|
||||
Reference in New Issue
Block a user