webapp: LAPS clear actually removes the password from session JSON

Previous /imaging/<serial>/laps clear path used update_session() to
re-feed state minus laps_password. But update_session MERGES payload
into existing state - it cannot delete a key the existing state
already has. The laps_password persisted on disk across the "clear"
POST, then came back into the page on next reload.

Fix: bypass update_session for the clear case. Read the session JSON
directly, pop laps_password, write via atomic tempfile-rename. Same
write pattern update_session uses for consistency.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
cproudlock
2026-05-15 07:33:07 -04:00
parent 8debc4ddb3
commit 220c5db5b9

View File

@@ -15,6 +15,7 @@ This file is the route surface; most logic lives in ``services/``:
import json import json
import os import os
import shutil import shutil
import tempfile
from datetime import datetime from datetime import datetime
from pathlib import Path from pathlib import Path
@@ -506,13 +507,29 @@ def imaging_set_laps(serial):
if not isinstance(pw, str): if not isinstance(pw, str):
return {"ok": False, "error": "password must be string"}, 400 return {"ok": False, "error": "password must be string"}, 400
if pw == "": if pw == "":
# Clear by direct read-modify-write since update_session skips empty values. # Clear by direct file write. update_session() merges payload INTO
state = imaging_status.get_session(serial) or {} # existing state and skips empty values, so it cannot remove a key.
# Pop the laps_password key directly from the session JSON and
# write the result atomically.
path = imaging_status._path_for(serial)
if os.path.isfile(path):
try:
with open(path, "r") as f:
state = json.load(f)
except (json.JSONDecodeError, OSError):
state = {}
if "laps_password" in state: if "laps_password" in state:
state.pop("laps_password", None) state.pop("laps_password", None)
# Re-feed everything (minus laps_password) through update_session. state["last_updated"] = imaging_status._now_iso()
state["serial"] = serial fd, tmp = tempfile.mkstemp(dir=config.IMAGING_DIR, prefix=".tmp-", suffix=".json")
imaging_status.update_session(state) try:
with os.fdopen(fd, "w") as f:
json.dump(state, f, indent=2)
os.replace(tmp, path)
except Exception:
try: os.unlink(tmp)
except OSError: pass
raise
return {"ok": True, "cleared": True} return {"ok": True, "cleared": True}
imaging_status.update_session({"serial": serial, "laps_password": pw}) imaging_status.update_session({"serial": serial, "laps_password": pw})
return {"ok": True} return {"ok": True}