feat(registry): sessions, linked identities and the account page #249
Loading…
Reference in a new issue
No description provided.
Delete branch "loop/223-registry-sessions"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #223 (slice 1 of #16).
What
tools/registry/src/database.trb): new tablesidentities(provider + provider user id, unique, never an e-mail),sessions(SHA-256 of the id, account, created, expires, last seen) andcsrf_secrets(cascade-deleted with their session). OnlyCREATE TABLE IF NOT EXISTS, so existing databases migrate in place; the test builds a pre-sessions database by hand and checks that its account's token keeps working.Registry.startSession/sessionOf/endSession): the cookie__Host-sessioncarries 256 random bits and nothing else,Max-Age; Path=/; Secure; HttpOnly; SameSite=Lax. There is an absolute expiry (REGISTRY_SESSION_SECONDS, 12 h) and an idle expiry (REGISTRY_SESSION_IDLE_SECONDS, 30 min), both evaluated by the database clock. Sign-out deletes the row, and the browser is told to drop a cookie that names no live session. Dead sessions are pruned whenever a session starts or ends.Sec-Fetch-Siteiscross-siteorsame-siteis also 403.GET /account(tools/registry/src/account.trb): HTML showing the owner name, the linked identities with unlink forms, passkeys and second factors ("None yet."), the tokens with revoke forms, and sign-out.Content-Security-Policy: default-src 'none'; form-action 'self'; frame-ancestors 'none'; base-uri 'none',nosniff,no-referrer,no-store./account.BEGIN IMMEDIATEtransaction. A successful unlink also ends the account's other sessions, since one of them may have signed in through the unlinked identity.Registry.startSession(account)andRegistry.linkIdentityhave no route. Only the tests call them, in place of a provider sign-in.Path(/account) || PathPrefix(/account/)on the registry host to the registry (tools/deploy/compose.example.yml,nginx.confcomment).tools/registry/README.md(calls table, account page, configuration, decisions, "Not built yet", testing) and thedocs/design/RELEASE.mdAccounts row and "Not built yet".Tests
tools/registry/tests/account.test.trbhas 21 tests. Every call is made once working and once refused:Sec-Fetch-Site;They also cover escaping, CSP, cache and nosniff headers on 200, 303, 401 and 403, cookie attributes, digest-only storage, the cascade, pruning, other sessions ending on unlink, and the migration of an old database.
Review
An architect review of the diff ran before this PR (security: sessions, CSRF, cookies, CSP, SQL quoting, unlink atomicity). It found nothing blocking. Its hardening points are applied here:
Sec-Fetch-Site;Left for slice 2 (sign-in)
provider,subject) rather than from an account name.startSession(account)stays the test seam.startSessionorlinkIdentity.randomQueryIdentifier(std/network). A dedicated random-bytes function in std would make that contract explicit.CI red on
eaee9f7: pdisc / pdisc -tools/registry/README.md: stale: body changed since the last stamp. The README body was edited without restamping. Runpython3 tools/pdisc/scripts/pdisc.py refresh tools/registry/README.md(and regenerate INDEX if it changes), then push. Reproduced locally on this head;mainpasses. All other checks are green. The review follows once CI is green on the new head.eaee9f794746fd86860b46fd86860bc95f86beb7Approved. CI green on
c95f86b(incl. pdisc and tools/registry).account.test.trb, each call working and refused (no/dead/expired session, wrong/foreign secret, Sec-Fetch-Site, foreign identity/token, last identity, migration of a pre-sessions DB).__Host-cookie with Secure/HttpOnly/SameSite=Lax/Path=/, strict CSP, nosniff, no-store, every interpolation escaped, SQL via sqlText/sqlInteger, no route starts a session or links an identity.CREATE TABLE IF NOT EXISTSonly, migration tested./account.Non-blocking: the slice-2 follow-ups listed in the PR body (verified-identity entry point, automated check that only the sign-in module calls
startSession/linkIdentity, dedicated random-bytes function in std) should get issues.