docs(auth): what re-reading the fixes found, and what was done (ADR 0017)

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-20 23:41:41 +02:00
commit 70b434bb84
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -291,6 +291,53 @@ deletion):
more can still hold an owner's sign-ins out for an hour at a time, and three requests
void a code its owner is typing.
### Re-reading the fixes (20 Oct 2026)
A second independent reading, of the fixes above (`7071933..1383640`), found H1, M1, M2,
M5 (in the code), M6, M7, M8, deletion and the lows fixed, M3 and M4 incomplete, and
these; all but the ninth were fixed (the developer's choice):
1. **Codes sent at once all got checked** before the tenth refused began a wait. Every
attempt at a code for an account is now counted before it is checked, one at a time
per account (a transaction holding an advisory lock on its key), the tenth in a row
beginning the wait as it is counted: of codes sent at once at most ten are checked.
2. **The ceiling of proofs per address was looked at only as a sign-in began, and
counted only wrong passwords**: sign-ins begun ahead got more checked, and a right
password could be told apart by whether the next one began. Every proof of a password
or of the phrase is now recorded before it is checked, whatever its outcome, 30 an
hour per address, refused past it; the check at begin stays as an early refusal.
3. **A stolen session alone could keep its owner's codes waiting**, renewed without end,
holding them out of signing in and of changing the password, unmailed. Codes from a
signed-in session (removing an authenticator, new recovery codes, confirming one) now
count per session, where the session is (`session_code_failures`), each counted
before it is checked; the tenth wrong in a row ends that session (`sessionEnded`); a
try that checked no code is taken back. The account's waits count only codes after a
right password, and the per-account ten tries an hour, which a session could fill, go.
4. **Whoever guessed decided whether the owner was mailed**, as the address was optional.
Every call carrying a code after the password or the phrase proved (sign-in, the
phrase's reset, a change, a new phrase, deletion) takes the account's address and is
refused before any code is checked when it isn't theirs; every wait mails the owner.
The app sends the address typed, or for changes while signed in the one the device
signed in with (`addressUnknown` where it doesn't know it).
5. **Strangers' wrong passwords held a signed-in owner out of changing the password, a
new phrase and deleting.** A change's challenge has its own purpose (`change`),
counted per account (10 an hour), never toward the address; no sign-in takes it.
6. **Waits survived the resets**: both now delete the codes refused before.
7. **Codes by mail served anything.** A code is asked for a purpose (`signUp`,
`finishSetup`, `authenticatorReset`, `accountReset`, `deletion`; a nullable column of
`email_code_request`), only that purpose takes it, and its mail says what it would do
and to type it only in the app on that instance: a hostile instance relaying a code
asked for one thing gets nothing else from it, and the person reads what they would
hand over.
8. **An account whose setup never finished, or whose authenticators' reset took effect,
couldn't delete itself.** It does with the password and a code by mail asked for
deleting.
9. *(Left as it is)* codes for one address, ten an hour from every network, can still be
used up from four networks, holding its owner's codes out for an hour at a time.
10. **A member behind the register's clock refused the session after a reset as
unavailable**, which the app read as "no password sign-in here": it has its own
refusal now (`passwordChanged`), saying to sign in again with the new password.
## Consequences
- People keep a password, an authenticator app and a recovery phrase: more to keep than a