diff --git a/CLAUDE.md b/CLAUDE.md index f29ba65..7b0acb2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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 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. + 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 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 @@ -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 — see `docs/ssh-collector-key-setup.md`. - **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 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. @@ -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. - **Vaultwarden**: dashboard admin credentials saved as "Homelab Monitor (dashboard admin)". OIDC client 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 @@ -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 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 -skipped). `GET .../issues?state=open` on this repo should return empty. 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: +skipped). `GET .../issues?state=open` on this repo should return empty. First real user click-through of +"Sign in with Authentik" surfaced two bugs (LAN-IP issuer, wrong-scheme redirect_uri in token exchange — +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 - `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 (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. diff --git a/README.md b/README.md index 222bea1..57a39d5 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ # Homelab Monitor A single dashboard for the health of everything in the homelab (documented in -[`jhodgkin/homelab`](https://git.jerodrigged.com/jhodgkin/homelab)): Proxmox host + LXC -CPU/mem/disk/pressure, and — as the backlog fills in — SSH-monitored bare-metal boxes, a LAN -device inventory, and Zabbix alerts. One login instead of N. +[`jhodgkin/homelab`](https://git.jerodrigged.com/jhodgkin/homelab)): Proxmox host + LXC + bare-metal +CPU/mem/disk/pressure, historical sparklines, and a LAN device inventory. Local auth always available, +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). -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 diff --git a/e2e/README.md b/e2e/README.md new file mode 100644 index 0000000..5e844a5 --- /dev/null +++ b/e2e/README.md @@ -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.