Commit Graph

23 Commits

Author SHA1 Message Date
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
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
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
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
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
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
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
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
bddc1f891d Add menu/popover + theme-switch partials (todo §1); data-driven .menu (items/check-groups/positioning), Light/Auto/Dark switch, shell reuses both 2026-06-15 13:27:44 +02:00
7716e38d84 Add field + auth-card partials (todo §1); data-driven .field (label/icon/hint/server error) and auth-card shell (head/SSO/body/alt) 2026-06-15 13:16:36 +02:00
fcf2abdf17 Add data-driven pagination partial (todo §1); rows-per-page GET form + page-number links, zero-JS, query-param driven 2026-06-15 13:10:24 +02:00
cf1b74f09d Add data-driven data-table partial (todo §1); sortable header links, row-select, typed cells/badges, kebab actions 2026-06-15 13:04:19 +02:00
637d5cf66d Add data-driven filter-bar partial (todo §1); GET form: search/segmented/select/chips/daterange + applied pills 2026-06-15 12:04:25 +02:00
67743cad23 Add recursive nav-tree partial (todo §1); header/leaf × clickable/static, counts + aria-current 2026-06-15 11:59:26 +02:00
672b831f8c Add app-shell partial (todo §1); sidebar + topbar + content/nav slots, reuses mockup classes + icon sprite 2026-06-15 11:51:44 +02:00
265704a7eb Add lucide icon sprite partial (todo §1); src/icons.ts generates only-used symbols from pinned lucide-static 2026-06-15 11:44:40 +02:00
30db8216e6 Move foundation CSS into public/css (todo §1); drop placeholder style.css, repoint views + mockups 2026-06-15 11:25:43 +02:00
3b2ba76530 Add 403 + 500 error templates (todo §0); render 500 via app error handler 2026-06-14 19:41:19 +02:00
4eed701419 Scaffold Docker-only Node 24 + TypeScript EJS web backend 2026-06-14 11:45:30 +02:00