docs: e2e suite writeup, update CLAUDE.md/README with OIDC verification status
CI / web (push) Successful in 17s
CI / api (push) Successful in 23s

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-12 22:30:21 -06:00
parent 294be26500
commit 13f69ea1aa
3 changed files with 74 additions and 15 deletions
+26 -11
View File
@@ -40,6 +40,17 @@ One login instead of logging into Proxmox, Zabbix, OMV, and every service separa
- Auth: local (bcrypt + signed session cookie) is **always** available — it's not an exclusive mode - Auth: local (bcrypt + signed session cookie) is **always** available — it's not an exclusive mode
switch. OIDC via Authentik is an *additional* sign-in button, live when `OIDC_ENABLED=true`. See switch. OIDC via Authentik is an *additional* sign-in button, live when `OIDC_ENABLED=true`. See
`docs/oidc-setup.md`. Deliberately additive so OIDC config problems can never lock out the admin login. `docs/oidc-setup.md`. Deliberately additive so OIDC config problems can never lock out the admin login.
Two real bugs found only once someone actually clicked the button (fixed, see `docs/oidc-setup.md` and
`e2e/README.md`): `OIDC_ISSUER_URL` pointed at Authentik's LAN IP, which got baked into the
browser-facing `authorization_endpoint`; and the callback hardcoded `http://` when reconstructing the
current URL (Fastify never sees HTTPS, it terminates upstream), sending the wrong scheme as
`redirect_uri` in the token exchange. Both are exactly the class of bug curl-based verification cannot
catch — real redirect chains and a real IdP login form need a real browser.
- **`e2e/`** (Playwright, done): drives a real browser against the **live** deployment, not a local dev
server. `local-login.spec.ts` and `oidc-login.spec.ts` — the latter uses a dedicated Authentik test
account (`playwright-test`, blueprint-provisioned, path `users/service-accounts`, never a real personal
login) so the full OIDC flow can run unattended. Not wired into CI (hits production, needs secrets) —
run by hand after auth-related changes. See `e2e/README.md` for the bugs it already caught.
- **Device discovery** (done, separate from the collector system above — it's inventory, not per-host - **Device discovery** (done, separate from the collector system above — it's inventory, not per-host
metrics): `scripts/discover-devices.sh` runs via systemd timer **on the CT122 host**, not in Docker metrics): `scripts/discover-devices.sh` runs via systemd timer **on the CT122 host**, not in Docker
(real ARP entries live in the host's network namespace, not Docker's bridge network). API reads the (real ARP entries live in the host's network namespace, not Docker's bridge network). API reads the
@@ -60,7 +71,11 @@ One login instead of logging into Proxmox, Zabbix, OMV, and every service separa
(gitignored, not in the repo). Public key installed on `omv` and `ripper` with a forced command — (gitignored, not in the repo). Public key installed on `omv` and `ripper` with a forced command —
see `docs/ssh-collector-key-setup.md`. see `docs/ssh-collector-key-setup.md`.
- **Authentik OIDC provider**: set up via an Authentik blueprint on CT121 (not a manual UI step) — see - **Authentik OIDC provider**: set up via an Authentik blueprint on CT121 (not a manual UI step) — see
`docs/oidc-setup.md`. Purely additive, didn't touch any existing Authentik user/credential. `docs/oidc-setup.md`. Purely additive, didn't touch any existing Authentik user/credential. Issuer URL
is the public `https://auth.jerodrigged.com` (a pre-existing NPM proxy host, predates this project) —
**never point this at the LAN IP**, see the bug writeup above.
- **Authentik test account**: `playwright-test` (blueprint-provisioned, `authentik_core.user`, path
`users/service-accounts`), used only by `e2e/tests/oidc-login.spec.ts`. Not a real person's login.
- **Public exposure (done, issue #13 closed)**: `https://monitor.jerodrigged.com` is live. Chain is - **Public exposure (done, issue #13 closed)**: `https://monitor.jerodrigged.com` is live. Chain is
Cloudflare Tunnel (existing tunnel `ac3a4440-...`, added a Public Hostname pointing at NPM, not Cloudflare Tunnel (existing tunnel `ac3a4440-...`, added a Public Hostname pointing at NPM, not
directly at the dashboard) → NPM proxy host id 14 (`192.168.1.185:80``192.168.1.103:8090`) → app. directly at the dashboard) → NPM proxy host id 14 (`192.168.1.185:80``192.168.1.103:8090`) → app.
@@ -74,9 +89,6 @@ One login instead of logging into Proxmox, Zabbix, OMV, and every service separa
OIDC login still works if the tunnel/DNS is ever down. OIDC login still works if the tunnel/DNS is ever down.
- **Vaultwarden**: dashboard admin credentials saved as "Homelab Monitor (dashboard admin)". OIDC client - **Vaultwarden**: dashboard admin credentials saved as "Homelab Monitor (dashboard admin)". OIDC client
credentials not yet saved there (pending a `BW_SESSION` from the user). credentials not yet saved there (pending a `BW_SESSION` from the user).
- **Not yet verified**: nobody has actually clicked "Sign in with Authentik" through a real browser —
the redirect chain and params are confirmed correct via curl, but the final interactive consent/login
step needs a human.
## Zabbix: intentionally skipped, not blocked ## Zabbix: intentionally skipped, not blocked
@@ -88,16 +100,19 @@ integration entirely rather than finish configuring a service that's being repla
issue #11 is closed as won't-do — if a metrics-alerting panel is wanted later, it should target issue #11 is closed as won't-do — if a metrics-alerting panel is wanted later, it should target
Grafana/Prometheus once `homelab#15` lands, as a new issue, not a reopening of #11. Grafana/Prometheus once `homelab#15` lands, as a new issue, not a reopening of #11.
## Status: original backlog complete ## Status: original backlog complete, OIDC verified end-to-end
All 14 issues filed at project start are closed (10 shipped, #11/Zabbix closed as won't-do, no others All 14 issues filed at project start are closed (10 shipped, #11/Zabbix closed as won't-do, no others
skipped). `GET .../issues?state=open` on this repo should return empty. If picking this up again with skipped). `GET .../issues?state=open` on this repo should return empty. First real user click-through of
no specific ask from the user, there's no queued work — check in with them for what's next rather than "Sign in with Authentik" surfaced two bugs (LAN-IP issuer, wrong-scheme redirect_uri in token exchange —
inventing scope. Remaining known gaps, none blocking: both above), fixed, and now covered by `e2e/oidc-login.spec.ts` as regression coverage. Both local and
OIDC login are Playwright-verified passing (5 consecutive runs) as of this writing.
If picking this up again with no specific ask from the user, there's no queued work — check in with them
for what's next rather than inventing scope. Remaining known gaps, none blocking:
- Nobody has clicked "Sign in with Authentik" through an actual browser yet (see note above) — worth
confirming next time a human is at the dashboard.
- OIDC client credentials aren't in Vaultwarden yet (Proxmox token and admin password are) — needs a - OIDC client credentials aren't in Vaultwarden yet (Proxmox token and admin password are) — needs a
`BW_SESSION` from the user, see prior session's request in the conversation history if resuming soon. `BW_SESSION` from the user.
- `diskPaths` in `hosts.yaml` vs. the remote forced-command scripts on omv/ripper is a manual-sync point - `diskPaths` in `hosts.yaml` vs. the remote forced-command scripts on omv/ripper is a manual-sync point
(documented, not automated — only 2 hosts, hasn't been worth it). (documented, not automated — only 2 hosts, hasn't been worth it).
- `e2e/` isn't wired into CI — run by hand (`cd e2e && npm test`) after any auth-related change.
+4 -4
View File
@@ -1,12 +1,12 @@
# Homelab Monitor # Homelab Monitor
A single dashboard for the health of everything in the homelab (documented in A single dashboard for the health of everything in the homelab (documented in
[`jhodgkin/homelab`](https://git.jerodrigged.com/jhodgkin/homelab)): Proxmox host + LXC [`jhodgkin/homelab`](https://git.jerodrigged.com/jhodgkin/homelab)): Proxmox host + LXC + bare-metal
CPU/mem/disk/pressure, and — as the backlog fills in — SSH-monitored bare-metal boxes, a LAN CPU/mem/disk/pressure, historical sparklines, and a LAN device inventory. Local auth always available,
device inventory, and Zabbix alerts. One login instead of N. plus an optional "Sign in with Authentik" OIDC button. Live at https://monitor.jerodrigged.com.
Project status, architecture, and how to resume work: see [`CLAUDE.md`](./CLAUDE.md). Project status, architecture, and how to resume work: see [`CLAUDE.md`](./CLAUDE.md).
Roadmap: [Gitea issues](https://git.jerodrigged.com/jhodgkin/homelab-monitor/issues), milestone `v1-dashboard`. Playwright e2e tests against the live deployment: see [`e2e/README.md`](./e2e/README.md).
## Local development ## Local development
+44
View File
@@ -0,0 +1,44 @@
# E2E tests
Playwright tests that drive a real browser against the **live** deployment
(`https://monitor.jerodrigged.com` by default) — not a local dev server or mocked
backend. They exist specifically because curl-based verification missed real bugs that
only show up with an actual browser (cookies, redirects, DOM interaction).
## Setup
```bash
cd e2e
npm install
npx playwright install chromium
cp .env.example .env # fill in LOCAL_PASSWORD and OIDC_PASSWORD
npm test
```
`OIDC_USERNAME`/`OIDC_PASSWORD` should be the dedicated `playwright-test` Authentik
account (blueprint-provisioned on CT121, path `users/service-accounts`) — **never** a
real personal login. See `docs/oidc-setup.md`.
## Not wired into CI
These hit the live production dashboard and a live Authentik instance, and need
credentials as secrets — deliberately not run automatically on every push. Run by hand
after auth-related changes.
## Bugs these caught on first write (all fixed, kept as regression coverage)
1. **Local logout silently failed.** Frontend always sent `Content-Type:
application/json` even for logout's bodyless POST; Fastify's default JSON parser
rejects that combination (400). curl testing missed it — curl doesn't set that
header without `-d`.
2. **OIDC login redirected to a LAN IP.** `OIDC_ISSUER_URL` used Authentik's internal
address; its discovery document echoes back whichever host you query it through,
so that LAN IP ended up as the *browser-facing* `authorization_endpoint`.
3. **OIDC token exchange rejected with `invalid_client`.** The callback handler
hardcoded `http://` when reconstructing the current URL (Fastify never sees HTTPS —
it terminates upstream), which sent the wrong scheme as `redirect_uri` in the token
exchange. Authentik's own event log said plainly "Invalid redirect URI used by
provider"; the error surfaced to the app as a generic `invalid_client`.
None of these were caught by earlier curl-based verification — browser-driven auth
flows (cookies, real redirect chains, a real IdP's login form) need a real browser.