diff --git a/src/auth/flow-view.ts b/src/auth/flow-view.ts index 40d8c12..6a41a95 100644 --- a/src/auth/flow-view.ts +++ b/src/auth/flow-view.ts @@ -149,6 +149,10 @@ function toField(node: UiNode, name: string, type: string, t: Translate): FlowFi }; } +function formAction(flow: Flow, type: FlowType): string { + return CODE_FLOWS.has(type) ? `/${type}?flow=${encodeURIComponent(flow.id)}` : flow.ui.action; +} + export function buildFlowView(flow: Flow, type: FlowType, t: Translate = ENGLISH): FlowView { const hidden: { name: string; value: string }[] = []; const fields: FlowField[] = []; @@ -177,7 +181,7 @@ export function buildFlowView(flow: Flow, type: FlowType, t: Translate = ENGLISH } return { - action: CODE_FLOWS.has(type) ? `/${type}?flow=${encodeURIComponent(flow.id)}` : flow.ui.action, + action: formAction(flow, type), buttons, fields, hidden, diff --git a/src/auth/kratos-public.ts b/src/auth/kratos-public.ts index 146bf4b..59ef058 100644 --- a/src/auth/kratos-public.ts +++ b/src/auth/kratos-public.ts @@ -117,9 +117,8 @@ export function createKratosPublic(config: { baseUrl: string; fetchImpl?: typeof async submitFlow(type, flowId, opts) { const url = new URL(`${base}/self-service/${type}`); url.searchParams.set("flow", flowId); - // Asked for HTML, Kratos answers with the redirects a browser posting to it directly gets, so - // every success/error/expiry branch stays Kratos'. Its nosurf demands a Referer, which this - // POST lacks, only on an https request: valid while KRATOS_PUBLIC_URL is in-cluster http. + // Asked for HTML, Kratos answers with the redirects a browser gets, so every outcome stays Kratos'. + // Its nosurf wants a Referer, absent here, only over https: valid while KRATOS_PUBLIC_URL is http. const h = { ...headers(opts.cookie), accept: "text/html", "content-type": "application/x-www-form-urlencoded" }; const res = await http(url, { body: opts.body, headers: h, method: "POST", redirect: "manual" }); if (res.status >= 500) throw new KratosError(`Kratos submit ${type} flow failed (${res.status})`, res.status, await res.text()); diff --git a/src/auth/routes.test.ts b/src/auth/routes.test.ts index a4ac5c2..7d4f959 100644 --- a/src/auth/routes.test.ts +++ b/src/auth/routes.test.ts @@ -26,12 +26,14 @@ test("nothing wired ⇒ only the flow-error sink exists", () => { assert.deepEqual(keys(buildAuthRoutes(deps({}))), ["GET /error"]); }); -test("kratos wired ⇒ the themed flow pages + POST /logout; no OAuth2, no /auth/complete", () => { +test("kratos wired ⇒ the themed flow pages, the two code-flow submits + POST /logout; no OAuth2, no /auth/complete", () => { const got = keys(buildAuthRoutes(deps({ kratos }))); assert.deepEqual(got, keys([ ...Object.keys(AUTH_FLOWS).map((path) => ({ method: "GET", path })), { method: "GET", path: "/error" }, { method: "POST", path: "/logout" }, + { method: "POST", path: "/recovery" }, + { method: "POST", path: "/verification" }, ])); });