resend: accept an email address as well as a username #127
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#127
Läser in…
Hänvisa till i nytt ärende
Ingen beskrivning angiven.
Ta bort grenen "%!s()"
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 resend form (#116) accepts only a username. A user who never received their set-up email is far more likely to remember their email address than the username they picked minutes earlier — so the recovery path asks for the one thing they may not have.
Raised by the operator after walking the journey.
Why it was built username-only
Not arbitrary: the link is sent to the address on file, never to one the caller types. Accepting an address as the destination would let anyone have a stranger's credential-reset link delivered to themselves — an account-takeover primitive.
Accepting an address purely as a lookup key, while still sending to the registered address, is safe from that angle. The enumeration risk is also already handled: the endpoint returns an identical response whether or not the account exists.
So the objection is not security. It is that we have no way to resolve an email to a person.
What I verified, so nobody re-derives it
Kanidm has no server-side email filter on
/v1/person. Afilter=query parameter is silently ignored:Same count both times — the parameter does nothing.
The portal's own database cannot resolve it either.
signups(src/db/schema.ts) is keyed byusernameand deliberately stores no email, to avoid duplicating PII that Kanidm already holds.Options
/v1/raw/searchor similar) with different privileges. Worth ruling in or out before building a workaround, since it would make this trivial.Recommendation
Investigate option 3 first. If Kanidm can search by attribute with a scoped token, this becomes a small change and the right one. If it cannot, option 2 is the honest fallback, with option 4's copy improvement shipped alongside either.
Do not implement option 1.
Done when
Someone who did not receive their set-up email can request a new one using either their username or their email address, with the link still going only to the address on file, and with the response revealing nothing about which accounts exist.
Unblocking the investigation this issue asks for
The API question is settled. Checked directly against the running server's own OpenAPI document
(
https://auth.gitborg.se/docs/v1/openapi.json, served unauthenticated), so this is the surface of theversion we actually run rather than the surface of whatever version the docs describe.
Kanidm 1.10.4 exposes two search endpoints, so no schema change and no stored derived data are
needed:
GET /v1/person/_search/{id}mailtoo — still has to be established empirically.POST /v1/raw/searchSearchRequestbody, i.e. an arbitrary filter such as{"filter":{"eq":["mail","<address>"]}}. Its own summary says "Raw request to the system, be warned this can be dangerous!"There is also
GET /v1/person, which lists persons — a match could be done client-side without anysearch endpoint. That works at present scale and stops working quietly later, so it is a poor default.
What remains, and it is an authorisation question, not an API one
Whether the portal's scoped service account may run either call. It is a member of
idm_people_on_boarding,idm_people_adminsandidm_bitborg_ent_managers(
bitborg-infra/ansible/roles/kanidm/defaults/main.yml), andidm_people_adminsmanages personentries — so read access to
mailis likely, but "likely" is what this issue's sibling #291 was closedon once already and should not be repeated. Establish it by calling the endpoint with the portal's own
token.
Prefer
GET /v1/person/_search/{id}over/v1/raw/searchif it turns out to matchmail: a scopedendpoint is a smaller grant than an arbitrary-filter one, and the raw endpoint's own summary warns
against casual use.
Why the previous attempt stopped here
The implementation pass had no network access, so it could not run any of this and deliberately did not
build on a guessed endpoint. The copy half of the fix — telling people where their username is written
down — shipped in #136; that stands on its own and is what the issue said should ship alongside either
approach.
The stored-keyed-digest alternative remains the fallback if the lookup turns out to be denied. It should
stay the fallback: it costs a migration plus a new pepper secret that only bitborg-infra can provision,
helps zero existing accounts, and stores derived personal data to work around a lookup that the running
server appears to support.