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:
parent
91a839e43a
commit
70b434bb84
1 changed files with 91 additions and 24 deletions
|
|
@ -187,7 +187,12 @@ Built on the three parts above, on the register (members forward every step thro
|
|||
leaves the account without one (a new password comes through the recovery phrase or
|
||||
the account's reset alone).
|
||||
- **Codes by mail** (`emailCode.sendCode`, without a session) only prove an address: for
|
||||
a password's setup, a sign-up, a setup never finished and the resets. Since step 3
|
||||
a password's setup, a sign-up, a setup never finished, the resets and deleting an
|
||||
account without a working second factor. Each is asked for a purpose
|
||||
(`EmailCodePurpose`: `signUp`, `finishSetup`, `authenticatorReset`, `accountReset`,
|
||||
`deletion`; `email_code_request.purpose`), only that purpose takes it (any other is
|
||||
refused as no longer valid and leaves it unused), and its mail says what it would do
|
||||
and to type it only in the app on that instance. Since step 3
|
||||
nothing turns a code into a session (`emailCode.verifyCode`, `emailSignIn.signIn` and
|
||||
the switch token went with build 22, `minimumAppBuild` 22 in Learn).
|
||||
- **Signing up** (`passwordSignIn.signUp(request, code, setup)`, without a session): an
|
||||
|
|
@ -211,25 +216,36 @@ Built on the three parts above, on the register (members forward every step thro
|
|||
password, an unknown address and a wrong, used or missing code are all
|
||||
`wrongCredentials`; `setupIncomplete` and `notDue` come only after a right password.
|
||||
At most 10 sign-ins begun per address and network an hour (5 proofs of the phrase),
|
||||
30 per network, and, from every network together, 30 failed proofs per address
|
||||
(wrong passwords, phrases' signatures by another key; an address nobody has alike),
|
||||
past which no sign-in or proof begins for it anywhere until the hour has passed: so
|
||||
whoever knows an address and fills its count from one network holds no other back,
|
||||
and guessing from many makes at most 30 guesses an hour.
|
||||
30 per network, and, from every network together, 30 proofs checked per address
|
||||
(`proofsPerAddress`: every password and phrase proof, recorded before it is checked
|
||||
whatever its outcome, so a right one counts as a wrong one and challenges begun ahead
|
||||
get no more checked; an address nobody has alike), past which no proof is checked and
|
||||
nothing begins for it anywhere until the hour has passed: so whoever knows an address
|
||||
and fills its count from one network holds no other back, and guessing from many
|
||||
makes at most 30 guesses an hour. A change's proofs (a `change` challenge) count per
|
||||
account instead, 10 begun an hour, never toward the address, and no sign-in takes one.
|
||||
Every call carrying a code after the password or phrase proved (sign-in, the phrase's
|
||||
reset, a change, a new phrase, deletion) takes the account's `address` and `language`,
|
||||
refused (`wrongCredentials`) before any code is checked when it isn't theirs.
|
||||
- **Authenticators:** a secret made on the register, shown once as an `otpauth://` URI
|
||||
(issuer: the account domain); confirmed by a code; while the account has a confirmed
|
||||
one, another counts only with a code from that one too (`wrongExistingCode`), so a
|
||||
session alone adds no second factor; the first confirmed brings ten recovery codes; at most 10 per account; the last confirmed one stays; removing a
|
||||
confirmed one, or new recovery codes, takes a code (10 tries an hour per account).
|
||||
Each step's code is accepted once, by a conditional update of `lastStep`.
|
||||
- **Codes refused in a row** (`second_factor_waits.dart`, table
|
||||
`second_factor_failures`): every code an account's second factor refuses after a
|
||||
right password (sign-in, a phrase's reset, a change) or from a signed-in device
|
||||
(removing an authenticator, new recovery codes, the code from one confirmed before)
|
||||
counts, per account, from every network; after every tenth in a row no code is taken
|
||||
(`codesWait`, with when), an hour, then twice as long each time, a day at the most;
|
||||
the first wait mails the address where a device sent it (sign-in and the phrase's
|
||||
reset: the instance keeps none); a code accepted clears it.
|
||||
session alone adds no second factor; the first confirmed brings ten recovery codes; at
|
||||
most 10 per account; the last confirmed one stays; removing a confirmed one, or new
|
||||
recovery codes, takes a code. Each step's code is accepted once, by a conditional
|
||||
update of `lastStep`. A signed-in session's codes here count for that session alone
|
||||
(`session_code_tries.dart`, table `session_code_failures`, kept where the session is,
|
||||
on a member too), each counted before it is checked; the tenth wrong in a row ends the
|
||||
session (`sessionEnded`), a code accepted clears the count, a try that checked no code
|
||||
is taken back; never toward the account's waits, so a stolen session holds nobody out.
|
||||
- **Codes tried in a row** (`second_factor_waits.dart`, table
|
||||
`second_factor_failures`): every code tried against an account's second factor after
|
||||
a right password or phrase (sign-in, the phrase's reset, a change, a new phrase,
|
||||
deletion) counts, per account, from every network, before it is checked, one at a time
|
||||
per account (an advisory lock), so codes sent at once get no more checked; every tenth
|
||||
in a row begins a wait as it is counted (`codesWait`, with when), an hour, then twice
|
||||
as long each time, a day at the most, and every wait mails the account's address
|
||||
(which those calls take); a code accepted, and either reset carried out, clears it.
|
||||
- **Resets:** a lost authenticator: SRP's proof plus a mailed code or the recovery
|
||||
phrase's signature asks for it; it takes effect 72 hours later, a mail tells the
|
||||
address at once, and signing in with a code calls it off; then the password alone
|
||||
|
|
@ -246,10 +262,12 @@ Built on the three parts above, on the register (members forward every step thro
|
|||
proof and code (a phrase of one's own would otherwise set a new password through the
|
||||
phrase's reset). A session alone changes neither, nor a session and a code.
|
||||
- **Deleting an account with a password** (`account.delete(proof:)`, a `DeletionProof`)
|
||||
takes the same: the password's proof for a `beginChange` challenge and a code, bound
|
||||
to the host the device addressed (`requireOwnerToDelete`; a member passes the proof
|
||||
on and the register checks it). An account without a password deletes with its
|
||||
session, as before.
|
||||
takes the same: the password's proof for a `beginChange` challenge, the account's
|
||||
address and a code, bound to the host the device addressed (`requireOwnerToDelete`; a
|
||||
member passes the proof on and the register checks it). Where no second factor works
|
||||
(a setup never finished, or the authenticators' reset taken effect) the code is one by
|
||||
mail asked for deleting (`request`, `code`). An account without a password deletes
|
||||
with its session, as before.
|
||||
- **Sessions end everywhere:** a change of password ends every other session of the
|
||||
account; a reset of the password or the account ends all. The register ends its own
|
||||
and keeps a `sessions_ended_notice` per member (told at once and hourly until it
|
||||
|
|
@ -257,8 +275,10 @@ Built on the three parts above, on the register (members forward every step thro
|
|||
session made since stays, and the session that asked for a change. A member answering
|
||||
one (`/halkyon-base/member/sessions-ended`) ends its sessions made before the moment;
|
||||
a member signing someone in after a reset ends its own first and makes the new session
|
||||
only once its clock is past the moment (`issueSessionAfter`). A refusal for a reset not
|
||||
due says when it takes effect (`takesEffectAt`).
|
||||
only once its clock is past the moment (`issueSessionAfter`), refusing with
|
||||
`passwordChanged` (sign in again with the new password) when its clock is more than
|
||||
`maxClockSkew` behind. A refusal for a reset not due says when it takes effect
|
||||
(`takesEffectAt`).
|
||||
- **Older ways:** an account that signs in with a password (it had a confirmed
|
||||
authenticator, which a reset may have taken since: `passwordOnly` in its ways in) no
|
||||
longer signs in with GitHub (`usePassword`); where passwords are offered, no new GitHub
|
||||
|
|
|
|||
Loading…
Reference in a new issue