test(signup): assert the resend lookup guarantees instead of commenting them #215
Inga granskare
Etiketter
Inga etiketter
area/backups
area/ci
area/control-panel
area/identity
area/infra
area/observability
area/payments
area/security
area/storage
area/web
blocked
needs-info
needs-triage
ready-for-implementation
type
bug
type
chore
type
docs
type
epic
type
feature
type
task
wontfix
Ingen milstolpe
Inget projekt
Inga tilldelade
1 deltagare
Notiser
Förfallodatum
Inget förfallodatum satt.
Beroenden
Inga beroenden satta
Referens
bitborg/bitborg-web!215
Läser in…
Hänvisa till i nytt ärende
Ingen beskrivning angiven.
Ta bort grenen "feat/resend-lookup-and-rate-limit"
Borttagning av en gren är permanent. Även om den borttagna grenen kan fortsätta existera en kort tid innan den faktiskt tas bort, kan det INTE ångras i de flesta fall. Vill du fortsätta?
The email-address lookup for re-issuing a set-up link shipped with #127. Its security argument
lived in comments. These are the tests that hold it, written against the code as it stands in
main. No behaviour changes. Tests only.The argument is that a typed address is only a lookup key. The link goes to the address on file,
re-read from the person entry after the account is found. It never goes to the string the caller
typed. A typed address used as the destination would let anyone have a stranger's
credential-reset link delivered to themselves. A test asserts the two cases apart: an account
found by one address and registered under another is mailed at the registered one.
Non-enumerability is now asserted rather than claimed. The route must answer identically for:
That last case is what makes the endpoint fail closed. If the Kanidm read grant is ever lost, the
route behaves exactly as it does for an unknown address, while logging the HTTP status and naming
the likely cause. A suite covering only the happy path would not catch that regressing.
The
kanidm.test.tsadditions cover two details. The lookup key is lowercased, because Kanidm'smail values compare exactly and the key must match how every write path stores an address. A
resolved name is re-validated against the Kanidm name charset before it reaches an API path, even
though it came back from Kanidm rather than from the caller.
Note on the commit message
The commit body on this branch describes the implementation, which is already in
main. The diffis tests only. The branch name mentions a rate limit for the same reason:
rateLimitIpis alreadyin
main. Treat this description as the accurate one.Verified
pnpm test: 322 passed, up 17 from main's 305pnpm check: 0 errors, 0 warningsRefs #127
The resend form asked for a username — the one thing a person who never received their set-up email is least likely to remember, since the username is written down in the email that never arrived. An address is now accepted as well, but ONLY AS A LOOKUP KEY. The link still goes to the address on file, re-read from the person entry after the account is found, never to the string the caller typed. That distinction is the whole security argument, and it is why the form was username-only to begin with: a typed address used as the DESTINATION would let anyone have a stranger's credential-reset link delivered to themselves. Used only to find the account, it grants a caller nothing they did not already have, because the message still goes where the account already points. A test asserts the two cases apart — an account found by one address and registered under another is mailed at the registered one. The API question the issue asked to settle, settled: /v1/person/_search/{id} is a substring match on `name` alone (f_sub(Attribute::Name, ...) in Kanidm 1.10.4's v1 handlers), so it can never match an address, and GET /v1/person cannot be filtered at all. POST /v1/raw/search is the only endpoint that can. Despite its "be warned this can be dangerous" summary it is a READ, served from the read query server, and the filter is built here — the caller's input appears in it only as the string value of a single `eq` term, so an anonymous visitor cannot reshape the query. No schema change, no stored derived personal data, and no new privilege: Kanidm's built-in idm_acp_people_pii_read names idm_people_admins as a receiver with `mail` among its searchable attributes, which is the same grant that already lets this service account read `mail` off GET /v1/person/{name}. Non-enumerability is preserved and now has tests that assert it rather than comments that assert it. The route answers identically for: a username that exists and one that does not, an address that exists and one that does not, a username and the address on the same account, an account with no address on file, a refused mail send, and a lookup that cannot run at all. That last one is what makes this fail closed — if the read grant is ever lost the endpoint behaves exactly as it does for an unknown address, while logging the HTTP status and naming the likely cause. Kanidm's mail values compare exactly, so the lookup key is lowercased to match how every write path stores an address. A resolved name is re-validated against the Kanidm name charset before it goes anywhere near an API path, even though it came back from Kanidm rather than from the caller. Closes #127Closing. This work is already in
main, and this branch is a pre-#180 copy of it.src/pages/api/resend-setup-link.test.tshere is byte-identical tosrc/test/resend-setup-link.test.tson
main, apart from the relative import paths. #180 moved that file out ofsrc/pageson2026-08-02 and added the lint rule that fails CI here. This branch predates both, so it still carries
the file at the old path.
The
src/lib/kanidm.test.tschange appends a seconddescribe("soleSearchResultName")whose fourcases have the same titles as the block already on
main.So the 17 extra passing tests were duplicates running twice, not new coverage. Nothing here is worth
salvaging. Branch to be deleted.
Ändringsförfrågan stängd