Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions docs/admin/realm-settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,10 +186,11 @@ The realm's sign-in policy (ADR 0025; concept: [Sign-in levels](../concepts/sign
| **Administration minimum level** (`AdministrationMinimumLevel`) | The level a session needs for `/api/admin/*` (default `Multi`). Realm only; kept separate so end users of a code-only App are not pushed into 2FA by opening their profile. `Single` is meant for test and development realms; the UI warns about it |
| **Setup grace days** (`SetupGraceDays`) | How long a user without a second factor may keep signing in at `Single` to something that requires `Multi`. `0` = set up immediately |
| **First factors** | `Password`, `EmailCode`, `Passkey` — what the realm offers at all; an App can only offer a subset |
| **Second factors** | `Totp`, `EmailAfterPassword` |
| **Second factors** | `Totp`, `EmailAfterPassword`. With `EmailAfterPassword` on, the e-mail code also stands in for a user's own second factor that is gone (see [When the second factor is gone](../concepts/sign-in-levels#when-the-second-factor-is-gone)) |
| **E-mail fallback wait** (`EmailFallbackWaitHours`) | Hours after a password reset by e-mail during which the e-mail code does not stand in for a user's own second factor (default `24`, `0` = no wait, at most `720`). Realm only |
| **User's own factor not offered** (`OwnFactorNotOffered`) | `Ignore` or `RequireViaBrowser` — what happens when a user switched on a second factor that the App does not offer |

New realms are seeded with: minimum `Single`, administration minimum `Multi`, grace 14 days, password on, e-mail code off, passkey on, TOTP on, e-mail after password on, `RequireViaBrowser`. A realm that never saved the section runs with the same values, except that the e-mail code is on when native grants are switched on for the realm or any App (see [Upgrading](../concepts/sign-in-levels#upgrading-from-0-14-and-earlier)). A policy that cannot be satisfied (`Multi` required, no way to reach it) is rejected on save. In a realm manifest the section lives under `Settings.SignIn`.
New realms are seeded with: minimum `Single`, administration minimum `Multi`, grace 14 days, password on, e-mail code off, passkey on, TOTP on, e-mail after password on, e-mail fallback wait 24 hours, `RequireViaBrowser`. A realm that never saved the section runs with the same values, except that the e-mail code is on when native grants are switched on for the realm or any App (see [Upgrading](../concepts/sign-in-levels#upgrading-from-0-14-and-earlier)). A policy that cannot be satisfied (`Multi` required, no way to reach it) is rejected on save. In a realm manifest the section lives under `Settings.SignIn`.

## Sessions

Expand Down
11 changes: 11 additions & 0 deletions docs/concepts/sign-in-levels.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,14 @@ need a recent proof (within **15 minutes**) of an account factor the user has. E

"Recent" is read from the session: a sign-in a minute ago is the proof, and nothing is asked again. Adding the *first* account factor needs only the proof of what the user has; adding a further one needs the existing one. Every account change sends a notice to the account's e-mail address.

### When the second factor is gone

A passkey lives on a device and an authenticator app on a phone; both can be lost. After a **password**, the login page therefore offers the e-mail code beside the user's own second factor, wherever the target offers the e-mail code after a password. The user does not have to switch it on; the address must be confirmed. The same proof — a session signed in with the password, the mailbox proven within the window — confirms an account change, so the user can remove the factor that is gone and set up a new one.

It stands in for a factor the user has; it is no substitute for having one. A user without any second factor still gets the setup duty, and the fallback never raises the level a sign-in must reach.

A password that was just reset by e-mail does not count: for the realm's **wait** (`EmailFallbackWaitHours`, default 24) the fallback is not offered, because the password and the code would both come from the same mailbox. With the fallback on, whoever holds the mailbox for longer than the wait can take over an account that has a password. A realm that does not accept that switches "E-mail code after a password" off; then a lost factor is reset by the operator with the [recovery CLI](/operate/recovery-cli).

**Account deletion is always reachable.** No minimum level and no setup grace ever block it; it needs the same proof as every other account change.

Account-change endpoints answer `403 { "RequiresReauthentication": true, "Methods": [...] }` when the proof is missing or too old; the SPA asks for it and retries.
Expand Down Expand Up @@ -179,6 +187,9 @@ Each scenario has a fixed number; the integration tests carry the same number. U
| S12 | A session signed in with an e-mail code opens the admin console | The administration asks for the missing factor (step-up) or, without one, for its setup |
| S13 | A session below the required level opens any page of Modgud's UI | The page loads and shows the step-up or the setup, never a raw error body |
| S14 | An administrator signs in to Modgud itself with a password or an e-mail code | The second factor is asked before the sign-in completes; without one, the setup is offered (grace) or required. The same administrator signing in for App A follows App A's rules |
| S15 | An administrator's only second factor is a passkey on a device that is gone; they sign in with the password | The e-mail code is offered beside the passkey; with it the session is `multi`, and the same session removes the passkey |
| S16 | The same, but the password was reset by e-mail an hour ago and the realm's wait is 24 hours | Only the passkey is offered; no code is sent |
| S17 | A password-only session wants to remove a passkey that is an account factor | The page asks for the passkey or a fresh e-mail code |

### The account

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -307,3 +307,41 @@ With the realm at `single` and the administration at `multi`, an administrator w
- An administrator who has lost their second factor can no longer sign in to Modgud at the portal's level to look around; the way back in is another administrator or the recovery CLI, as it already was for the administration itself.
- An administrator without a second factor sees the setup offer at every sign-in to Modgud during the grace, not only after the first visit to the administration.
- Scenario S14 is new; S43 now describes the session that comes from an App.

## Amendment 3 — the e-mail code stands in for a second factor that is gone

### Why

An administrator enrolled a passkey as their only second factor and the device broke. The sign-in asked for that passkey and offered nothing else. The e-mail code could not be switched on, because switching it on is an account change and an account change needs the account factor — the same passkey. The only way back in was the recovery CLI, which a realm administrator cannot reach: it needs a shell on the server. Amendment 2 names "another administrator" as a way back in; the admin console has no such action.

"Password + e-mail code" already is `multi` (§1). It was only offered to users who had switched it on.

### Decision

- **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 (`EmailAfterPassword`). The user does not have to switch it on. It needs a confirmed e-mail address.
- **It stands in; it does not replace the setup.** A user without any second factor still gets the setup duty (grace, then required). The fallback creates no demand of its own: it never raises the level a sign-in must reach. The user's own switch keeps its meaning (§7).
- **Still only after a password.** After an e-mail code or a magic link, a second e-mail code proves the same mailbox again. An account without a password has no fallback.
- **The same proof confirms an account change.** A session that was signed in with the password and proved the mailbox within the window (C) may change what protects the account — remove the passkey that is gone, set up an authenticator app. The login page offers the e-mail code in that step.
- **A password that came through the mailbox does not count.** For `EmailFallbackWaitHours` (realm setting, default 24, `0` = no wait) after a password reset by e-mail, the fallback is neither offered at sign-in nor accepted for an account change: the password and the code would be one proof. A password the user changed while signed in, or one an administrator set, starts no wait.
- **A realm switches the fallback off** by not offering the e-mail code after a password. Then the ways back in are the recovery CLI or an administrator action, as before.

### What this changes about "what the mailbox alone can do"

The table in C gains a time dimension for a user with an account factor and a password:

| The user has… | With the mailbox alone, an attacker… |
|---|---|
| An account factor and a password, fallback on | resets the password and, once the wait is over, signs in with the e-mail code and can change the account. The reset ends every session, and the reset and every account change send a notice to the address |
| An account factor, fallback off or no password | as before: no account change |

The wait does not stop an attacker who holds the mailbox for longer than the wait and whose reset goes unnoticed. It stops the reset-then-code sequence from being a single step, and it gives the owner the length of the wait to react to a reset they did not ask for. A realm that needs "the mailbox alone is never enough" switches the fallback off.

This reverses an earlier hardening on purpose: the e-mail second factor was issued only to users who had switched it on, so that an account with TOTP could not be reduced to its mailbox. The switch still gates the code for a user without another factor; beside another factor, the wait takes its place.

### Consequences

- A user who lost their only second factor signs in with password and e-mail code, removes the lost factor and sets up a new one, without an administrator or the operator.
- Users with TOTP or a passkey see "Code via Email" as a further choice after their password where the target offers it.
- Not covered: an account without a password, and a realm that does not offer the e-mail code after a password. Recovery codes and a reset by another administrator in the admin console remain possible additions; neither is decided here.
- New realm setting `EmailFallbackWaitHours`; `UserSecurityData.PasswordResetByEmailAt` records the last reset by e-mail.
- Scenarios S15–S17 are new.
2 changes: 1 addition & 1 deletion docs/end-user/first-steps.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ Profile → Account. Make sure your first name, last name, and email are correct
- **Forgot password but have email**: use "Forgot password?" on the login page → a password-reset link arrives in your inbox → click it to set a new password directly.
- **Forgot password, no email access**: contact your admin. They can send you a sign-in link.
- **Lost your 2FA device, but still have another method enrolled** (e.g. a passkey): sign in with that method, then remove the lost device from Profile → Security and enrol a replacement.
- **Lost your only 2FA device**: contact your admin. They can reset 2FA for you — you'll re-enrol from scratch.
- **Lost your only 2FA device**: sign in with your password and choose **Code via Email** as the second step. Then remove the lost device from Profile → Security and enrol a replacement. If that choice is not offered (your organisation switched it off, you have no password, or you reset your password by e-mail in the last day), contact your admin.

## Tips

Expand Down
2 changes: 1 addition & 1 deletion docs/end-user/passkey.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ Deleting a passkey on Modgud's side **only removes the registration** — your d
Two scenarios:

- **You have other passkeys / 2FA methods**: sign in with one of those, delete the lost device's passkey from Modgud, enrol a fresh one.
- **You have nothing else**: contact your admin. They can reset 2FA for you; you'll re-enrol from a clean slate.
- **You have nothing else**: sign in with your password and choose **Code via Email** as the second step; then delete the lost device's passkey and enrol a fresh one. The code is not offered if your organisation switched it off, if you have no password, or for a day after you reset your password by e-mail — then contact your admin.

## Browser support

Expand Down
2 changes: 1 addition & 1 deletion docs/end-user/two-factor.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ Some realms require 2FA. If you try to disable your only remaining method there,
Two scenarios:

- **You have another 2FA method enrolled** (e.g. a passkey) → sign in with that instead, then remove the lost authenticator from Profile → Security and enrol a replacement.
- **The authenticator was your only method** → contact your admin. They can `Reset 2FA completely` for you, opening a fresh grace period in which you can re-enrol.
- **The authenticator was your only method** → sign in with your password and choose **Code via Email** as the second step; then remove the lost authenticator from Profile → Security and enrol a replacement. The code is not offered if your organisation switched it off, if you have no password, or for a day after you reset your password by e-mail — then contact your admin.

## Multiple methods

Expand Down
8 changes: 6 additions & 2 deletions docs/operate/recovery-cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -295,8 +295,12 @@ to the console, not recorded.

- **No admin can sign in** → `bootstrap-admin` (Direct mode) creates
a fresh admin in one shot.
- **A user lost their 2FA device** → `reset-2fa <username>` then
`magic-link <username>` so they can log in and re-enrol.
- **A user lost their 2FA device** and cannot get back in themselves →
`reset-2fa <username>` then `magic-link <username>` so they can log in
and re-enrol. A user with a password usually does not need this: the
e-mail code stands in for the lost factor (see
[When the second factor is gone](../concepts/sign-in-levels#when-the-second-factor-is-gone)),
unless the realm switched that off or the account has no password.
- **Production hostname doesn't route to a realm** → `realm-list` to confirm what's configured, then `realm-add-domain` to bind the new hostname, then `realm-set-primary-domain` to make it the realm's canonical primary.
- **Marten projections out of sync after a schema change** →
`rebuild-projections`.
Expand Down
Loading
Loading