docs(auth): proofs bound to the base URL and to what they approve (ADR 0017)

The full reading's part in ADR 0017 records the device's medium 1 as done
(the identity's purpose and digest, the base URL in place of the host, an
instance under a path taking no password sign-in), and "The rules as they
stand" says what "password" binds; the module's and halkyon_app's READMEs
say the same.

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 14:18:23 +02:00
commit 7bef70948d
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -210,13 +210,17 @@ Built on the three parts above, on the register (members forward every step thro
session. A setup never finished (never a confirmed authenticator) signs in with a code
mailed to the address it began with in its place (`request`, `code`), which calls off
a reset of the account waiting; any other account takes no code by mail there and
leaves it unused. SRP's identity I is `passwordIdentity`: the challenge's ID and the
host the device addressed (`host` on every proving call: in production it must be the
server's `apiServer.publicHost`, else `otherInstance`; in development any, as an
emulator reaches the machine as 10.0.2.2), so a hostile instance relaying a sign-in or
a phrase's proof here gets nothing taken; a member forwards its own host, and the
phrase's proof for a new password signs that password's digest
(`newPasswordDigest`). An address nobody has (or whose
leaves it unused. SRP's identity I is `passwordIdentity`: the challenge's ID, the
instance's base URL the device addressed (`instanceUrl` on every proving call, written
alike on both sides by `canonicalInstanceUrl`: in production it must be the server's
own public scheme, host and port at the root, `publicInstanceUrlOf`, else
`otherInstance`, so an instance served under a path takes no password sign-in; in
development any, as an emulator reaches the machine as 10.0.2.2), what the proof is for
and the digest of what it sets (`PasswordProofPurpose`; signing in and an
authenticator's reset each its own, with none), so a hostile instance relaying a
sign-in or a phrase's proof here gets nothing taken, nor one beside it on the same host;
a member forwards its own base URL, and the phrase's proof for a new password signs
that URL and that password's digest (`newPasswordDigest`). An address nobody has (or whose
account has no password) gets a salt and verifier from `signInDecoyKey` and the
address's hash, the same queries and arithmetic, and the same refusal: a wrong
password, an unknown address and a wrong, used or missing code are all
@ -238,7 +242,13 @@ Built on the three parts above, on the register (members forward every step thro
recovery codes, a new phrase, a new password, deleting) takes the password's SRP proof
for a challenge `password.beginChange` gave (30 begun an hour per session, holding
back no other) and then a TOTP or recovery code (`proveOwner`, `ownersCode`). A proof
given where none is taken is refused, so none passes unchecked. Each proof counts for
given where none is taken is refused, so none passes unchecked. The proof is bound to
what it approves: SRP's identity carries the purpose and the digest of what is set
(`newPasswordDigest` of the new password, `newPhraseDigest` of the phrase's wrapping
and proof key, `authenticatorDigest` of the authenticator confirmed or removed; none
for new recovery codes, calling resets off and deleting), which the register works out
from the call itself, so whoever stands between swaps nothing in and a proof made for
one change makes no other. Each proof counts for
its session alone (`owner_proof_tries.dart`, `asOwnerProofTry`, table
`session_code_failures`, kept where the session is, on a member too), before it is
checked; the tenth wrong in a row ends the session (`sessionEnded`), only a proof
@ -295,7 +305,7 @@ Built on the three parts above, on the register (members forward every step thro
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
to the base URL the device addressed and made for deleting (`requireOwnerToDelete`; a member passes the proof on
and the register checks it), counted for the session as every owner's proof is. The
code is a TOTP or recovery code wherever an authenticator is confirmed; where no second
factor works (a setup never finished, or the authenticators' reset taken effect;