Skip to content

feat(pi-ext-auth): send X-Admin-Token from adminFetch - #151

Merged
BrettKinny merged 1 commit into
mainfrom
audit/pi-ext-admin-auth
Jun 6, 2026
Merged

BrettKinny merged 1 commit into
mainfrom
audit/pi-ext-admin-auth

Conversation

@BrettKinny

Copy link
Copy Markdown
Owner

Admin-auth epic — part 3 of 4 (dotty-pi-ext caller). Pairs with the permissive /xiaozhi/admin/* middleware (#149). Off main.

adminFetch merges an X-Admin-Token header (from DOTTY_ADMIN_TOKEN, read at module load) into any caller-supplied headers when the token is set; untouched when unset. Single seam covers every admin call (fetchSongCatalog, playAsset, MCP dispatch). Replaces the now-stale "we don't do admin auth" comment.

Tests: new tests/admin_auth.test.ts (wired into npm test) — sets the env before a dynamic import (ESM hoists static imports), asserts X-Admin-Token is sent and the caller content-type header is preserved. Existing play_song test unaffected.

⚠️ Deploying this needs a dotty-pi image rebuild (TS). Foundation only — no behavior change until the enforcement flip.

Part of: #149 (server), #150 (behaviour). Remaining: bridge (~8 sites).

🤖 Generated with Claude Code

Admin-auth epic, part 3 of 4 (dotty-pi-ext caller). Pairs with the permissive
/xiaozhi/admin/* middleware (part 1). adminFetch now merges an X-Admin-Token
header (from DOTTY_ADMIN_TOKEN, read at module load) into any caller-supplied
headers when the token is set; untouched when unset. Covers every admin call
(fetchSongCatalog, playAsset, MCP dispatch) via the single seam. Replaces the
stale "we don't do admin auth" comment.

Tests: new tests/admin_auth.test.ts (wired into `npm test`) — sets the env
before a dynamic import, asserts X-Admin-Token is sent and the caller
content-type header is preserved. Existing play_song test unaffected.

Note: deploying this needs a dotty-pi image rebuild (TS). Part of the
foundation; no behavior change until the enforcement flip.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 5, 2026 10:54

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Adds caller-side support in dotty-pi-ext for the new /xiaozhi/admin/* admin-auth middleware by having the shared adminFetch attach an X-Admin-Token header when DOTTY_ADMIN_TOKEN is set, plus a small test to ensure header merging preserves caller headers.

Changes:

  • Attach X-Admin-Token in adminFetch when DOTTY_ADMIN_TOKEN is set.
  • Add a new Node/TS test that dynamically imports the module after setting the env var and asserts header behavior.
  • Wire the new test into npm test via a new test:adminauth script.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
dotty-pi-ext/src/lib/xiaozhi_admin.ts Adds DOTTY_ADMIN_TOKEN handling and header merge behavior in adminFetch.
dotty-pi-ext/tests/admin_auth.test.ts New test validating token header injection and preservation of caller content-type.
dotty-pi-ext/package.json Adds test:adminauth and includes it in the main test chain.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +42 to +47
// Merge the admin token into any caller-supplied headers (e.g. content-type)
// when DOTTY_ADMIN_TOKEN is set; otherwise leave headers untouched.
const headers = new Headers(init.headers);
if (ADMIN_TOKEN) headers.set("X-Admin-Token", ADMIN_TOKEN);
try {
return await fetch(url, { ...init, signal: ac.signal });
return await fetch(url, { ...init, headers, signal: ac.signal });
Comment on lines +30 to +36
try {
await playAsset("/opt/xiaozhi-esp32-server/config/assets/songs/x.opus");
assertEq("X-Admin-Token sent when DOTTY_ADMIN_TOKEN set", token, "test-token");
assertEq("caller content-type header preserved", contentType, "application/json");
} finally {
globalThis.fetch = original;
}
@BrettKinny
BrettKinny merged commit f76f538 into main Jun 6, 2026
10 checks passed
@BrettKinny
BrettKinny deleted the audit/pi-ext-admin-auth branch June 6, 2026 11:16
BrettKinny added a commit that referenced this pull request Jun 6, 2026
Admin-auth epic, part 4 of 4 (bridge caller — the scattered one). Pairs with the
permissive /xiaozhi/admin/* middleware (part 1). Adds a `_xiaozhi_admin_headers()`
helper (reads DOTTY_ADMIN_TOKEN into a module-level `_ADMIN_TOKEN`) to both
bridge.py and bridge/dashboard.py, threaded through every admin call site:

- bridge.py: _dispatch_abort / _dispatch_set_state / _dispatch_set_toggle and the
  dashboard _dashboard_abort_device / _dashboard_inject_to_device helpers (5 POSTs).
- dashboard.py: _xiaozhi_device_count + _xiaozhi_list_songs (urllib GETs, now via
  urllib.request.Request with headers) and play_song's play-asset POST (3 sites).

Calls to dotty-behaviour (vision photo proxy, perception getters) are deliberately
NOT given the header. Header only sent when the token is set — no-op until the flip.

Tests: new tests/test_admin_headers.py (6 cases) — both helpers set/unset, and an
integration through _dispatch_abort capturing the headers kwarg. Full tests/ 71
passed; ruff clean.

Completes the foundation: #149 (server) + #150 (behaviour) + #151 (pi-ext) + this.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
BrettKinny added a commit that referenced this pull request Jun 6, 2026
…152)

Admin-auth epic, part 4 of 4 (bridge caller — the scattered one). Pairs with the
permissive /xiaozhi/admin/* middleware (part 1). Adds a `_xiaozhi_admin_headers()`
helper (reads DOTTY_ADMIN_TOKEN into a module-level `_ADMIN_TOKEN`) to both
bridge.py and bridge/dashboard.py, threaded through every admin call site:

- bridge.py: _dispatch_abort / _dispatch_set_state / _dispatch_set_toggle and the
  dashboard _dashboard_abort_device / _dashboard_inject_to_device helpers (5 POSTs).
- dashboard.py: _xiaozhi_device_count + _xiaozhi_list_songs (urllib GETs, now via
  urllib.request.Request with headers) and play_song's play-asset POST (3 sites).

Calls to dotty-behaviour (vision photo proxy, perception getters) are deliberately
NOT given the header. Header only sent when the token is set — no-op until the flip.

Tests: new tests/test_admin_headers.py (6 cases) — both helpers set/unset, and an
integration through _dispatch_abort capturing the headers kwarg. Full tests/ 71
passed; ruff clean.

Completes the foundation: #149 (server) + #150 (behaviour) + #151 (pi-ext) + this.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants