§10 gate the dashboard + make "/" replaceable by a plugin (todo §10); "/" is now gated to a signed-in session (anonymous → /login via loginRedirect, query preserved as return_to) and fully replaceable via a new optional home?: RouteHandler on PluginManifest — a handler with the same signature as any route (the most ergonomic shape). The app.ts "/" branch gates first, then renders the single home plugin's handler against its own views/ with the native shell via ctx.chrome (HEAD / void-return / response-hook parity with a plugin route), else the built-in mock-data People list. home mounts at the root above the /<id> namespace, so it can't shadow or be shadowed by a built-in route. Single-slot + loud: findConflicts errors on >1 home (new "home" kind), discovery rejects a non-function home — never last-write-wins. Tests-first (338 → 344 units): app.test.ts gate + home-override; plugin.test.ts home conflict; discovery.test.ts home validation. Docs: plugin-contract.md (manifest table + "The dashboard (home)" section + conflict row), README. E2E: visual.spec plants a dev-signed session (the anonymous plugin-gate probe uses the cookie-free request fixture); all e2e web/gateway healthchecks repointed from the gated "/" to /public/css/styles.css. stability-reviewer: APPROVE, no Critical/High/Medium. typecheck + 344 units + visual(9) + full-flow(7) E2E green.

This commit is contained in:
2026-06-20 17:18:30 +02:00
parent df53106a5a
commit 2eb5b84ccf
14 changed files with 192 additions and 41 deletions

View File

@@ -136,5 +136,5 @@ everything via Docker.
- [x] Go over all tests and combine/unify ones that cover the same stuff or are very related and could be combined in a good way. Remove tests that aren't helping, we only want tests that are actually helpful to us. → Pass over the §9 test accretion (`security-headers`/`denylist`/`logger`/`safe-url` units, `gen-jwks` `rotateJwks`, + the §9 additions across `config`/`app`/`jwt-middleware`/`context`/`plugin-api`/`guards`). The new unit files are one-concern-per-test matrices (logger severity/level/format/trace-continuation, denylist iat-cutoff/TTL-evict, safe-url scheme/host-relative, security-headers strict-CSP) and the per-field `config` toggles (`SERVICE_NAME`/`LOG_*`/`OTLP_*`/`REVOCATION_*`/`JWT_CLOCK_SKEW`/`ORY_TIMEOUT`) follow the file's existing per-field validation pattern — no fat, and force-merging distinct fields/surfaces would only hurt readability (the §3 "don't merge across distinct concerns" rule). **One genuine §9-era overlap:** `app.test.ts` carried two `/login?return_to=…` tests for the *same* surface — the §6 "bakes the return target into the Kratos flow init (OAuth bounce)" and the §9 "a first-party deep link is wrapped through `/auth/complete`; an absolute target passes through as-is". The §9 test subsumes the §6 one: its middle assertion already proves an absolute `/oauth2/login?login_challenge=` target is passed to `initBrowserFlow` **unchanged** (the exact §6 OAuth-bounce contract — and it's labeled as such in the test name + inline comment), plus the new host-relative-wrap + protocol-relative cases the §6 test never had. Removed the now-redundant standalone §6 test (zero coverage lost). Pure test refactor, no production code (per the §6/§7/§8 precedent, no stability reviewer). 339 → 338 units; typecheck + tests green.
## 10. User added stuff
- [ ] The dashboard, the first landing page after logging in, should be gated to only logged in users. It should also be replaceable fully from a plugin. It is important that the ergonomics for the plugin writer is great.
- [x] The dashboard, the first landing page after logging in, should be gated to only logged in users. It should also be replaceable fully from a plugin. It is important that the ergonomics for the plugin writer is great.`/` is now **gated to a signed-in session** (anonymous → `/login` via `loginRedirect`, query preserved as `return_to`), and **fully replaceable by a plugin**: a new optional `home?: RouteHandler` on `PluginManifest` (`src/plugin.ts`) — the most ergonomic possible shape, a handler with the same signature as any route. The host (`app.ts` `/` branch) gates first, then renders the single home plugin's handler against its own `views/` with the native shell via `ctx.chrome` (same path/parity as a plugin route: HEAD, `void`-return, response hooks), else the built-in mock-data People list. Identity stays folder-derived; `home` mounts at the root above the `/<id>` namespace, so it can't shadow (or be shadowed by) a built-in route. **Single-slot, loud:** `findConflicts` errors when >1 plugin declares `home` (new `"home"` conflict kind), `discovery.shapeError` rejects a non-function `home` — never last-write-wins. Tests-first (344 units, was 338): `app.test.ts` gate (anon→303 /login) + home-override integration (plugin dashboard replaces the People list, still gated); `plugin.test.ts` home conflict; `discovery.test.ts` home-not-a-function + two-homes + valid-home-loads. Docs: `docs/plugin-contract.md` (manifest table + a "The dashboard (home)" section + conflict-rule row), README (Building-a-plugin note + Layout). **E2E:** the Ory-free `visual.spec.ts` now plants a dev-signed session (signs with the committed tokenizer key, bind-mounted into the runner; the anonymous plugin-gate probe uses the cookie-free `request` fixture); all five e2e web/gateway healthchecks repointed from the now-gated `/` to the auth-free `/public/css/styles.css`. stability-reviewer on the prod diff: **APPROVE, no Critical/High/Medium** (verified no shadowing either direction, gate↔re-mint ordering, HEAD/void/hook parity, open-redirect-safe). typecheck + 344 units + visual (9) + full-flow (7) E2E green, stacks torn down.
- [ ] Make some pages optionally available publicly. A plugin should be able to set the permissions of a page (including the menu option) to publicly available.