§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:
@@ -4,6 +4,7 @@
|
||||
// models; `handleAdminUsers` is the imperative shell app.ts dispatches to — gated admin-only,
|
||||
// CSRF-guarded, each action mapped to a RouteResult (render, or redirect after a write — PRG).
|
||||
|
||||
import { safeDecode } from "./admin-groups.ts";
|
||||
import { ADMIN_USERS_BASE, adminNav, buildConfirmModel, guardedForm, requireAdmin } from "./admin-nav.ts";
|
||||
import type { RequestContext, User } from "./context.ts";
|
||||
import type { Identity, KratosAdmin, RecoveryCode } from "./kratos-admin.ts";
|
||||
@@ -253,7 +254,7 @@ export function buildUserFormModel(opts: {
|
||||
{ id: "first", label: "First name", name: "first", optional: true, value: np.first },
|
||||
{ id: "last", label: "Last name", name: "last", optional: true, value: np.last },
|
||||
];
|
||||
if (!editing) fields.push({ autocomplete: "new-password", hint: "Optional — leave blank to have the user set one via a recovery link.", icon: "i-lock", id: "password", label: "Password", name: "password", optional: true, type: "password" });
|
||||
if (!editing) fields.push({ autocomplete: "new-password", hint: "Optional — leave blank to have the user set one via a recovery code.", icon: "i-lock", id: "password", label: "Password", name: "password", optional: true, type: "password" });
|
||||
|
||||
return {
|
||||
edit: editing ? {
|
||||
@@ -335,7 +336,8 @@ export async function handleAdminUsers(ctx: RequestContext, csrfToken: string, d
|
||||
if (seg.length === 1 && seg[0] === "new" && method === "GET") return renderForm({});
|
||||
|
||||
// /admin/users/:id …
|
||||
const targetId = decodeURIComponent(seg[0]!);
|
||||
const targetId = safeDecode(seg[0]!); // malformed %-encoding → 404, not a 500 (matches groups/roles/clients)
|
||||
if (targetId === null) return { html: await render("404", { title: "Not found" }), status: 404 };
|
||||
const identity = await kratosAdmin.getIdentity(targetId);
|
||||
if (!identity) return { html: await render("404", { title: "Not found" }), status: 404 };
|
||||
const back = `${ADMIN_USERS_BASE}/${encodeURIComponent(targetId)}`;
|
||||
|
||||
Reference in New Issue
Block a user