- Add _sanitize_banner_html() to src/setup_banner.py: strips <script>/ <iframe> blocks, on* event-handler attributes, and javascript:/data: URI schemes post-render (I-2). Defense-in-depth — /setup is partly anonymous so malformed admin content must not execute in visitors' browsers. - Apply sanitizer in render_setup_banner() before returning rendered HTML. - Add 3 unit tests: test_render_strips_script_tags, test_render_strips_event_handlers, test_render_strips_javascript_uri. - Drop unused Optional import from src/repositories/welcome_template.py and src/repositories/setup_banner.py (M-6).
53 lines
1.9 KiB
Python
53 lines
1.9 KiB
Python
"""Repository for the per-instance welcome-prompt override (singleton row)."""
|
|
|
|
from datetime import datetime, timezone
|
|
from typing import Any
|
|
|
|
import duckdb
|
|
|
|
|
|
class WelcomeTemplateRepository:
|
|
def __init__(self, conn: duckdb.DuckDBPyConnection):
|
|
self.conn = conn
|
|
|
|
def get(self) -> dict[str, Any]:
|
|
"""Return the singleton row. Always exists post-migration; content
|
|
is None when no override is set (= use shipped default)."""
|
|
row = self.conn.execute(
|
|
"SELECT id, content, updated_at, updated_by FROM welcome_template WHERE id = 1"
|
|
).fetchone()
|
|
if row is None:
|
|
# Defensive: re-seed if a previous admin manually deleted it.
|
|
self.conn.execute(
|
|
"INSERT INTO welcome_template (id, content) VALUES (1, NULL) "
|
|
"ON CONFLICT (id) DO NOTHING"
|
|
)
|
|
return {"id": 1, "content": None, "updated_at": None, "updated_by": None}
|
|
return {
|
|
"id": row[0],
|
|
"content": row[1],
|
|
"updated_at": row[2],
|
|
"updated_by": row[3],
|
|
}
|
|
|
|
def set(self, content: str, *, updated_by: str) -> None:
|
|
now = datetime.now(timezone.utc)
|
|
self.conn.execute(
|
|
"""INSERT INTO welcome_template (id, content, updated_at, updated_by)
|
|
VALUES (1, ?, ?, ?)
|
|
ON CONFLICT (id) DO UPDATE SET
|
|
content = excluded.content,
|
|
updated_at = excluded.updated_at,
|
|
updated_by = excluded.updated_by""",
|
|
[content, now, updated_by],
|
|
)
|
|
|
|
def reset(self, *, updated_by: str) -> None:
|
|
"""Clear override; renderer falls back to shipped default."""
|
|
now = datetime.now(timezone.utc)
|
|
self.conn.execute(
|
|
"""UPDATE welcome_template
|
|
SET content = NULL, updated_at = ?, updated_by = ?
|
|
WHERE id = 1""",
|
|
[now, updated_by],
|
|
)
|