feat(base): SimpleLogin accounts' keys without SRP, signing up in one call (ADR 0020, step 3)

An account that signs in with SimpleLogin keeps its keys under a password
that signs nobody in (D19): `account_password` keeps the password's own
proof key (Ed25519, `passwordProofKey`) in place of SRP's verifier, which
becomes nullable. The migration adds the column and drops the NOT NULL,
nothing else.

- `simpleLogin.signUp(attempt, handle, setup)`: someone new whose identity
  waits under the attempt gets, in one transaction, an account
  (identified), the link, a profile with the handle and the keys; then a
  session (D18). Refused, the identity still waiting, for a setup out of
  range and a handle refused as saving a profile refuses it.
- `simpleLogin.checkHandle(attempt, handle)`: whether someone new could
  take a handle, at most 30 an hour per attempt and 60 per network.
- `simpleLogin.complete` says, for an account, whether it has its keys and
  its handle, and keeps the sign-in ten minutes under its attempt, the
  account as its caller: D20's confirmation for the changes to come.
- `keys.begin`, `keys.unlock`: the keys for a signed-in device once the
  password is proven with its proof key, for a nonce, bound to the base
  URL the device addressed; wrong proofs counted per session as ADR
  0017's.
- An authenticator is enrolled only for a password that signs in.
- Members ask the register for all of it; `RemoteRegister` and the
  instance API carry the four new operations.

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-23 21:49:44 +02:00
commit 090d901e92
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -19,7 +19,8 @@ Endpoints, under the nickname `base` in a host's client (`client.modules.base.em
| `passwordSignIn` | `begin`, `complete`: SRP-6a's two steps with a code from an authenticator (or a recovery code; a setup never finished, a code by mail); a session. `signUp`: an account at the address a mailed code proves, with a password; a session | without a session |
| `authenticator` | `enrol`, `confirm` (the first brings ten recovery codes; another takes a code from one confirmed before), `list`, `remove`, `newRecoveryCodes` | signed in |
| `accountRecovery` | `resetAuthenticator`, `beginKeyProof`, `resetPassword` (with the recovery phrase), `startAccountReset`, `completeAccountReset` | without a session |
| `simpleLogin` | `begin(platform)`: an attempt and SimpleLogin's address to open, at most 30 an hour per network; `complete(attempt, code)`: signed out, a session for the account with that SimpleLogin ID, or for someone new the handle suggested from their name (their account comes with step 3); signed in, SimpleLogin added to the caller's account, who becomes `identified` | anyone |
| `simpleLogin` | `begin(platform)`: an attempt and SimpleLogin's address to open, at most 30 an hour per network; `complete(attempt, code)`: signed out, a session for the account with that SimpleLogin ID, or for someone new the handle suggested from their name; signed in, SimpleLogin added to the caller's account, who becomes `identified`; for an account, whether it has its keys and its handle, the attempt standing ten minutes for the sign-in; `checkHandle(attempt, handle)`: whether someone new could take a handle, at most 30 an hour per attempt and 60 per network; `signUp(attempt, handle, setup)`, without a session: someone new's account, `identified`, with the handle and the keys the device made; a session | anyone |
| `keys` | `begin`, `unlock`: the caller's keys for their device, once the password is proven with its own proof key (an account that signs in with SimpleLogin, Halkyon Learn's ADR 0020, D19); the tenth wrong proof in a row ends the session, as ADR 0017's do | signed in |
| `gitHubUpgrade` | `upgrade`: links a GitHub account to the caller, who becomes `identified`; refused where passwords or SimpleLogin are offered (deprecated) | signed in |
| `gitHubSignIn` | `signIn`: a session for whoever has that GitHub account, unless they sign in with a password; none once `GITHUB_SIGN_IN_ENDS` has come | without a session |
| `emailCode` | `sendCode`: a code by mail, which proves the address and signs nobody in | anyone |
@ -53,7 +54,13 @@ Endpoints, under the nickname `base` in a host's client (`client.modules.base.em
(`halkyon_oidc_identity`, the provider `simplelogin`): one per account, each an
account's. Someone new's identity waits 30 minutes under the attempt's ID, with a
handle suggested from the name SimpleLogin shared (`handleFromName`: `anna_mueller`,
a number added where taken), which nothing keeps.
a number added where taken), which nothing keeps. Signing up (`signUp`, D18) takes
the identity and makes the account in one transaction: `identified`, linked, a
profile with the handle (refused as saving a profile refuses it) and the keys, the
password's own proof key (Ed25519) in place of SRP's verifier (D19). The password
opens the keys and signs nobody in, so such an account has no authenticator (`enrol`
refuses it `noPassword`). A sign-in into an account keeps its attempt ten minutes
more, the account its caller: it confirms a change the device asks for next (D20).
- SimpleLogin's forwarding page (Halkyon Learn's ADR 0020; `addSimpleLoginCallbackRoute`,
on the register, on halkyon_oidc's `addOidcCallbackForwarding`, ADR 0023):
`/simplelogin-callback`, the one address the instance's SimpleLogin
@ -198,7 +205,8 @@ Built on the three parts above, on the register (members forward every step thro
- **Tables** (`serverOnly`, each row deleted with its person by `deletePerson`):
`account_password` (one per account: Argon2id's salt and costs, SRP's verifier padded
to N, the public keys, the private keys wrapped under the password and under the
to N, or for an account that signs in with SimpleLogin the password's proof key in its
place, the public keys, the private keys wrapped under the password and under the
recovery phrase), `totp_authenticator` (the secret sealed with XChaCha20-Poly1305 under
a key derived from `totpKey`, bound to the row's account and ID; `confirmedAt`;
`lastStep`), `recovery_code` (keyed hashes), `sign_in_challenge` (a step begun: SRP's b