9.3 KiB
9.3 KiB
Plainpages — implementation TODO
Build order is top → bottom; each phase is roughly independent and testable. Conventions: write tests first (node --test for units, Playwright for E2E), tear down test containers after runs, keep deps minimal, pin all versions, run everything via Docker.
North-star / MVP. Done = a developer can clone, run one command, get a working register/login, and start hacking on their own plugin — no manual key generation, no hand-edited Ory config, no DB setup. Everything below serves that; the one-command bootstrap (§3) and the example plugin (§7) are what make the MVP real. Hydra/SSO are explicitly post-MVP.
0. Housekeeping / primitives
- Decide JWT verify approach:
node:crypto(RS256/ES256 viacreatePublicKey({format:"jwk"})) vs addjose— justify if adding. →node:crypto(no new dep);src/jwt.tsverifies JWS signatures. - Cookie helpers: parse
Cookieheader, buildSet-Cookie(HttpOnly, Secure, SameSite). - Request context type threaded to handlers:
{ req, res, url, params, query, user|null, roles }. - Error templates: add 403 + 500 (404 exists).
- Config/env loader: Ory endpoints, cookie/CSRF secret, JWKS location, ports.
1. Building blocks — extract from html-css-foundation/ (no Ory needed; render mock data)
- Move
styles.css+auth.cssintopublic/css/; reconcile with existingstyle.css. - Lucide icon sprite from
lucide-static(dep added) →views/partials/icons.ejs; serve/inline only the icons used. - App-shell partial (sidebar + topbar + content slot).
- Nav-tree partial — recursive, header/leaf × clickable/static, counts,
aria-current. - Filter-bar partial — GET form (search, segmented, selects, chips, daterange, applied pills).
- Data-table partial — sortable headers, row-select, badges, kebab row actions.
- Pagination partial — rows-per-page + page numbers, query-param driven.
- Form-field partials (input/label/hint/error) + auth-card partial.
- Menu/popover + theme-switch partials (pure CSS
details/summary). - Helper
composeNav(fragments, override, roles)→ merged, permission-filtered tree. - Helper
parseListQuery(url)→{ q, filters, sort, page, pageSize }. - Helper
paginate(total, page, pageSize)→ page model. - Unit tests for all helpers (first).
- Replace placeholder
indexwith the app-shell dashboard.
2. Plugin host
- Specify the plugin contract (big job, do first — it's the product's main API surface). Write it down as the authoritative reference: the full manifest shape; the
RequestContexthanded to handlers and what's guaranteed stable; contract versioning (aapiVersion/engines-style field so a plugin declares the host it targets, and the host refuses or warns on mismatch); conflict rules (two plugins claiming the samebasePath, nav slot, orpermissionname → defined, loud resolution, not last-write-wins); the local dev/test story (how an author runs + tests one plugin in isolation against the host). Audience is experienced devs: optimise for a powerful, predictable, clearly-documented API. Crash-isolation (a bad plugin can't take down the host) is a nice-to-have, not a blocker — fail loud at boot/discovery over sandboxing at runtime. - Discovery: scan
plugins/, import eachplugin.tsdefault export, validate. - Router: match method+path under
basePath, resolve path params, run permission gate, call handler with context. - Per-plugin view resolver (
plugins/<id>/views/*.ejs). - Per-plugin static serving:
plugins/<id>/public/→/public/<id>/. config/menu.tscentral override: reorder/rename/hide/group + branding (app name, logo, default theme).- Wire branding into the app shell.
- Tests: discovery, routing, param matching, permission gate, nav merge + filter.
3. Ory stack — compose + config
postgresservice (pinned tag); separate DB/schema per Kratos/Keto/Hydra.kratosservice (pinned) +migrate; identity schema (traits: email, name).- Kratos self-service flows (login, registration, recovery, verification, settings) → return URLs at our themed pages.
- Kratos OIDC/SSO providers (Google/Microsoft/SAML) config (secrets via env). None enabled by default — a clean clone runs password-only; a provider activates purely by supplying its env creds.
- Kratos session settings (cookie name, lifespan, sliding refresh).
- Kratos tokenizer template
plainpages: claims{ sub, email, roles },ttl ≈ 10m,jwks_urlsigner,claims_mapper_url(Jsonnet readingmetadata_admin.roles). - Generate + mount the JWT signing JWKS; document key rotation.
ketoservice (pinned) +migrate; namespaces in OPL (role,group, resource permissions).hydraservice (pinned) +migrate; issuer + login/consent URLs → our app.- Split dev (
compose.override.yml) vs prod (compose.yml) wiring; health checks +depends_onordering. - One-command bootstrap (the MVP bar):
docker compose upbrings up web + all Ory services + Postgres with zero manual prep. Commit working default Ory configs; auto-run migrations on first boot; auto-generate the JWKS signing key if absent; seed an admin identity + its Keto roles + a demo password (admin/admin) idempotently. Land anOPL/namespace bootstrap so Keto answers checks out of the box. - First-run banner / log line printing the login URL + seeded admin creds, with a clear "change these before production" warning.
- Document the only things that can't be auto-generated: third-party SSO provider client id/secret (optional — password login works without them) and production secrets (real cookie/CSRF secret + signing key, supplied via env, replacing the dev throwaways). Everything else must work from a clean clone.
4. Auth — identity, session JWT, guards
- Kratos public client (fetch): init/get/submit flows,
whoami,whoami?tokenize_as=plainpages. - Kratos admin client (fetch): identity CRUD +
metadata_adminupdate. - Keto client (fetch):
check, list/expand relations, write/delete tuples. - Render Kratos flows: fetch flow → render fields against our themed pages → POST to
flow.ui.action(Kratos handles its CSRF), map field errors/messages. - SSO buttons → Kratos OIDC flows. Render per configured provider only: derive the list from Kratos' enabled OIDC providers (no creds ⇒ no button); hide the whole SSO section when none are configured. No code change needed to add/remove a provider — config only.
- Login completion: read roles from Keto → write
metadata_adminprojection → tokenize → set JWT cookie. - JWT middleware: verify signature via cached JWKS, validate
exp/iss/aud(+clock skew), build context (user, roles). - JWKS fetch + cache + rotation handling.
- Guards:
requireSession(validate JWT),can(role)(claim, in-process),check(relation, object)(live Keto). - Session re-mint on TTL expiry (re-read roles from Keto).
- Logout: revoke Kratos session + clear cookie.
- Secure cookie flags; CSRF for our own POST forms.
- Tests: JWT verify (valid/expired/bad-sig), guard behavior, login→projection→tokenize flow (Ory mocked).
5. Built-in admin screens (writes go only to Keto/Kratos)
- Users: list (Kratos identities) with filter/sort/pagination; create/edit/deactivate/delete; trigger recovery.
- Groups: Keto subject sets — list/create/delete + membership management.
- Roles & permissions: Keto relations — assign roles to users/groups; "effective access" view via Keto expand.
- Wire into the menu (admin section, permission-gated).
- Tests: CRUD flows (Ory mocked) + permission gating.
6. Hydra — OAuth2/OIDC provider (can ship after the rest)
- Login-challenge handler: authenticate via Kratos session, accept/reject.
- Consent-challenge handler: show / auto-accept first-party, grant scopes, accept/reject.
- OAuth2 client registration (admin UI or CLI).
- Tests: authorization-code login+consent happy path; token + refresh.
7. Example plugin (reference)
- Reference plugin (e.g. people directory or scheduling): list page fetching upstream data, a form that forwards writes upstream, permission-gated nav.
- Verify the full plugin contract end-to-end against the README.
8. Testing & CI
- node --test units across helpers / router / nav / auth (tests-first throughout).
- Playwright full E2E: login (password + mocked SSO), menu filtering by role, users/groups/permissions CRUD, a plugin page, logout.
- E2E harness: bring up the full compose stack, seed Keto roles + a test identity, tear down after.
- Typecheck + tests green in Docker (
docker compose run --rm web …).
9. Production, security, ops
compose.ymlprod: Ory + Postgres, secrets via env, no source mount.- Security headers; secure/HttpOnly/SameSite cookies; CSRF; clock-skew tolerance.
- Optional revocation denylist for instant role/session revoke.
- Structured logging / basic observability. use @larvit/log for OTLP compability - but add subtasks and stuff for supporting incoming trace id etc from a reverse-proxy etc.
- JWT signing-key rotation runbook.
- Refresh README
Layout+ drop_(planned)_markers as pieces land.