Skip to content

Recover accounts stuck behind an expired activation link - #826

Merged
zakius merged 2 commits into
stagingfrom
fix/810-expired-activation-recovery
Sep 12, 2026
Merged

zakius merged 2 commits into
stagingfrom
fix/810-expired-activation-recovery

Conversation

@zakius

@zakius zakius commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

Fixes #810.

The dead end. An expired activation link left the account inactive forever: re-registering the address hit the silent {success: true} no-op, and password reset requires an active account.

Registration as the recovery path. POST /api/register for an existing address now answers success as always, and lets the mailbox say what actually happened (only when outgoing mail is configured):

  • inactive + expired (or missing) activation token → mint a fresh 24h token (refreshActivationToken, status-gated), resend the activation mail;
  • active account → mint a 1h reset token and send a new account-exists notice whose link is the ordinary reset flow;
  • pending + still-valid link → unchanged no-op (the first mail covers it).

Both branches sit behind the register throttle, which moved ahead of them now that the route can send more than one kind of mail (ADR 0004's request-counter pattern).

Links survive being looked at. The SPA activation page no longer POSTs on mount — a scanner or mail preview executing the page used to consume the token. It now shows a confirmation with an Activate account button; the reset page was already submit-only, and opening either link changes nothing.

Tests. Route tests pin the fresh-activation and account-exists branches (and the no-mutation guarantee without mail), the mail-sender spec pins the notice's exact payload, and a browser spec proves a visit sends nothing while the button sends exactly one POST. Token digesting is shared via #shared/util/token-digest.ts now that two routes store digests.

An activation link older than a day was a dead end: the account stayed
inactive, registering again answered success and sent nothing, and the
password reset could not reach an account that never activated (#810).
Re-registration now reads the address's own state and answers only in
the mailbox: a pending registration gets a fresh activation link, and
an active account gets a notice -- the same success body either way,
so the browser still cannot tell who exists -- carrying a reset link,
the way in that never assumes the password still works.

The page behind an activation link no longer activates on visit either:
a mail preview or link scanner following the URL found the token spent
by the on-mount POST. Activation now happens on the button, and the
reset page was already submit-driven. Token digesting moves to a
shared helper, since registration now stores reset digests too.
@zakius
zakius merged commit 0fc9550 into staging Sep 12, 2026
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant