docs(project-state): open ^/mcp on the stats proxy provider (public MCP with Bearer key) + record the verification
Deploy / build (push) Successful in 33s

This commit is contained in:
2026-09-29 06:23:42 +08:00
parent b7ecd27076
commit e4a2da29db
+2 -1
View File
@@ -86,7 +86,7 @@ The user asked for the dashboard to be reachable remotely **without** publishing
| Item | State |
|---|---|
| authentik objects | proxy provider **pk 64 `Provider Proxy Stats`** (`mode=proxy`, `external_host=https://stats.hoelee.com`, `internal_host=http://umami:3000`, `skip_path_regex` = `^/script\.js$` + `^/api/send` + `^/api/heartbeat$`) + application **`umami-stats`** (launch URL + 512×512 logo, see below). Attached to the **embedded outpost** `e16274ac-a3ad-4c21-bb16-4a5d32570bc6` (20th provider) |
| authentik objects | proxy provider **pk 64 `Provider Proxy Stats`** (`mode=proxy`, `external_host=https://stats.hoelee.com`, `internal_host=http://umami:3000`, `skip_path_regex` = `^/script\.js$` + `^/api/send` + `^/api/heartbeat$` + `^/mcp(/|$)`) + application **`umami-stats`** (launch URL + 512×512 logo, see below). Attached to the **embedded outpost** `e16274ac-a3ad-4c21-bb16-4a5d32570bc6` (20th provider) |
| Per-app login flow | **`auth-stats`** (designation `authentication`, 4 stages: identification → password → mfa-validation → user-login), so the SSO page for this app is its own branded screen and **not** shared with the other 19 apps. Provider `authentication_flow` points at it |
| App icon | ⚠ In this build the application icon field is **`meta_icon`** (not `icon` — PATCHing `icon` silently no-ops). Uploaded with the house convention `application-icons/umami.png` via `POST /admin/file/` (multipart, `usage=media`), then `PATCH /core/applications/umami-stats/ {"meta_icon": "application-icons/umami.png"}` → `meta_icon_url` `/files/media/public/application-icons/umami.png` verified 200 (21,989 B PNG) |
| DSM cut-over | vhost `…c378795c2acb.w3conf` §2731 `proxy_pass http://localhost:5410` → **`http://localhost:10000`** (the outpost) + `ReverseProxy.json` `"port" : 5410` → `10000`; backups `*.bak-20260929-sso-stats-sso`. Both edits asserted unique first (`grep -c 5410` = 1 in each file), `nginx -t` clean, reloaded |
@@ -94,6 +94,7 @@ The user asked for the dashboard to be reachable remotely **without** publishing
| ⚠ Other pitfall | After any provider change the **embedded outpost needs ~1–2 min to pick up the new config**; before that, `/` answers `302 → /flows/-/default/authentication/` and app paths 404. Judging the wiring before that window expires gives a false "it's broken" |
| Verified from the public internet (2026-09-29) | anonymous `/` → 302 → `/outpost.goauthentik.io/start` → `auth.hoelee.com/if/flow/auth-stats/…` → **200** (same shape as the working `auth-mysql` chain); `/login`, `/api/websites` → 302 gated; `/script.js` **200**, `/api/heartbeat` **200**, `POST /api/send` **400 app-level** |
| End-to-end tracker through the SSO path | temp website + 2 pageviews POSTed to `https://stats.hoelee.com/api/send` → recorded **2 pageviews / 1 visitor / 1 session**, `country=MY, region=MY-07, city=George Town`, referrer metric `google.com` → **the outpost does not break `X-Forwarded-For`, geo still works**; temp website deleted |
| MCP over the public URL (added 2026-09-29, same day) | `^/mcp(/|$)` appended to the provider's `skip_path_regex` (done via `ak shell` ORM — the `hermes3` API token was dead by then, and the ORM write is the same model path the API uses). Verified: `POST https://stats.hoelee.com/mcp` with `Authorization: Bearer umami_…` + `Accept: application/json, text/event-stream` → **200**, `Content-Type: text/event-stream`, chunked, `x-powered-by: authentik`, `tools/list` returns the tool table, `tools/call list_websites` → `count: 0`; **no key → 401** (`Missing bearer API key`, i.e. the request reaches Umami, not the SSO gate); dashboard paths still 302, tracker paths unchanged. ⚠ `x-api-key:` does **not** work for Umami's API/MCP — only `Authorization: Bearer` |
| Known trade-off (honest) | (1) SSO protects the *dashboard route*, but Umami has **no OIDC**, so after SSO the user still sees **Umami's own login page** — double login, expected, one-click after the first time (2FA is available per-account in Settings → Profile; `TWO_FACTOR_ENCRYPTION_KEY` already in the stack env). (2) `<http://192.168.1.1:5411>` remains a **LAN break-glass path that bypasses SSO** (still needs Umami credentials; verified closed from the internet) — delete/close it if that is not wanted. (3) If the authentik outpost goes down, the dashboard goes down with it (tracker too — but the site keeps loading, the tracker fails silently) |
| Rollback | Start the gate again (`sudo /usr/local/bin/docker start umami-gateway`, or start stack 285 in Portainer) → re-point the vhost + `ReverseProxy.json` to `5410` → reload nginx. Backups of both files sit next to the originals. (The gate container is now **`Exited (0)`** — it was stopped once the outpost took over, so port 5410 is free; the stack is kept as the rollback asset) |
| Note | The `hermes3` authentik API token expired mid-session (DB says `expires 2026-09-28 22:17:08Z`); the leftover self-test account was removed through authentik's own ORM (`ak shell`), so no admin-group test user is left behind |