feat(signup): re-issue a setup link when the email never arrived #118
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!118
Läser in…
Hänvisa till i nytt ärende
Ingen beskrivning angiven.
Ta bort grenen "feat/116-resend-setup-link"
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?
Closes #116. Implements both halves we agreed: the recovery path (A) and making the failure visible (B).
The constraint that shaped everything
Someone whose setup-link email failed has an account but no credential, so they cannot log in. That rules out an authenticated recovery page and forces an anonymous-reachable path — which is exactly where this feature could go wrong.
Send to the address on file, never one supplied
The form takes a username. The link goes to the address already registered on the account.
Accepting an email address would let anyone request a stranger's credential-reset link and have it delivered to themselves. That is not a recovery path, it is an account-takeover primitive. This is the single most important decision in the change.
Uniform response
An unknown username, a person with no address, and a provider failure all return the same
sentstatus as a genuine success. The difference is logged for operators and never shown, so the endpoint cannot enumerate who has an account.The success and not-found branches deliberately return the same thing; a comment says so, because "tidying" that into distinct statuses would silently reintroduce the oracle.
Other hardening
../adminoralice/../bobwould address a different resource. Tested, with an assertion that Kanidm is not contacted at all.kanidmResendDeps), so a resend cannot mutate a person even if the calling code is wrong.signupsOpen()— closing registration must not strand people who already have an account.a b&c=d).The B half: the failure is now visible
A sign-up whose email fails returns
nomailinstead ofsuccess. The user is told the account exists, the email did not arrive, and how to get it re-sent — instead of being told to check an inbox that will stay empty.That false reassurance is precisely how a completely broken sender looked identical to a working sign-up from the outside on 2026-07-31.
UI shape
The re-issue view is a separate view on
/signuprather than a second form on the default page, so each view carries exactly one captcha widget and an ordinary visitor is never asked to solve proof-of-work twice./signup?status=resendis a durable URL for someone returning later, linked from the sign-up form.New sv/en strings pass the content-style detector (Swedish terms tracking Forgejo
sv-SE, British spelling, brand casing, sentence-case buttons).Built test-first
15 tests in
src/lib/resend.test.ts, each watched failing before the code existed. Beyond the happy path they cover malformed Kanidm responses (null, non-object, wrong types, blank address), the no-intent-for-unknown-user assertion, token encoding, and path-traversal usernames.129 tests pass,
astro check0 errors, eslint + stylelint clean.Known limitation
A person who never received the email and does not remember their username still needs operator help. Recovering by email address is what would fix that, and it is the thing that cannot be done safely without also building address verification — deliberately out of scope.