Skip to content

Sign-in: the e-mail code stands in for a second factor that is gone - #260

Merged
windischb merged 1 commit into
developfrom
feat/email-code-fallback-second-factor
Oct 13, 2026
Merged

windischb merged 1 commit into
developfrom
feat/email-code-fallback-second-factor

Conversation

@windischb

Copy link
Copy Markdown
Contributor

Why

An administrator whose only second factor was a passkey on a broken device could not sign in: the login asked for that passkey and offered nothing else. The e-mail code could not be switched on either, because that is an account change, which needs the same passkey. The way back in was the recovery CLI, which a realm administrator cannot reach.

What changes (ADR 0025, amendment 3)

  • After a password, the e-mail code is offered beside the user's own second factor (TOTP or a usable passkey) wherever the target offers the e-mail code after a password. No opt-in; a confirmed address is required. Browser only.
  • It stands in for a factor the user has. A user without any second factor still gets the setup duty, and the fallback never raises the level a sign-in must reach.
  • A session signed in with the password and a fresh e-mail code may change what protects the account: remove the passkey that is gone, set up an authenticator app.
  • For EmailFallbackWaitHours (new realm setting, default 24, 0–720) after a password reset by e-mail the fallback is neither offered nor accepted: password and code would come from the same mailbox.
  • A realm that does not offer the e-mail code after a password has no fallback.

This deliberately reverses the earlier hardening that issued the e-mail second factor only to users who had switched it on (audit M2). The opt-in still gates the code for a user without another factor.

Also

  • A password reset by e-mail now sends the account-change notice; only the change in the profile did.
  • /api/account/email-otp/login accepts a code only where one may be issued as this user's second factor. A code issued by the e-mail-code sign-in shares the challenge and was accepted there after a password.

Not covered

Accounts without a password, and realms that switch the fallback off, still need the operator. A reset by another administrator in the admin console and recovery codes are not part of this.

Tests

  • Unit tests for the rule and the wait.
  • Integration scenarios S15–S17: sign in and remove the passkey; the wait after a reset, including a code from the sign-in path (fails without the new check); confirming an account change from a password session; the realm switch.
  • Full backend suite before the last two additions: 1819 unit tests and 960 of 961 integration tests passed, the one failure being the new wait test in an intermediate state. With the final code, 92 integration tests around sign-in, e-mail codes, password reset and realm settings passed.
  • Not checked in a browser: the new field in the realm settings.

🤖 Generated with Claude Code

…gone

An administrator whose only second factor was a passkey on a broken device
could not sign in: the login asked for that passkey and offered nothing
else, and the e-mail code could not be switched on because that is an
account change, which needs the same passkey. The way back in was the
recovery CLI, which a realm administrator cannot reach.

ADR 0025, amendment 3:

- After a password, the e-mail code is offered beside the user's own second
  factor (TOTP or a usable passkey) wherever the target offers the e-mail
  code after a password. No opt-in; a confirmed address is required. Browser
  only: no native grant starts with a password.
- It stands in for a factor the user has. A user without any second factor
  still gets the setup duty, and the fallback never raises the level.
- A session signed in with the password and a fresh e-mail code may change
  what protects the account (remove the passkey, set up TOTP). The step-up
  for an account change offers the code.
- For EmailFallbackWaitHours (realm setting, default 24, 0-720) after a
  password reset by e-mail the fallback is neither offered nor accepted:
  password and code would come from the same mailbox.
  UserSecurityData.PasswordResetByEmailAt records the reset.
- A realm that does not offer the e-mail code after a password has no
  fallback.

This deliberately reverses the earlier hardening that issued the e-mail
second factor only to users who had switched it on (audit M2). The opt-in
still gates the code for a user without another factor.

Also:

- A password reset by e-mail now sends the account-change notice; only the
  change in the profile did.
- /api/account/email-otp/login accepts a code only where one may be issued
  as this user's second factor. A code issued by the e-mail-code sign-in
  shares the challenge and was accepted there after a password.

Tests: unit tests for the rule and the wait; integration scenarios S15-S17
(sign in and remove the passkey, the wait after a reset including a code
from the sign-in path, confirming an account change from a password
session) and the realm switch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@windischb
windischb merged commit 915f0b5 into develop Oct 13, 2026
8 checks passed
@windischb
windischb deleted the feat/email-code-fallback-second-factor branch October 13, 2026 14:25
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