Link the admin's recovery-code notice to the flow the code was minted on #200
@@ -6,6 +6,12 @@ at boot. Entries start at 0.3.0.
|
||||
|
||||
## Unreleased
|
||||
|
||||
### The admin's recovery-code notice links to the flow the code was minted on
|
||||
|
||||
After an admin generates a recovery code for a user, the notice's link opens the recovery flow the
|
||||
code belongs to, where the user enters it. Before, it opened a new recovery flow at its email step,
|
||||
which doesn't accept the code.
|
||||
|
||||
### A verification or recovery code pasted with whitespace around it is accepted
|
||||
|
||||
The code field on the verification and recovery pages takes a code with spaces or tabs around it.
|
||||
|
||||
@@ -257,3 +257,20 @@ Read most (opens and searches):
|
||||
- `README.md` 2
|
||||
- `AGENTS.md` 1
|
||||
- `src/ui/theme-switch.test.ts` 1
|
||||
|
||||
## Reading cost, chunk 4a258a237c53, finished 2026-10-31T12:04:07Z
|
||||
|
||||
154777 new input tokens before the first commit.
|
||||
|
||||
Read most (opens and searches):
|
||||
|
||||
- `e2e-tests/full-flow.spec.ts` 9
|
||||
- `todo.md` 6
|
||||
- `README.md` 5
|
||||
- `src/auth/flow-view.ts` 5
|
||||
- `src/auth/flow-view.test.ts` 4
|
||||
- `src/auth/routes.ts` 4
|
||||
- `src/auth/kratos-public.test.ts` 3
|
||||
- `src/i18n/locales/en-US.ts` 3
|
||||
- `e2e-tests/compose.full.yml` 2
|
||||
- `src/auth/kratos-admin.ts` 2
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Admin user create/edit form body, captured into the shell content slot. Config:
|
||||
form { action, csrfToken, submitLabel, cancelHref, fields: field.ejs config[] }
|
||||
edit? { nextLabel, stateAction, recoveryAction, deleteAction } (edit mode only)
|
||||
recovery? { code? } shown after a recovery code is generated (recovery is code-based)
|
||||
recovery? { code, link } shown after a recovery code is generated; link is the flow the code was minted on, the only one that accepts it
|
||||
error? string shown when a write was rejected
|
||||
%><%
|
||||
const form = locals.form;
|
||||
@@ -14,7 +14,7 @@
|
||||
<%- include("partials/alert", { text: locals.error, tone: "neg" }) %>
|
||||
<% } -%>
|
||||
<% if (recovery) { -%>
|
||||
<div class="alert alert-pos" role="status"><svg class="ico ico-sm" aria-hidden="true"><use href="#i-check-circle"/></svg><div class="alert-body"><strong><%= t("admin.users.recovery.title") %></strong><span><%= t("admin.users.recovery.body") %> <a href="<%= localeHref("/recovery") %>"><%= t("admin.users.recovery.link") %></a></span><% if (recovery.code) { %><span class="recovery-code"><code><%= recovery.code %></code></span><% } %></div></div>
|
||||
<div class="alert alert-pos" role="status"><svg class="ico ico-sm" aria-hidden="true"><use href="#i-check-circle"/></svg><div class="alert-body"><strong><%= t("admin.users.recovery.title") %></strong><span><%= t("admin.users.recovery.body") %> <a href="<%= recovery.link %>"><%= t("admin.users.recovery.link") %></a></span><% if (recovery.code) { %><span class="recovery-code"><code><%= recovery.code %></code></span><% } %></div></div>
|
||||
<% } -%>
|
||||
<form class="form-card" method="post" action="<%= localeHref(form.action) %>">
|
||||
<input type="hidden" name="_csrf" value="<%= form.csrfToken %>">
|
||||
|
||||
@@ -63,7 +63,7 @@ test("admin Users screen: gate, list/filter, create, edit, deactivate, delete, r
|
||||
let lastCreate: { traits?: unknown } | undefined;
|
||||
const kratosAdmin = stubAdmin({
|
||||
createIdentity: async (payload) => { lastCreate = payload as { traits?: unknown }; const created = mk("grace@example.com"); store.push(created); return created; },
|
||||
createRecoveryCode: async (id) => ({ code: "123456", link: `http://kratos/self-service/recovery?code=123456&id=${id}` }),
|
||||
createRecoveryCode: async (id) => ({ code: "123456", link: `http://kratos/self-service/recovery?flow=flow-${id}` }),
|
||||
deleteIdentity: async (id) => { const i = store.findIndex((x) => x.id === id); if (i >= 0) store.splice(i, 1); },
|
||||
getIdentity: async (id) => store.find((x) => x.id === id) ?? null,
|
||||
listIdentities: async () => ({ identities: store, nextPageToken: null }),
|
||||
@@ -133,13 +133,13 @@ test("admin Users screen: gate, list/filter, create, edit, deactivate, delete, r
|
||||
assert.equal(target.state, "inactive");
|
||||
assert.equal(denylist.isRevoked(target.id, 0), true);
|
||||
|
||||
// Recovery: renders the edit page (200) showing the generated code (code-based; no admin-host link).
|
||||
// Recovery: renders the edit page (200) showing the generated code, linked to the flow it was minted on.
|
||||
const rec = await post(`/admin/users/${target.id}/recovery`, `_csrf=${token}`);
|
||||
assert.equal(rec.status, 200);
|
||||
const recHtml = await rec.text();
|
||||
assert.match(recHtml, /Recovery code generated/);
|
||||
assert.match(recHtml, /<code>123456<\/code>/);
|
||||
assert.doesNotMatch(recHtml, /self-service\/recovery\?code=/); // the unreachable admin-API link is gone
|
||||
assert.match(recHtml, new RegExp(`<a href="http://kratos/self-service/recovery\\?flow=flow-${target.id}">`)); // the code only works in the flow that minted it
|
||||
|
||||
// Delete needs a deliberate confirm step (zero-JS): GET renders the interstitial, POST performs it.
|
||||
const confirm = await (await get(`/admin/users/${target.id}/delete`)).text();
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
|
||||
| ID | Release | Blocked by | Exempt | Item | L | E | I | R | S | Moves | Score |
|
||||
|---|---|---|---|---|---|---|---|---|---|---|---|
|
||||
| 45 | 0.4.21 | | | **Link the admin's recovery-code notice to the flow the code was minted on, so the user can enter it.** | 1 | 2 | 2 | 3 | 2 | 2 by 5 | 7.0 |
|
||||
| 19 | 0.5.0 | | question | **Run the documented Docker commands on macOS and fix whatever misbehaves.** | 2 | 2 | 1 | 3 | 3 | 1 by 3 | 3.3 |
|
||||
| 16 | 0.5.0 | | question | **Decide whether the single Keto `Resource` namespace splits into per-domain namespaces.** | 3 | 2 | 3 | 6 | 6 | 7 by 2 | 0.5 |
|
||||
| 6 | 0.5.0 | | | **Warn at discovery when a route or nav node gates on a permission no plugin declares.** | 2 | 1 | 1 | 2 | 2 | 7 by 3, 9 by 3 | 8.0 |
|
||||
@@ -26,13 +25,6 @@
|
||||
|
||||
## Details
|
||||
|
||||
### 45. Link the admin's recovery-code notice to the flow the code was minted on, so the user can enter it.
|
||||
|
||||
The notice (`examples/plugins/admin/views/partials/user-form-body.ejs`) links a bare `/recovery`,
|
||||
which starts a fresh flow at its email step. Kratos binds an admin-minted code to the flow it was minted on,
|
||||
whose URL is the `recovery_link` that `createRecoveryCode` returns as `RecoveryCode.link`. Render that
|
||||
link instead.
|
||||
|
||||
### 19. Run the documented Docker commands on macOS and fix whatever misbehaves.
|
||||
|
||||
Needs a Mac. Two suspects, both from `--user "$(id -u):$(id -g)"`: a macOS `id -g` is `20`, which is
|
||||
|
||||
Reference in New Issue
Block a user