docs(auth): the full reading, and the rules as they stand now (ADR 0017)

ADR 0017's "Full reading (21 Oct 2026)": each server finding and what was
done (H1, M1, M2, M3, L1, L4, L5, L6, L7, L10), those accepted with their
reasons (L2, L3, L8, L9), and the device's M1, the owner's proof bound to
its operation and the whole base URL, still to do. "The rules as they
stand" brought up to date; the module README and Learn's AGENTS.md too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-21 13:39:18 +02:00
commit 8ec2743bfa
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -429,31 +429,83 @@ authenticator. What was done (`18ab2ee`, `f3c8706`, `403b88c`):
a warning and secures nothing. A new password clears it, as does deleting the person.
An owner who stays signed in sees it only in the account section.
### Full reading (21 Oct 2026)
The whole branch read again, the server and the device apart, against the rules below.
The developer decided (21 Oct): resets wait while their owner is held out and devices
show them; resets lapse; GitHub-only accounts go at GitHub's end; `http://` instances are
refused but on this machine; the phrase is saved, never copied. Server side:
- **H1, a reset pushed through while its owner was held out.** Whoever knew the password
and had the mailbox asked an authenticators' reset and kept the codes waiting until it
took effect; whoever had the mailbox filled the address's ceiling from three networks
until an account's reset did. Every hold-out after a reset was asked (a wait beginning,
an attempt a wait refuses, a sign-in or proof the ceiling refuses) now moves the
account's resets not carried out to 72 hours after it ends (`pushOutResets`); a reset
carried out is never pushed, a lapsed one never brought back. `SignInWays` carries
when each waiting reset takes effect; the account section shows them with Call it off,
which takes the owner's proof (`password.callOffResets`), no wait holding it back.
- **M1 and L6, GitHub's end.** At `GITHUB_SIGN_IN_ENDS` the daily run first deletes the
accounts that sign in with GitHub alone (no address, never a password), members told;
the app tells them so for the three months before. `password.set` adds an address
without GitHub only for someone anonymous where the session lives (`anonymousHere`,
which a member tells), so a stolen session of a forgotten GitHub account claims
nothing.
- **M2, resets that never lapsed.** A reset that took effect and wasn't carried out lapses
a week later, as never asked (`resetLapsesAfter`; the pruning forgets it); asked again,
it waits anew with a new mail. An authenticators' reset carried out never lapses.
- **M3, the phrase held back by the password's waits.** Codes count per account and way
(`SecondFactorPath`: after the password, after the phrase), each with its own waits; an
accepted code clears both.
- **L1.** Only a session made after the warning dismisses it (`signedInBefore` otherwise).
- **L4.** Replacing a setup's password ends the account's other sessions.
- **L5.** Members learn of ended sessions in the background, this account's notices
first, never inside the request.
- **L7.** A member asks the register whether the person is its account before deleting,
rather than trusting the session's scope.
- **L10.** A member clock more than five seconds ahead of the register's is refused as
one behind is.
- **Accepted:** L2, the phrase alone sets a password once an authenticators' reset took
effect (the phrase is 256 bits, and the reset's 72 hours were the owner's to call it
off); L3, a codes wait tells whoever guesses the password that it was right, which
matters only once someone holding it made codes wait; L8, decoys answer today's costs
(`newPasswordCosts`), revisit when `PasswordKdf.current` rises; L9, a key proof's first
step gives out an address's public keys (step 4 shares keys: revisit then).
- **Not done yet, the device's M1:** the owner's proof is bound to the challenge and the
host, not to the operation it approves nor the instance's whole base URL; binding a
purpose label and the digest of what is set into SRP's identity on both sides, and
scheme, host, port and path in place of the host, is the next change. Until then a
`change` challenge's proof can be replayed for another signed-in change by whoever
reads and changes the traffic (plain `http://`, TLS intercepted), which the refusal of
`http://` instances (the device's part 2) narrows.
### The rules as they stand (21 Oct 2026)
Who changes what of an account, with which proofs. "Password" is SRP's proof bound to
the host the device addressed; "code" a TOTP code from a confirmed authenticator or a
recovery code; "mailed code" one asked for that purpose, for the account's own address
unless said otherwise. Signed-in proofs count per session (ten wrong in a row end it);
sign-ins' codes per account (waits from ten wrong in a row, the owner mailed).
sign-ins' codes per account and way, after the password or the phrase (waits from ten
wrong in a row, the owner mailed).
| What | Without a session | Signed in |
|---|---|---|
| Sign in | password + code; password alone once an authenticator reset took effect, until one is confirmed; password + mailed code (`finishSetup`) while no authenticator was ever confirmed | — |
| Sign up / first password | mailed code (`signUp`) to an address nobody has, or whose account has no password | anonymous: mailed code (`signUp`) to the address added; with an address: mailed code (`firstPassword`); GitHub alone: GitHub sign-in of the last 10 minutes + mailed code (`signUp`) to the address added. Never once an authenticator was confirmed |
| Sign up / first password | mailed code (`signUp`) to an address nobody has, or whose account has no password | anonymous where the session lives: mailed code (`signUp`) to the address added; with an address: mailed code (`firstPassword`); GitHub alone: GitHub sign-in of the last 10 minutes + mailed code (`signUp`) to the address added. Never once an authenticator was confirmed |
| First authenticator | — | password (no code) + the new one's code |
| Another authenticator | — | password + code + the new one's code |
| Remove an authenticator | — | confirmed: password + code (never the last); unconfirmed: nothing |
| New recovery codes | — | password + code |
| New recovery phrase | — | password + code |
| New password | phrase's proof + code (keys kept) | password + code |
| Authenticator reset | password + (mailed code or phrase's proof); takes effect after 72 h, owner mailed, called off by a sign-in with a code | — |
| Account reset (new keys) | mailed code (`accountReset`) asks, a new one carries it out after 72 h, owner mailed, called off by any sign-in with the password | — |
| Authenticator reset | password + (mailed code or phrase's proof); takes effect after 72 h (pushed out while the owner is held out), owner mailed, called off by a sign-in with a code; lapses a week after taking effect unless carried out | password + code calls it off |
| Account reset (new keys) | mailed code (`accountReset`) asks, a new one carries it out after 72 h (pushed out alike), owner mailed, called off by any sign-in with the password; lapses a week after taking effect | password + code calls it off |
| Delete the account | — | password + code; where no second factor works, password + mailed code (`deletion`) |
Every reset carried out, and every new password, ends the account's sessions in every
service (a change keeps the session asking). A session ended for ten wrong proofs leaves
its owner a warning; dismissing it takes the session alone, and a new password clears it.
Every reset carried out, and every new password (a setup's replaced too), ends the
account's sessions in every service (a change keeps the session asking). A session ended
for ten wrong proofs leaves its owner a warning; a session made since dismisses it, and a
new password clears it. At GitHub's end, accounts that sign in with GitHub alone go.
## Consequences