§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:
12
src/app.ts
12
src/app.ts
@@ -152,6 +152,9 @@ export function createApp(options: AppOptions = {}): Server {
|
||||
if (anyRequestHooks) {
|
||||
const short = await runRequestHooks(plugins, ctx);
|
||||
if (short) {
|
||||
// Set the fresh CSRF cookie like every other page-emitting path, so a form the hook
|
||||
// renders (its token is in ctx.chrome.csrfToken) has the matching double-submit cookie.
|
||||
if (csrf.fresh) res.appendHeader("set-cookie", csrfCookie(csrf.token, { secure: secureCookies }));
|
||||
await sendResult(res, short.result, (view, data) => renderView(short.plugin.id, view, data));
|
||||
return;
|
||||
}
|
||||
@@ -164,6 +167,9 @@ export function createApp(options: AppOptions = {}): Server {
|
||||
if (match) {
|
||||
const routeCtx = buildContext(req, res, { chrome: chrome(), params: match.params, user, verifyCsrf });
|
||||
if (!isAuthorized(match.route, routeCtx.roles)) {
|
||||
// Anonymous → sign in (like the built-in screens' requireSession); a signed-in user who
|
||||
// simply lacks the role gets the 403 page.
|
||||
if (!routeCtx.user) { res.writeHead(303, { location: "/login" }).end(); return; }
|
||||
sendHtml(res, 403, await render("403", { title: "Forbidden" }));
|
||||
return;
|
||||
}
|
||||
@@ -213,6 +219,12 @@ export function createApp(options: AppOptions = {}): Server {
|
||||
// Themed Kratos self-service pages (login/registration/recovery/verification/settings).
|
||||
const flowType = AUTH_FLOWS[pathname];
|
||||
if (kratos && flowType && (method === "GET" || method === "HEAD")) {
|
||||
// Already signed in? Re-authenticating / re-registering is pointless — send them home.
|
||||
// (/settings, /recovery, /verification stay reachable — a signed-in user can use those.)
|
||||
if (ctx.user && (pathname === "/login" || pathname === "/registration")) {
|
||||
res.writeHead(303, { location: "/" }).end();
|
||||
return;
|
||||
}
|
||||
const cookie = req.headers.cookie;
|
||||
const flowId = ctx.url.searchParams.get("flow");
|
||||
if (!flowId) {
|
||||
|
||||
Reference in New Issue
Block a user