From 7b5ea2b29ea111936e65b745cbfbde054209b57f Mon Sep 17 00:00:00 2001 From: Joshua Date: Sun, 20 Sep 2026 19:44:30 -0400 Subject: [PATCH 1/5] Document the TouchHub setup runbook --- README.md | 58 ++++++++++++++++++++++++++++++++++++++++++ tests/test_frontend.py | 10 ++++++++ 2 files changed, 68 insertions(+) diff --git a/README.md b/README.md index fd29aa7..884ba92 100644 --- a/README.md +++ b/README.md @@ -155,6 +155,64 @@ Sensors the automations read: `sensor.nestquest__quests_due_today` `..._quests_completed_today`, and `binary_sensor.nestquest__present_today`. +## TouchHub setup + +Run these steps in order to display the NestQuest panel on a TouchHub +wall device. The panel is an HA Lovelace dashboard, so the kiosk browser +is simply a Home Assistant client. + +1. **Allow the panel to be framed.** The NestQuest Lovelace panel card + embeds a Home Assistant iframe, which Home Assistant blocks by default. + Add the following to `configuration.yaml` and save it: + + ```yaml + http: + use_x_frame_options: false + ``` + +2. **Restart Home Assistant.** Use *Settings → System → Restart* and pick + **Restart Home Assistant** (a full restart, not a YAML reload) — the + `http:` setting is only applied at startup. + +3. **Create a dedicated kiosk user.** In *Settings → People → Users*, add + a new user for the wall device. Make it **non-admin**, and do **not** + add it to the NestQuest admin allowlist. This is the account the panel + runs as, and it should have no authority beyond viewing the dashboard. + +4. **Build a dashboard with only the NestQuest panel card.** Create a new + Lovelace dashboard that contains nothing but the NestQuest panel card. + Keep the surface minimal — no other cards, no admin controls. + +5. **Point TouchHub at it.** In the TouchHub launcher, add the **Home + Assistant Lovelace** app to the dock and paste the dashboard URL from + step 4. TouchHub loads that URL in its kiosk browser. + +6. **Log the kiosk session in.** Sign in on the device as the dedicated + kiosk user from step 3, and leave that session logged in. + +### The clickjacking trade-off + +`use_x_frame_options: false` removes the `X-Frame-Options` response +header, which is what stops Home Assistant from being embedded in a frame +on another site. Disabling it is what lets the panel card iframe Home +Assistant — but it also lets **any** page frame Home Assistant, so a +malicious site could render the HA UI invisibly and trick a logged-in +user into clicking controls they did not mean to click. That is +clickjacking. + +This is an accepted, scoped risk for a wall display, and it is contained +by how the kiosk account and dashboard are set up. Two mitigations: + +- The kiosk user is non-admin and is **not in the NestQuest admin + allowlist**, so even a hijacked click cannot reach admin-only + operations or other parts of Home Assistant. +- The dashboard **contains only the NestQuest panel card**, so there are + no unrelated controls on screen for a clickjacking overlay to weaponise. + +A third, optional hardening is to keep the kiosk browser on a trusted +device and network, and to leave the kiosk session logged in only on that +device. + ## Developer Setup and Testing This project uses [`uv`](https://github.com/astral-sh/uv) for fast Python package and dependency management. diff --git a/tests/test_frontend.py b/tests/test_frontend.py index 59c5989..27bb57d 100644 --- a/tests/test_frontend.py +++ b/tests/test_frontend.py @@ -163,3 +163,13 @@ def test_frontend_and_www_have_no_cdn_font_references() -> None: text = path.read_text(encoding="utf-8", errors="ignore") for marker in CDN_MARKERS: assert marker not in text, f"{path}: {marker}" + + +def test_readme_documents_the_touchhub_setup_runbook() -> None: + readme = " ".join( + (REPO_ROOT / "README.md").read_text(encoding="utf-8").split() + ) + for required in ("use_x_frame_options", "TouchHub", "kiosk", "clickjacking"): + assert required in readme, required + assert "not in the NestQuest admin allowlist" in readme + assert "contains only the NestQuest panel card" in readme -- 2.55.0 From 02f78b884d883a7ca14e5d5d3648e609ee33585a Mon Sep 17 00:00:00 2001 From: Joshua Date: Sun, 20 Sep 2026 20:05:49 -0400 Subject: [PATCH 2/5] Drop the unnecessary X-Frame-Options runbook step --- README.md | 58 ++++++++++-------------------------------- tests/test_frontend.py | 4 ++- 2 files changed, 16 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index 884ba92..7c59ffe 100644 --- a/README.md +++ b/README.md @@ -161,57 +161,25 @@ Run these steps in order to display the NestQuest panel on a TouchHub wall device. The panel is an HA Lovelace dashboard, so the kiosk browser is simply a Home Assistant client. -1. **Allow the panel to be framed.** The NestQuest Lovelace panel card - embeds a Home Assistant iframe, which Home Assistant blocks by default. - Add the following to `configuration.yaml` and save it: - - ```yaml - http: - use_x_frame_options: false - ``` - -2. **Restart Home Assistant.** Use *Settings → System → Restart* and pick - **Restart Home Assistant** (a full restart, not a YAML reload) — the - `http:` setting is only applied at startup. - -3. **Create a dedicated kiosk user.** In *Settings → People → Users*, add - a new user for the wall device. Make it **non-admin**, and do **not** - add it to the NestQuest admin allowlist. This is the account the panel +1. **Create a dedicated kiosk user.** In *Settings → People → Users*, add + a new user for the wall device. Make it **non-admin**, and ensure it is + not in the NestQuest admin allowlist. This is the account the panel runs as, and it should have no authority beyond viewing the dashboard. -4. **Build a dashboard with only the NestQuest panel card.** Create a new - Lovelace dashboard that contains nothing but the NestQuest panel card. - Keep the surface minimal — no other cards, no admin controls. +2. **Build a dashboard with only the NestQuest panel card.** Create a new + Lovelace dashboard that contains only the NestQuest panel card. Keep + the surface minimal — no other cards, no admin controls. -5. **Point TouchHub at it.** In the TouchHub launcher, add the **Home +3. **Point TouchHub at it.** In the TouchHub launcher, add the **Home Assistant Lovelace** app to the dock and paste the dashboard URL from - step 4. TouchHub loads that URL in its kiosk browser. + step 2. -6. **Log the kiosk session in.** Sign in on the device as the dedicated - kiosk user from step 3, and leave that session logged in. +4. **Log the kiosk session in.** Sign in on the device as the dedicated + kiosk user from step 1, and leave that session logged in. -### The clickjacking trade-off - -`use_x_frame_options: false` removes the `X-Frame-Options` response -header, which is what stops Home Assistant from being embedded in a frame -on another site. Disabling it is what lets the panel card iframe Home -Assistant — but it also lets **any** page frame Home Assistant, so a -malicious site could render the HA UI invisibly and trick a logged-in -user into clicking controls they did not mean to click. That is -clickjacking. - -This is an accepted, scoped risk for a wall display, and it is contained -by how the kiosk account and dashboard are set up. Two mitigations: - -- The kiosk user is non-admin and is **not in the NestQuest admin - allowlist**, so even a hijacked click cannot reach admin-only - operations or other parts of Home Assistant. -- The dashboard **contains only the NestQuest panel card**, so there are - no unrelated controls on screen for a clickjacking overlay to weaponise. - -A third, optional hardening is to keep the kiosk browser on a trusted -device and network, and to leave the kiosk session logged in only on that -device. +TouchHub loads the dashboard URL directly. The NestQuest cards are normal +Lovelace cards, not an iframe, so no Home Assistant framing setting is +needed. ## Developer Setup and Testing diff --git a/tests/test_frontend.py b/tests/test_frontend.py index 27bb57d..b8acba3 100644 --- a/tests/test_frontend.py +++ b/tests/test_frontend.py @@ -169,7 +169,9 @@ def test_readme_documents_the_touchhub_setup_runbook() -> None: readme = " ".join( (REPO_ROOT / "README.md").read_text(encoding="utf-8").split() ) - for required in ("use_x_frame_options", "TouchHub", "kiosk", "clickjacking"): + for required in ("TouchHub", "kiosk"): assert required in readme, required assert "not in the NestQuest admin allowlist" in readme assert "contains only the NestQuest panel card" in readme + assert "use_x_frame_options" not in readme + assert "clickjacking" not in readme -- 2.55.0 From 1a2775adfcbfad65c4324731ee877d60430f38bf Mon Sep 17 00:00:00 2001 From: Joshua Date: Sun, 20 Sep 2026 20:11:35 -0400 Subject: [PATCH 3/5] Correct the kiosk account permissions and document both panel card views --- README.md | 36 +++++++++++++++++++++++++++++++----- tests/test_frontend.py | 2 ++ 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 7c59ffe..4de8a39 100644 --- a/README.md +++ b/README.md @@ -163,12 +163,38 @@ is simply a Home Assistant client. 1. **Create a dedicated kiosk user.** In *Settings → People → Users*, add a new user for the wall device. Make it **non-admin**, and ensure it is - not in the NestQuest admin allowlist. This is the account the panel - runs as, and it should have no authority beyond viewing the dashboard. + not in the NestQuest admin allowlist. The allowlist blocks the NestQuest + **admin** operations — `uncomplete_quest`, `create_quest_definition`, + `update_quest_definition`, `set_quest_definition_active`, + `set_presence_pattern`, `create_presence_override`, + `delete_presence_override`, `export_history_csv`, and `manage_child` + (plus `regenerate` flows) — it does not make the account view-only. The + kiosk user *can* call `nestquest.complete_quest`, the one open NestQuest + service, to complete quests from the panel, and as a non-admin Home + Assistant user it may also reach other permitted Home Assistant entities + and services. -2. **Build a dashboard with only the NestQuest panel card.** Create a new - Lovelace dashboard that contains only the NestQuest panel card. Keep - the surface minimal — no other cards, no admin controls. +2. **Build a dashboard with only the NestQuest panel cards.** A functional + panel needs **two** Lovelace views, each with its own card config from + `design/ENTITIES-AND-SERVICES.md` §4. The dashboard contains only the + NestQuest panel card for each view — no other cards, no admin controls. + + - **View 1 — party board** (`type: custom:nestquest-party-board-card`): + the household quest board. Set `weather_entity` (optional; the dock + falls back to date/time), `quest_log_path: /nestquest/log` (the view + the crest tap navigates to), and `child_order: [slug1, slug2, slug3]` + listing each child's slug in display order. + + - **View 2 — quest log** (`type: custom:nestquest-quest-log-card`): the + per-child quest screen the party board hops to. Set + `board_path: /nestquest/board` (the party-board view to return to), + `weather_entity`, `idle_return_seconds` (must be shorter than + TouchHub Auto-Return), `confirm_timeout_seconds`, and + `complete_screen_seconds`. The quest log reads the selected child's + slug from the view's URL path, so the board → log hop is a plain + Lovelace navigation. + + See `design/ENTITIES-AND-SERVICES.md` §4 for the full card YAML. 3. **Point TouchHub at it.** In the TouchHub launcher, add the **Home Assistant Lovelace** app to the dock and paste the dashboard URL from diff --git a/tests/test_frontend.py b/tests/test_frontend.py index b8acba3..e6cd580 100644 --- a/tests/test_frontend.py +++ b/tests/test_frontend.py @@ -173,5 +173,7 @@ def test_readme_documents_the_touchhub_setup_runbook() -> None: assert required in readme, required assert "not in the NestQuest admin allowlist" in readme assert "contains only the NestQuest panel card" in readme + assert "nestquest-party-board-card" in readme + assert "nestquest-quest-log-card" in readme assert "use_x_frame_options" not in readme assert "clickjacking" not in readme -- 2.55.0 From 353ad82c7e76e2068cdeb1095b48df2f69492e59 Mon Sep 17 00:00:00 2001 From: Joshua Date: Sun, 20 Sep 2026 20:14:01 -0400 Subject: [PATCH 4/5] Document the required dashboard and view URL paths --- README.md | 54 ++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 36 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 4de8a39..89ffe71 100644 --- a/README.md +++ b/README.md @@ -174,31 +174,49 @@ is simply a Home Assistant client. Assistant user it may also reach other permitted Home Assistant entities and services. -2. **Build a dashboard with only the NestQuest panel cards.** A functional - panel needs **two** Lovelace views, each with its own card config from - `design/ENTITIES-AND-SERVICES.md` §4. The dashboard contains only the - NestQuest panel card for each view — no other cards, no admin controls. +2. **Build a dashboard with only the NestQuest panel cards.** Create a + Lovelace dashboard whose **URL slug is `nestquest`** (Settings → + Dashboards → Edit → URL slug). A functional panel needs **two** views + in that dashboard, each with its own card config from + `design/ENTITIES-AND-SERVICES.md` §4. Give the views these exact URL + paths (the path segment after the dashboard slug): - - **View 1 — party board** (`type: custom:nestquest-party-board-card`): - the household quest board. Set `weather_entity` (optional; the dock - falls back to date/time), `quest_log_path: /nestquest/log` (the view - the crest tap navigates to), and `child_order: [slug1, slug2, slug3]` - listing each child's slug in display order. + - **View 1 path `board`** (URL `/nestquest/board`) — party board + (`type: custom:nestquest-party-board-card`): the household quest + board. Set `weather_entity` (optional; the dock falls back to + date/time), `quest_log_path: /nestquest/log` (the view the crest tap + navigates to), and `child_order: [slug1, slug2, slug3]` listing each + child's slug in display order. - - **View 2 — quest log** (`type: custom:nestquest-quest-log-card`): the - per-child quest screen the party board hops to. Set - `board_path: /nestquest/board` (the party-board view to return to), - `weather_entity`, `idle_return_seconds` (must be shorter than - TouchHub Auto-Return), `confirm_timeout_seconds`, and - `complete_screen_seconds`. The quest log reads the selected child's - slug from the view's URL path, so the board → log hop is a plain - Lovelace navigation. + - **View 2 path `log`** (URL `/nestquest/log`) — quest log + (`type: custom:nestquest-quest-log-card`): the per-child quest screen + the party board hops to. Set `board_path: /nestquest/board` (the + party-board view to return to), `weather_entity`, + `idle_return_seconds` (must be shorter than TouchHub Auto-Return), + `confirm_timeout_seconds`, and `complete_screen_seconds`. The quest + log reads the selected child's slug from the view's URL path, so the + board → log hop is a plain Lovelace navigation. + + The dashboard contains only the NestQuest panel card for each view — + no other cards, no admin controls. + + The `quest_log_path` and `board_path` values must match the dashboard + slug and view paths above: `//`. If you chose `nestquest` + as the dashboard slug with view paths `board` and `log`, the card + configs are already correct. If you pick different names, substitute + your actual paths in **both** card configs — e.g. a dashboard slug + `chores` with view paths `main` and `history` needs + `quest_log_path: /chores/history` and `board_path: /chores/main`. + Navigation between the two views fails (the crest tap or back button + lands on a 404) if the card config paths do not match the dashboard + slug and view paths exactly. See `design/ENTITIES-AND-SERVICES.md` §4 for the full card YAML. 3. **Point TouchHub at it.** In the TouchHub launcher, add the **Home Assistant Lovelace** app to the dock and paste the dashboard URL from - step 2. + step 2 — the party-board view URL, e.g. `/nestquest/board` (TouchHub + loads that URL directly as the kiosk landing page). 4. **Log the kiosk session in.** Sign in on the device as the dedicated kiosk user from step 1, and leave that session logged in. -- 2.55.0 From e7148c4184c855a8deebe8ec328b131b3bde669a Mon Sep 17 00:00:00 2001 From: Joshua Date: Sun, 20 Sep 2026 20:18:01 -0400 Subject: [PATCH 5/5] Set both panel views to Panel layout and document TouchHub Auto-Return --- README.md | 15 ++++++++++++--- tests/test_frontend.py | 2 ++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 89ffe71..a590c80 100644 --- a/README.md +++ b/README.md @@ -197,8 +197,13 @@ is simply a Home Assistant client. log reads the selected child's slug from the view's URL path, so the board → log hop is a plain Lovelace navigation. - The dashboard contains only the NestQuest panel card for each view — - no other cards, no admin controls. + Each view's layout MUST be set to **Panel** (Edit Dashboard → pencil on the + view → View type: Panel). Home Assistant's default Masonry layout constrains + the 1080px-tall NestQuest card to a narrow column; Panel (single-card) + layout is what renders the card full-screen at 1920×1080. + + The dashboard contains only the NestQuest panel card for each view — + no other cards, no admin controls. The `quest_log_path` and `board_path` values must match the dashboard slug and view paths above: `//`. If you chose `nestquest` @@ -216,7 +221,11 @@ is simply a Home Assistant client. 3. **Point TouchHub at it.** In the TouchHub launcher, add the **Home Assistant Lovelace** app to the dock and paste the dashboard URL from step 2 — the party-board view URL, e.g. `/nestquest/board` (TouchHub - loads that URL directly as the kiosk landing page). + loads that URL directly as the kiosk landing page). Set TouchHub + **Auto-Return** to a value GREATER than the quest log's + `idle_return_seconds` (default 40s) — recommend **60s or higher**. If + Auto-Return is at or below 40s, TouchHub returns to its launcher before + the quest log's idle return can navigate back to the party board. 4. **Log the kiosk session in.** Sign in on the device as the dedicated kiosk user from step 1, and leave that session logged in. diff --git a/tests/test_frontend.py b/tests/test_frontend.py index e6cd580..3fc7e94 100644 --- a/tests/test_frontend.py +++ b/tests/test_frontend.py @@ -175,5 +175,7 @@ def test_readme_documents_the_touchhub_setup_runbook() -> None: assert "contains only the NestQuest panel card" in readme assert "nestquest-party-board-card" in readme assert "nestquest-quest-log-card" in readme + assert "Panel" in readme + assert "Auto-Return" in readme assert "use_x_frame_options" not in readme assert "clickjacking" not in readme -- 2.55.0