§8 review convergence (todo §8); re-ran the architecture + product reviewers to convergence — 5 rounds, until both returned zero new actionable findings. Fixed across rounds 1-4 (tests-first): bounded every outbound Ory fetch with a timeout (src/fetch-timeout.ts withTimeout + ORY_TIMEOUT_SEC default 5, incl. the http JWKS fetch) so a hung Ory can't park a request handler; anonymous on a permission-gated plugin route now 303→/login (was a dead-end 403; signed-in-without-role still 403); an already-signed-in user is sent home from /login + /registration; the onRequest hook short-circuit now sets the fresh CSRF cookie; admin-users malformed :id → 404 (was 500) via safeDecode; parseJwks validates key element shape (fails loud at load); removed the dead COOKIE_SECRET (loaded + enforced + documented but never read); documented HYDRA_ADMIN_URL; admin recovery shows the code + links to the public /recovery instead of the browser-unreachable admin-API link; reference-plugin breadcrumb-label + pagination/datetime README notes; corrected the contract doc to not over-promise a post-login "retry". Declined: unconditional base-ctx chrome (would build the menu per request, regressing the lazy hot path). Deferred → §9: return_to-preservation for deep-link login. Stability-reviewer on the cumulative diff: APPROVE, no Critical/High (addressed its Low nits). typecheck + 310 units + the full scripts/ci.sh gate (visual 9 · auth 1 · oauth 2 · full 6) green.

This commit is contained in:
2026-06-20 00:42:23 +02:00
parent bd20d00714
commit a20f3507e0
19 changed files with 181 additions and 59 deletions

View File

@@ -1,6 +1,7 @@
import { createApp } from "./app.ts";
import { loadConfig } from "./config.ts";
import { discoverPlugins } from "./discovery.ts";
import { withTimeout } from "./fetch-timeout.ts";
import { runBootHooks } from "./hooks.ts";
import { createHydraAdmin } from "./hydra-admin.ts";
import { createJwksProvider } from "./jwks.ts";
@@ -11,15 +12,17 @@ import { loadMenuConfig } from "./menu-config.ts";
const config = loadConfig(); // validates the env (incl. enforced secrets) — fails loud at boot
const menu = await loadMenuConfig(); // config/menu.ts override + branding — fails loud if malformed
// Every outbound Ory call is bounded so a hung/silent Ory can't park a request handler forever.
const oryFetch = withTimeout(fetch, config.oryTimeoutSec * 1000);
// Ory clients for the themed self-service routes + login completion (§4).
const kratos = createKratosPublic({ baseUrl: config.kratosPublicUrl });
const kratosAdmin = createKratosAdmin({ baseUrl: config.kratosAdminUrl });
const keto = createKetoClient({ readUrl: config.ketoReadUrl, writeUrl: config.ketoWriteUrl });
const kratos = createKratosPublic({ baseUrl: config.kratosPublicUrl, fetchImpl: oryFetch });
const kratosAdmin = createKratosAdmin({ baseUrl: config.kratosAdminUrl, fetchImpl: oryFetch });
const keto = createKetoClient({ fetchImpl: oryFetch, readUrl: config.ketoReadUrl, writeUrl: config.ketoWriteUrl });
// Hydra admin client for the OAuth2 login/consent challenge handshake (§6).
const hydra = createHydraAdmin({ baseUrl: config.hydraAdminUrl });
const hydra = createHydraAdmin({ baseUrl: config.hydraAdminUrl, fetchImpl: oryFetch });
// Session-JWT verify key: primed at boot from the configured JWKS (file mount, base64 inline,
// or fetched http), then served from cache with TTL refresh + rotation-on-miss (§4).
const jwks = await createJwksProvider(config.jwksUrl);
const jwks = await createJwksProvider(config.jwksUrl, { fetchImpl: oryFetch }); // bound an http JWKS fetch too
const plugins = await discoverPlugins(); // scans plugins/, validates — fails loud on a bad plugin
console.log(`Discovered ${plugins.length} plugin(s)${plugins.length ? `: ${plugins.map((p) => p.id).join(", ")}` : ""}`);