signup: no resend path for the setup-link email — a failed send strands the account #116

Stängd
öppnade 2026-07-31 11:58:31 +00:00 av supernaut · 0 kommentarer
Ägare

When the setup-link email fails to send, the account is already provisioned in Kanidm but the person
has no way to set a credential. There is no resend path — recovery requires SSH access and the
runbook.

src/pages/api/signup.ts:188 names the gap in a comment:

// Set-up link email in the user's language (best-effort — the Kanidm person already
// exists, so a mail failure must not fail the sign-up; a resend path can re-issue the
// link). Just log failures so bounces are visible.

The best-effort design is right and should stay: a mail hiccup must not fail a sign-up that already
succeeded. But the consequence is that a failed send leaves a user stranded with no self-service
route out, and no operator route either short of a shell.

This is not hypothetical

It happened on 2026-07-31. A wrong-account Sweego API key made every send fail with HTTP 422
(bitborg-infra#273). bitborg-test-1 was provisioned with no deliverable link, and recovering it
meant:

kanidm person credential create-reset-token --ttl 86400 <username> --name idm_admin

then handing the printed URL over out of band. For a test account that is merely awkward. For a real
sign-up it means an account that looks created, a user who cannot log in, and no way for them to
report it except email — which is the thing that is broken.

What exists today

The mechanism is already there and used at sign-up: a Kanidm credential update-intent
(src/lib/kanidm.ts — GET /v1/person/<username>/_credential/_update_intent/<ttl>), turned into
<publicBase>/ui/reset?token=… by resetLinkUrl. TTL is 24 h (RESET_TTL_SECONDS). So a resend is
re-minting an intent and re-sending the same email — no new integration.

Design considerations

The obvious shape — an unauthenticated "resend my setup link" form taking an email address — is an
abuse and enumeration risk: it mints credential-reset intents for arbitrary accounts and reveals
which addresses exist. Worth thinking about before building:

  • Rate limit and captcha it the way sign-up already is (src/lib/captcha.ts, ADR 0029), and
    respond identically whether or not the address matches an account.
  • Or make it operator-only — an authenticated admin action in the control panel, which avoids the
    enumeration surface entirely and still removes the SSH requirement. Smaller scope, less user-facing
    value.
  • Or surface it at the point of failure: when sendEmail fails outright, tell the user their
    account exists but the email could not be sent, with a support contact. Cheapest, and it converts
    an invisible failure into a visible one even without a resend button.

The third option composes with either of the first two and is worth doing regardless — it is the same
"best-effort masks systematic failure" gap recorded as gap 3 in #115.

Done when

A person whose setup-link email failed can obtain a working link without an operator opening an SSH
session, and the path cannot be used to enumerate accounts or mint unbounded reset intents.

When the setup-link email fails to send, the account is already provisioned in Kanidm but the person has no way to set a credential. There is no resend path — recovery requires SSH access and the runbook. `src/pages/api/signup.ts:188` names the gap in a comment: ```ts // Set-up link email in the user's language (best-effort — the Kanidm person already // exists, so a mail failure must not fail the sign-up; a resend path can re-issue the // link). Just log failures so bounces are visible. ``` The best-effort design is right and should stay: a mail hiccup must not fail a sign-up that already succeeded. But the consequence is that a failed send leaves a user stranded with no self-service route out, and no operator route either short of a shell. ## This is not hypothetical It happened on 2026-07-31. A wrong-account Sweego API key made every send fail with HTTP 422 (bitborg-infra#273). `bitborg-test-1` was provisioned with no deliverable link, and recovering it meant: ```bash kanidm person credential create-reset-token --ttl 86400 <username> --name idm_admin ``` then handing the printed URL over out of band. For a test account that is merely awkward. For a real sign-up it means an account that looks created, a user who cannot log in, and no way for them to report it except email — which is the thing that is broken. ## What exists today The mechanism is already there and used at sign-up: a Kanidm credential update-intent (`src/lib/kanidm.ts` — `GET /v1/person/<username>/_credential/_update_intent/<ttl>`), turned into `<publicBase>/ui/reset?token=…` by `resetLinkUrl`. TTL is 24 h (`RESET_TTL_SECONDS`). So a resend is re-minting an intent and re-sending the same email — no new integration. ## Design considerations The obvious shape — an unauthenticated "resend my setup link" form taking an email address — is an abuse and enumeration risk: it mints credential-reset intents for arbitrary accounts and reveals which addresses exist. Worth thinking about before building: - **Rate limit and captcha it** the way sign-up already is (`src/lib/captcha.ts`, ADR 0029), and respond identically whether or not the address matches an account. - **Or make it operator-only** — an authenticated admin action in the control panel, which avoids the enumeration surface entirely and still removes the SSH requirement. Smaller scope, less user-facing value. - **Or surface it at the point of failure**: when `sendEmail` fails outright, tell the user their account exists but the email could not be sent, with a support contact. Cheapest, and it converts an invisible failure into a visible one even without a resend button. The third option composes with either of the first two and is worth doing regardless — it is the same "best-effort masks systematic failure" gap recorded as gap 3 in #115. ## Done when A person whose setup-link email failed can obtain a working link without an operator opening an SSH session, and the path cannot be used to enumerate accounts or mint unbounded reset intents.
supernaut lade till detta till projektet Bitborg Web 2026-07-31 11:58:41 +00:00
Logga in för att delta i denna konversation.
Ingen milstolpe
Inget projekt
Inga tilldelade
1 deltagare
Notiser
Förfallodatum
Förfallodatumet är ogiltigt eller utanför gränserna. Använd formatet "åååå-mm-dd".

Inget förfallodatum satt.

Beroenden

Inga beroenden satta

Referens
bitborg/bitborg-web#116
Ingen beskrivning angiven.