|
|
0900bf49bd
|
Built-in OAuth2 consent-challenge handler (todo §6); /oauth2/consent grants scopes to a client logging in through us. New src/oauth-consent.ts (pure, sibling of oauth-login.ts): resolveConsentChallenge auto-accepts a first-party client (Hydra metadata.first_party===true) or a Hydra-skipped one, else returns a view to show the themed consent screen; acceptConsent re-reads the challenge so scopes/audience are never client-supplied; rejectConsent → access_denied. The grant carries an OIDC session.id_token with email/name projected from the Kratos identity (whoami traits, omitted when absent). src/hydra-admin.ts gains the consent half (get/accept/reject consent + types; login/consent URL builder folded into one reqUrl(kind,…) + shared put()). Wired in app.ts at GET|POST /oauth2/consent (gated on hydra+kratos): GET shows/auto-accepts (sets the CSRF cookie when fresh), POST is CSRF-guarded (same signed double-submit as /logout) and dispatches allow→accept / else→reject → 303 to Hydra; a stale/consumed challenge (Hydra 4xx) degrades to a recoverable 400, a real outage (5xx) → 500 (mirrors /oauth2/login). views/oauth-consent.ejs + partials/consent-body.ejs reuse the auth-card, listing the requested scopes (friendly labels for the standard OIDC ones) with Allow/Deny submit buttons. Tests-first: hydra-admin consent contracts + oauth-consent skip/first-party/third-party/audience/id_token/refetch/reject matrix + app HTTP integration (auto-accept / screen+CSRF cookie / allow+deny / forged-CSRF→403 / missing→400 / stale→400 / outage→500). Stability-reviewer run as a local PR: APPROVE, no Critical/High. Extended e2e/oauth-login.spec.ts to drive the whole authorization-code flow against real Hydra — login accept → follow login_verifier through Hydra → web's consent screen (third-party e2e-login, scopes listed) → Allow → consent_verifier → client callback with a real code (per-host cookie jars, Hydra resume URLs rebased onto the compose host). typecheck + 262 units + 8 visual + OAuth login+consent E2E green. OAuth2 client registration is the next §6 item.
|
2026-06-19 10:53:21 +02:00 |
|
|
|
3c8090e8e3
|
Built-in OAuth2 login-challenge handler (todo §6); /oauth2/login resolves a Hydra login challenge via the Kratos session — skip→accept(subject), live session→accept(identity id), no session→bounce to /login?return_to back here so Kratos lands on the challenge once signed in. New src/hydra-admin.ts (fetch client: get/accept/reject login request + HydraError, mirrors the kratos/keto clients) + src/oauth-login.ts (pure resolveLoginChallenge); wired in app.ts (the absolute return URL derives from the request Host + the SECURE_COOKIES scheme — a spoofed Host can't escape, Kratos validates return_to against its allow-list; /login now bakes return_to into the flow init), config.hydraAdminUrl (default http://hydra:4445), server builds the client, compose web now gates on hydra healthy (the app consumes it). A stale/invalid/consumed challenge (Hydra 4xx — back button, slow login) degrades to a recoverable 400, not a 500; a genuine Hydra 5xx outage still surfaces as 500. Tests-first: hydra-admin/oauth-login units + app/config/compose HTTP integration + full-stack e2e/oauth-login.spec.ts (compose.e2e-oauth.yml — registers an OAuth2 client, starts an auth flow, asserts the unauthenticated bounce and the authenticated accept; boot-verified then torn down). Stability-reviewer run as a local PR: APPROVE, no Critical/High; addressed its one warning (4xx→400 degrade). Deferred §9: document that prod allowed_return_urls entries must be exact origins with a trailing /. typecheck + 253 units + 8 visual + oauth-login E2E green. Consent handler + client registration are the next §6 items.
|
2026-06-18 21:45:24 +02:00 |
|
|
|
0a5eafd2f8
|
Tighten §5 admin comments + README (todo §5 cleanup); compress the three near-identical admin module headers (drop restatement the README/code already carry), shorten the README Layout views/ run-on + add the missing delete-confirm view. Also bank a pre-existing AGENTS.md tweak: skip the stability-reviewer for purely doc/comment changes. Docs/comments-only — typecheck + 244 units green.
|
2026-06-18 19:25:02 +02:00 |
|
|
|
c78e95889c
|
Address whole-project architecture + product reviews (todo §5): make readRoles transitive so group→role grants reach the JWT (matches the Roles 'Effective access' view + OPL model; per-login only), per the user's call; add a zero-JS server-rendered confirm step for delete user/group/role (views/admin/confirm.ejs + shared buildConfirmModel; the Delete control is now a GET link, the delete stays a CSRF-guarded POST); self-lockout guards — no self-delete/deactivate (Users), no self-revoke of the direct admin grant + no delete of the admin role (Roles), each → 400 + inline error (direct-grant paths incl. the seeded admin; group-only-admin lockout = robust last-effective-admin check deferred §9); extract the gate+CSRF preamble copied across the 3 admin handlers into admin-nav.ts requireAdmin/guardedForm; shellUser keeps the email (name = local part, full email beneath). Reviewers: architecture no Critical/High, product 2 Critical + 1 High (all fixed). Deferred (scoped): host route-table→§6, list/template dedup→§5 cleanup, success-flash/empty-states/dangling-refs→§5 polish/§8, safeUrl→§7, 413/https/§N-drift→§9. Tests-first (extended the 3 admin HTTP tests + login/shell-context units); typecheck + 244 units + 8 visual + auth-refresh E2E green; stability-reviewer APPROVE
|
2026-06-18 19:18:50 +02:00 |
|
|
|
6920751cb8
|
Wire built-in admin screens into the global menu (todo §5); extract adminSection() = one permission-gated 'Admin' header (Users/Groups/Roles), reused by both the home dashboard menu and the in-screen adminNav so they can't drift. composeNav drops the whole gated header+subtree for a non-admin/anonymous (cosmetic — the admin routes stay independently GuardError(403)-gated); narrowed AdminScreen to groups|roles|users. Reuses existing sprite icons (no icon-guard change); default anonymous / render byte-equivalent so visual E2E unaffected. Tests-first: dashboard model gating (admin→3 hrefs, non-admin→absent) + app HTTP (admin JWT→/admin/users link, anon→absent). Stability-reviewer run as a local PR: APPROVE, no Critical/High/Medium. README Layout updated. 242→244 units + typecheck green
|
2026-06-18 18:33:19 +02:00 |
|
|
|
a016a0131e
|
Built-in Roles & permissions admin screen (todo §5); /admin/roles list (search/sort/paginate) + create/delete + assign-to-users/groups + "effective access" (Keto expand → transitive members), writing only to Keto — gated admin-only + CSRF-guarded like Users/Groups (Kratos read only to label members). A role = Keto subject set Role:<name>#members; reuses the Groups membership helpers (now-exported pagedTuples/memberCandidates/safeDecode); added a Roles nav entry (i-shield) + a .plain-list CSS rule. Stability-reviewer run as a local PR: APPROVE, no Critical/High; addressed its explicit-expand-depth nit. Live boot-verify caught a real bug the tests missed — Keto v26.2.0 nests the expand subject under tuple (not node top-level as the §4 ExpandTree type guessed), so expandToEffectiveUsers returned []; fixed type+walker+fixtures, re-verified a group-only member surfaces in effective access. 237→243 units + typecheck green; expand chain boot-verified live then torn down.
|
2026-06-18 18:18:18 +02:00 |
|
|
|
32e5e2f7eb
|
Built-in Groups admin screen (todo §5); /admin/groups list (search/sort/paginate) + create/delete + membership (add/remove users & nested groups), writing only to Keto — gated admin-only + CSRF-guarded like Users (Kratos read only to label pickers). A group = Keto subject set Group:<name>#members, exists while it has ≥1 member: create writes the first-member tuple, delete removes all by partial-filter. Extracted shared admin-nav.ts (Dashboard·Users·Groups); new generic rowHeader <th scope=row> data-table cell. Stability-reviewer run as a local PR: symmetric subject UUID-validation, duplicate-name rejection, malformed-%→404. 228→237 units + typecheck green; core Keto interactions boot-verified live
|
2026-06-18 17:40:36 +02:00 |
|
|
|
79cfa2ee7f
|
Built-in Users admin screen (todo §5); /admin/users list (filter/sort/paginate) + create/edit/deactivate/delete + trigger-recovery, writing only to Kratos via the admin client — gated admin-only (anon→/login, non-admin→403) and CSRF-guarded like logout. New kratosAdmin.createRecoveryCode; reserved the "admin" plugin id; views:[viewsDir] so subfolder views reuse partials/. Reviewer §5 opener: extracted shell-context.ts (buildShellContext/shellUser) shared by dashboard+admin, threading the real signed-in user (drops the hardcoded demo profile). 217→228 units + 8 visual E2E green; boot-verified full CRUD+recovery live on the Ory stack
|
2026-06-18 12:26:19 +02:00 |
|
|
|
b5af4ba6cd
|
E2E for token timeout + refresh (todo §4); full-stack auth-refresh.spec.ts (real Ory stack): a lapsed session JWT is silently re-minted from the live Kratos session (roles re-read from Keto), and cleared once the session is revoked; ory/kratos/e2e.yml shortens the tokenizer ttl to 8s + adds JWT_CLOCK_SKEW_SEC config so re-mint fires at expiry; scope visual suite to visual.spec.ts
|
2026-06-18 11:32:23 +02:00 |
|
|
|
4b2173cb84
|
Secure cookie flags + CSRF for our own POST forms (todo §4); SECURE_COOKIES toggle on session/CSRF cookies; csrf.ts signed double-submit token + body.ts form reader; logout is now a CSRF-guarded POST form
|
2026-06-18 11:12:32 +02:00 |
|
|
|
dec55f85a6
|
Logout (todo §4); GET /logout clears plainpages_jwt + revokes the Kratos session (createLogoutFlow → redirect to Kratos logout URL → /login); wire shell Sign out link
|
2026-06-18 10:35:07 +02:00 |
|
|
|
4f6b60463b
|
Session re-mint on TTL expiry (todo §4); resolveSession flags a lapsed token, app.ts hot path re-mints via remintSession (roles re-read from Keto → fresh cookie) only when a live Kratos session backs it; a dead session clears the stale cookie
|
2026-06-18 10:25:05 +02:00 |
|
|
|
228a206469
|
Auth guards (todo §4); guards.ts: requireSession/can/check + GuardError, app.ts maps GuardError → 303 /login or 403 (never 500)
|
2026-06-18 10:10:15 +02:00 |
|
|
|
24eb6b1c68
|
JWKS fetch + cache + rotation (todo §4); cachingJwks: TTL cache + rotation-on-miss reload (throttled, last-good on error), createJwksProvider routes file/base64/http + primes at boot
|
2026-06-18 10:01:40 +02:00 |
|
|
|
c8b56b85eb
|
JWT session middleware (todo §4); authenticate(): verify the session cookie via cached JWKS (key by kid) → exp/nbf/iss/aud claims (clock skew) → ctx.user/roles; iss/aud opt-in; fail-closed
|
2026-06-18 09:53:37 +02:00 |
|
|
|
38157605d0
|
Login completion (todo §4); /auth/complete: roles from Keto → metadata_public projection → tokenize → plainpages_jwt cookie; fix tokenizer projection metadata_admin→metadata_public (whoami strips admin metadata)
|
2026-06-17 23:15:28 +02:00 |
|
|
|
26a7821611
|
Render SSO buttons per configured Kratos OIDC provider (todo §4); flow-view collects oidc nodes → auth-card submit buttons, server-side visibility, drop mockup #sso-toggle CSS
|
2026-06-17 18:20:45 +02:00 |
|
|
|
0928f9dd39
|
Render Kratos self-service flows as themed pages (todo §4); buildFlowView + views/auth.ejs + login/registration/recovery/verification/settings routes
|
2026-06-17 17:55:56 +02:00 |
|
|
|
2a64cfd409
|
Add Keto fetch client (todo §4); createKetoClient(): check / list / expand relations + write / delete tuples
|
2026-06-17 17:33:59 +02:00 |
|
|
|
5e96678fda
|
Add Kratos admin-API fetch client (todo §4); createKratosAdmin(): identity CRUD + surgical metadata_admin update (login role projection)
|
2026-06-17 17:22:02 +02:00 |
|
|
|
898dc7f2cf
|
Add Kratos public-API fetch client (todo §4); createKratosPublic(): self-service flow init/get/submit, whoami, session→JWT tokenize
|
2026-06-17 17:15:50 +02:00 |
|
|
|
e83cf4da88
|
Address project-wide review (todo §3); fix JWKS_URL default → tokenizer signing key + read-only web mount, cap bootstrap restart, --no-deps for unit commands
|
2026-06-17 16:49:37 +02:00 |
|
|
|
1fc6b42156
|
Document the only manual prep (todo §3); README 'What you must supply' — production secrets + optional SSO creds, everything else auto-generated
|
2026-06-17 16:32:54 +02:00 |
|
|
|
4d65665063
|
Bootstrap: print first-run login banner (URL + seeded creds + change-before-prod warning)
|
2026-06-17 16:22:48 +02:00 |
|
|
|
a6900217cb
|
One-command bootstrap (todo §3); idempotent first-boot seed: JWKS-if-absent, demo admin in Kratos, admin role in Keto
|
2026-06-17 16:18:21 +02:00 |
|
|
|
4af090f803
|
Split dev/prod compose wiring (todo §3); Ory readiness healthchecks, web gated on kratos+keto, dev-only host ports, Ory-free E2E
|
2026-06-17 16:06:05 +02:00 |
|
|
|
93e62d8661
|
Add Hydra service + migrate (todo §3); pin oryd/hydra:v26.2.0, OAuth2 issuer + login/consent URLs → our app routes
|
2026-06-17 15:45:37 +02:00 |
|
|
|
fa87280f46
|
Add Keto service + migrate (todo §3); OPL role/group/resource namespaces, fine-grained resource permits
|
2026-06-17 15:12:01 +02:00 |
|
|
|
6640dfc84e
|
Generate + mount the JWT signing JWKS (todo §3); ES256 gen-jwks tool, committed dev key, key-rotation docs
|
2026-06-17 13:24:31 +02:00 |
|
|
|
95c759d773
|
Wire Kratos session tokenizer template (todo §3); plainpages JWT (sub/email/roles), 10m TTL, Jsonnet claims mapper reading metadata_admin
|
2026-06-17 12:02:21 +02:00 |
|
|
|
d6960c9bad
|
Add optional env-activated Kratos OIDC/SSO providers (todo §3); off by default, committed claims mapper, SAML via OIDC bridge note
|
2026-06-17 10:58:31 +02:00 |
|
|
|
f2898696e6
|
Wire Kratos self-service flows to themed routes (todo §3); enable recovery/verification via email code, add mailpit dev courier + --watch-courier
|
2026-06-17 10:19:29 +02:00 |
|
|
|
120e1a0929
|
Add kratos service + migrate (todo §3); pin oryd/kratos:v26.2.0, identity schema (email, name), bootable password config
|
2026-06-16 23:24:32 +02:00 |
|
|
|
bc15f00c44
|
Add postgres service (todo §3); pin postgres:18.4-alpine3.23, one DB per Kratos/Keto/Hydra via init.sql
|
2026-06-16 17:13:40 +02:00 |
|
|
|
9489bd124b
|
Tighten code comments + README (todo §2); trim verbose §2 headers, drop stale planned/next-item markers, correct README status
|
2026-06-16 16:31:57 +02:00 |
|
|
|
ff7b55be4c
|
Wire branding into the app shell (todo §2); render config logo + default theme, fall back to the brand mark
|
2026-06-16 16:07:24 +02:00 |
|
|
|
952dd03cc2
|
Add config/menu.ts central override + branding (todo §2); loadMenuConfig validates+merges, override applied to nav, branding into shell
|
2026-06-16 15:52:03 +02:00 |
|
|
|
3cdefff233
|
Serve per-plugin static assets (todo §2); /public/<id>/ → plugins/<id>/public/ via routePublic, core public/ unaffected
|
2026-06-16 15:18:20 +02:00 |
|
|
|
fe89dd1c06
|
Add per-plugin view resolver (todo §2); render plugins/<id>/views/<view>.ejs with nested names + traversal guard, core partials reachable via include()
|
2026-06-16 13:41:02 +02:00 |
|
|
|
9b6684c653
|
Mount plugin routes via the router (todo §2); match method+path under /<id>, resolve :params, permission gate, RouteResult→response
|
2026-06-16 12:22:15 +02:00 |
|
|
|
ca3f6ba8ce
|
Discover plugins at boot (todo §2); scan plugins/, import + validate each plugin.ts default export, fail loud on bad plugin/conflict
|
2026-06-16 12:11:04 +02:00 |
|
|
|
0ebe8144c2
|
Document how plugins get into the container (README); bind-mount to /app/plugins/<id> or bake in, front-and-center under Building a plugin
|
2026-06-16 11:58:28 +02:00 |
|
|
|
1623a81ddc
|
Refine plugin contract (todo §2); derive id/mount from folder (isValidPluginId), apiVersion literal not HOST_API_VERSION, nav icon = Lucide, drop redundant basePath
|
2026-06-16 10:58:29 +02:00 |
|
|
|
a0d39ef624
|
Make checkApiVersion semver-based (todo §2); strict parseSemver via official semver regex (no dep), major/minor compatibility rules
|
2026-06-16 10:46:02 +02:00 |
|
|
|
3be67ff8e4
|
Specify the plugin contract (todo §2); typed manifest + version/conflict rules in src/plugin.ts, authoritative docs/plugin-contract.md
|
2026-06-15 17:07:55 +02:00 |
|
|
|
f91e08c2a6
|
Add Full, parallel E2E principle (todo §1.1); AGENTS §6 + README, 404 E2E coverage, --build the runner so spec edits apply
|
2026-06-15 16:58:26 +02:00 |
|
|
|
645a316419
|
Make markup semantic + add semantic DOM principle (todo §1); page <h1>, skip link, row-header <th scope=row>, descriptive error pages
|
2026-06-15 16:53:07 +02:00 |
|
|
|
6f590148af
|
Add dockerized Playwright E2E (todo §1); screenshot live pages + foundation mockups, assert shared design-system styles match
|
2026-06-15 16:37:21 +02:00 |
|
|
|
947851b4ff
|
Replace placeholder index with the app-shell People dashboard (todo §1); wire parseListQuery/paginate/composeNav + partials into a real zero-JS list page
|
2026-06-15 15:57:42 +02:00 |
|
|
|
c06429e4d5
|
Add paginate helper (todo §1); page model with row window + ellipsis sequence for pagination.ejs, clamped/guarded inputs
|
2026-06-15 13:50:15 +02:00 |
|