Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 21 additions & 3 deletions obs/api/v1/visu.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,24 @@

router = APIRouter(tags=["visu"])

_CAMERA_SECRET_KEYS = frozenset({"password", "apiKeyValue"})


def _sanitize_widget_config(widget: WidgetInstance) -> WidgetInstance:
cfg = dict(widget.config or {})
for key in _CAMERA_SECRET_KEYS:
if key in cfg:
cfg[key] = ""
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep camera secrets for runtime widget rendering

Blanking password and apiKeyValue in _sanitize_widget_config breaks live Kamera widgets that rely on those fields to build authenticated stream URLs (frontend/src/widgets/Kamera/Widget.vue reads props.config.password/apiKeyValue directly). Because sanitization is applied on read paths, cameras using basic/apikey auth can fail immediately at runtime even when no page edit/save occurs.

Useful? React with 👍 / 👎.

return widget.model_copy(update={"config": cfg})


def _sanitize_page_config(page_config: PageConfig | None) -> PageConfig | None:
if page_config is None:
return None
widgets = [_sanitize_widget_config(w) for w in page_config.widgets]
return page_config.model_copy(update={"widgets": widgets})


# ── Hilfsfunktionen ───────────────────────────────────────────────────────────


Expand All @@ -67,7 +85,7 @@ def _row_to_node(row) -> VisuNode:
icon=row["icon"],
access=row["access"],
access_pin=None, # PIN-Hash niemals in der API zurückgeben
page_config=PageConfig(**pc) if pc else None,
page_config=_sanitize_page_config(PageConfig(**pc) if pc else None),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Avoid sanitizing configs in shared DB-to-model mapper

Redacting secrets inside _row_to_node means every internal consumer now receives scrubbed data, not just public API responses. copy_node loads the source via _get_node_or_404 and then persists source.page_config, so copied pages will store password/apiKeyValue as empty strings and Kamera widgets lose working credentials after copy. Sanitization should be applied at response serialization boundaries, not in the shared row-mapping path used for write operations.

Useful? React with 👍 / 👎.

created_at=row["created_at"],
updated_at=row["updated_at"],
)
Expand Down Expand Up @@ -504,7 +522,7 @@ async def get_page(
if not await _check_user_access(db, node_id, user):
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Zugriff verweigert")

return node.page_config or PageConfig()
return _sanitize_page_config(node.page_config) or PageConfig()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve secrets in edit round-trips for page config

GET /pages/{node_id} now always returns a redacted page_config, while save_page still overwrites the full stored JSON with whatever the client posts. In a normal edit flow (load page, change layout, save), the redacted empty password/apiKeyValue values are written back and permanently erase existing Kamera credentials even when the user did not intend to rotate them. This needs either a non-redacted privileged read path or server-side merge logic that retains existing secret fields when clients submit placeholders.

Useful? React with 👍 / 👎.



@router.get("/widget-ref/{page_id}", response_model=list[WidgetInstance])
Expand All @@ -517,7 +535,7 @@ async def get_widget_ref(page_id: str, db: Database = Depends(get_db)):
node = await _get_node_or_404(db, page_id)
if node.type != "PAGE":
raise HTTPException(status_code=400, detail="Knoten ist keine Seite")
pc = node.page_config or PageConfig()
pc = _sanitize_page_config(node.page_config) or PageConfig()
return pc.widgets


Expand Down