registry: sign-in through GitHub, GitLab, Codeberg or a passkey, and a second factor #16

Closed
opened 2026-10-05 21:07:10 +00:00 by orion · 13 comments
Collaborator

Goal

Let a person sign in to the registry's write service through GitHub, GitLab, Codeberg or a passkey, and require a
second factor of every account that can publish. Today accounts exist only when the operator creates them with the
administrator token; there is no sign-in and no second factor.

Specification (pinned)

Status of refine

Not ready. The records fix what (providers, passkey, TOTP-or-passkey for publishers) but not how: there is no web
front, std/signature has Ed25519 only (WebAuthn's common algorithm ES256 needs P-256), the e-mail + passkey sign-up
needs outgoing mail, and the rollout against existing operator-created accounts and tokens is unspecified. Owner
decision filed in the comments below. The split below is the plan once it is answered.

Planned split (dependency-linked issues, label loop, created after the owner decision)

  1. Foundation: sessions, linked identities, account page, schema (SQLite migration). No dependency.
  2. OAuth 2.0 / OIDC sign-in for GitHub, GitLab, Codeberg (authorization code with PKCE and state), tests against recorded responses. Depends on 1.
  3. P-256 ECDSA verification in std/signature (new public std surface, test vectors). No dependency.
  4. Passkeys (WebAuthn) registration and assertion, tests against recorded authenticator data. Depends on 1 and 3.
  5. TOTP and recovery codes. Depends on 1.
  6. Enforcement: an account that can publish needs a second factor before it can mint or use publishing tokens. Depends on 4 and 5.

Each is one PR; every one is security-relevant (implementer opus/high, architect review of the diff).

Seed impact

Expected none: all pieces are TorbScript on existing std (no syntax change, no native, no driver flag, no looked-up
std name). To be re-checked per slice (TOTP needs HMAC-SHA1; if std/digest lacks SHA-1 it is a std addition, still
no native unless a decision below says otherwise).

Acceptance criteria (draft, final wording per slice after the decision)

  • A person signs in through each of GitHub, GitLab and Codeberg and gets an account whose owner name they choose; identity is keyed by provider and the provider's user id, never by e-mail address.
  • A person registers and signs in with a passkey (WebAuthn: challenge, origin and RP-ID checks, signature counter, user verification flag).
  • A second factor (TOTP with recovery codes, or a passkey) can be enrolled, and an account that can publish cannot mint or use a publishing token without one.
  • Sessions are server-side, cookies HttpOnly; Secure; SameSite=Lax, state-changing calls carry a CSRF token; secrets (TOTP seeds) are stored encrypted.
  • Tests run against recorded provider responses and recorded authenticator data over loopback (no network, no credentials), every call once as it works and once as it is refused; torb test tools/registry/tests green natively and in the VM.
  • tools/registry/README.md and RELEASE.md "Not built yet" updated; skills/ regenerated if docs under docs/ change.

Dependencies

None known for the first slices; see the split above. Registering OAuth applications at the three providers is an
owner action needed at deployment only, not for the tests.

Moved from the planning lists of the docs; the design record stays the specification.

## Goal Let a person sign in to the registry's write service through GitHub, GitLab, Codeberg or a passkey, and require a second factor of every account that can publish. Today accounts exist only when the operator creates them with the administrator token; there is no sign-in and no second factor. ## Specification (pinned) - [RELEASE.md 7.1 Owners](https://git.torb.dev/torbscript/language/src/commit/393ef2608e4a60b8ff76d8b6b408951b822d887d/docs/design/RELEASE.md#L866): a person's owner is the account, created at sign-up (first come); several providers from day one, so an owner is not one provider's account. - [RELEASE.md 7.6 Security](https://git.torb.dev/torbscript/language/src/commit/393ef2608e4a60b8ff76d8b6b408951b822d887d/docs/design/RELEASE.md#L1029): "a mandatory second factor (TOTP or a passkey) for every account that can publish". - [RELEASE.md, the write service as built](https://git.torb.dev/torbscript/language/src/commit/393ef2608e4a60b8ff76d8b6b408951b822d887d/docs/design/RELEASE.md#L1216): sign-in is "the web front's, which does not exist yet"; "Not built yet: sign-in and a second factor". - [ROADMAP.md, the package manager](https://git.torb.dev/torbscript/language/src/commit/393ef2608e4a60b8ff76d8b6b408951b822d887d/docs/ROADMAP.md#L96) and [tools/registry/README.md](https://git.torb.dev/torbscript/language/src/branch/main/tools/registry/README.md) ("Not built yet"). ## Status of refine Not ready. The records fix *what* (providers, passkey, TOTP-or-passkey for publishers) but not *how*: there is no web front, `std/signature` has Ed25519 only (WebAuthn's common algorithm ES256 needs P-256), the e-mail + passkey sign-up needs outgoing mail, and the rollout against existing operator-created accounts and tokens is unspecified. Owner decision filed in the comments below. The split below is the plan once it is answered. ## Planned split (dependency-linked issues, label `loop`, created after the owner decision) 1. **Foundation:** sessions, linked identities, account page, schema (SQLite migration). No dependency. 2. **OAuth 2.0 / OIDC sign-in** for GitHub, GitLab, Codeberg (authorization code with PKCE and `state`), tests against recorded responses. Depends on 1. 3. **P-256 ECDSA verification in `std/signature`** (new public std surface, test vectors). No dependency. 4. **Passkeys (WebAuthn)** registration and assertion, tests against recorded authenticator data. Depends on 1 and 3. 5. **TOTP and recovery codes.** Depends on 1. 6. **Enforcement:** an account that can publish needs a second factor before it can mint or use publishing tokens. Depends on 4 and 5. Each is one PR; every one is security-relevant (implementer opus/high, architect review of the diff). ## Seed impact Expected none: all pieces are TorbScript on existing `std` (no syntax change, no native, no driver flag, no looked-up std name). To be re-checked per slice (TOTP needs HMAC-SHA1; if `std/digest` lacks SHA-1 it is a std addition, still no native unless a decision below says otherwise). ## Acceptance criteria (draft, final wording per slice after the decision) - [ ] A person signs in through each of GitHub, GitLab and Codeberg and gets an account whose owner name they choose; identity is keyed by provider and the provider's user id, never by e-mail address. - [ ] A person registers and signs in with a passkey (WebAuthn: challenge, origin and RP-ID checks, signature counter, user verification flag). - [ ] A second factor (TOTP with recovery codes, or a passkey) can be enrolled, and an account that can publish cannot mint or use a publishing token without one. - [ ] Sessions are server-side, cookies `HttpOnly; Secure; SameSite=Lax`, state-changing calls carry a CSRF token; secrets (TOTP seeds) are stored encrypted. - [ ] Tests run against recorded provider responses and recorded authenticator data over loopback (no network, no credentials), every call once as it works and once as it is refused; `torb test tools/registry/tests` green natively and in the VM. - [ ] `tools/registry/README.md` and RELEASE.md "Not built yet" updated; `skills/` regenerated if docs under `docs/` change. ## Dependencies None known for the first slices; see the split above. Registering OAuth applications at the three providers is an owner action needed at deployment only, not for the tests. _Moved from the planning lists of the docs; the design record stays the specification._
Author
Collaborator

[loop:torbscript] intake: in scope (author orion, label loop). Registry write service (tools/registry), design in RELEASE.md section 7.11, no collision with CONCEPT "Open Questions". Security-relevant (opus/high worker, architect review); needs OAuth app credentials from the owner, which the loop cannot obtain, so repo-side work only with recorded responses. Likely split per sign-in method in refine. Release freeze for 0.1.0 (2026-10-06) holds new PRs, next: refine (queued)

[loop:torbscript] intake: in scope (author orion, label loop). Registry write service (`tools/registry`), design in RELEASE.md section 7.11, no collision with CONCEPT "Open Questions". Security-relevant (opus/high worker, architect review); needs OAuth app credentials from the owner, which the loop cannot obtain, so repo-side work only with recorded responses. Likely split per sign-in method in refine. Release freeze for 0.1.0 (2026-10-06) holds new PRs, next: refine (queued)
Author
Collaborator

[loop:torbscript] claim refine by session:loop-torbscript-20261008 until 2026-10-08T14:40:00Z

[loop:torbscript] claim refine by session:loop-torbscript-20261008 until 2026-10-08T14:40:00Z
Author
Collaborator

[loop:torbscript] owner decision: how registry sign-in, passkeys and the second factor are built (issue #16). RELEASE.md 7.1 and 7.6 say what (GitHub, GitLab, Codeberg or e-mail + passkey; TOTP or passkey for every publisher); the points below are not settled by any design record and some reach beyond the repo (a public sign-in page, OAuth applications at three providers, outgoing mail). Please answer per number; the recommendation is the first option.

1. Where does the user sign in? There is no web front, and WebAuthn only works from a browser page.

  • (a) A small server-rendered account page served by the registry itself (a few HTML forms plus one small script for WebAuthn), under the registry's own host; no separate site or framework. torb login becomes a later issue on top.
  • (b) A separate front end on the site, the registry stays API-only.
  • (c) API-only; passkeys deferred.

2. Which passkey algorithms? std/signature has Ed25519 only, the registry has RS256 verification; the common authenticator algorithm is ES256 (P-256).

  • (a) ES256, EdDSA and RS256, with a new pure-TorbScript P-256 ECDSA verifier in std/signature (new public std surface, no native, no seed change).
  • (b) EdDSA and RS256 only; some security keys and platform authenticators will not work.

3. E-mail + passkey sign-up. 7.1 names it, but verifying an address needs outgoing mail for the domain, which RELEASE.md section 12 point 7 lists as the owner's.

  • (a) First cut without e-mail: sign up through one of the three providers, then add passkeys and TOTP to that account; e-mail sign-up follows when mail exists.
  • (b) Build e-mail verification now (you provide SMTP credentials at deployment).

4. Second factor policy and rollout. 7.6 says TOTP or a passkey, mandatory for every account that can publish.

  • (a) A passkey counts as the second factor on its own (it is possession plus user verification); a provider sign-in needs TOTP or a passkey on top; minting or using publishing tokens requires an enrolled factor; tokens that already exist keep working until their expiry (at most 365 days).
  • (b) As (a), but existing operator-created accounts and their tokens are cut over at deployment (tokens revoked until the factor is enrolled).
  • (c) A passkey never counts; TOTP is always required for publishers.
    TOTP seeds are stored encrypted with a key from the environment, plus 10 single-use recovery codes. This is a behaviour change for anyone publishing today, hence your call.

5. Identity and linking. Recommended: an identity is the provider plus the provider's user id (never the e-mail address, which can be unverified or change); one account may link several identities and passkeys; the owner name is chosen at first sign-up, first come, under the existing similarity rules; removing the last sign-in method is refused.

  • (a) As recommended.
  • (b) Something else (say what).

6. OAuth applications. The repo work uses recorded responses and needs no credentials. At deployment you register an OAuth application at each of GitHub, GitLab and Codeberg (callback under the registry's host) and put client id and secret into the registry's environment; I would not do this myself.

  • (a) Proceed on that basis.
  • (b) Hold the OAuth slice until the applications exist.

Planned split once answered (all loop, one PR each): foundation (sessions, identities, schema) -> OAuth for three providers; P-256 in std/signature; passkeys (needs foundation and P-256); TOTP and recovery codes (needs foundation); enforcement (needs passkeys and TOTP). Plan and draft criteria are in the issue body. Seed impact expected none.

Answer by commenting [owner-answer] q=<id> with the choices (e.g. 1a 2a 3a 4a 5a 6a), or in the TorbScript group.

[loop:torbscript] owner decision: how registry sign-in, passkeys and the second factor are built (issue #16). RELEASE.md 7.1 and 7.6 say *what* (GitHub, GitLab, Codeberg or e-mail + passkey; TOTP or passkey for every publisher); the points below are not settled by any design record and some reach beyond the repo (a public sign-in page, OAuth applications at three providers, outgoing mail). Please answer per number; the recommendation is the first option. **1. Where does the user sign in?** There is no web front, and WebAuthn only works from a browser page. - (a) A small server-rendered account page served by the registry itself (a few HTML forms plus one small script for WebAuthn), under the registry's own host; no separate site or framework. `torb login` becomes a later issue on top. - (b) A separate front end on the site, the registry stays API-only. - (c) API-only; passkeys deferred. **2. Which passkey algorithms?** `std/signature` has Ed25519 only, the registry has RS256 verification; the common authenticator algorithm is ES256 (P-256). - (a) ES256, EdDSA and RS256, with a new pure-TorbScript P-256 ECDSA verifier in `std/signature` (new public std surface, no native, no seed change). - (b) EdDSA and RS256 only; some security keys and platform authenticators will not work. **3. E-mail + passkey sign-up.** 7.1 names it, but verifying an address needs outgoing mail for the domain, which RELEASE.md section 12 point 7 lists as the owner's. - (a) First cut without e-mail: sign up through one of the three providers, then add passkeys and TOTP to that account; e-mail sign-up follows when mail exists. - (b) Build e-mail verification now (you provide SMTP credentials at deployment). **4. Second factor policy and rollout.** 7.6 says TOTP or a passkey, mandatory for every account that can publish. - (a) A passkey counts as the second factor on its own (it is possession plus user verification); a provider sign-in needs TOTP or a passkey on top; minting or using publishing tokens requires an enrolled factor; tokens that already exist keep working until their expiry (at most 365 days). - (b) As (a), but existing operator-created accounts and their tokens are cut over at deployment (tokens revoked until the factor is enrolled). - (c) A passkey never counts; TOTP is always required for publishers. TOTP seeds are stored encrypted with a key from the environment, plus 10 single-use recovery codes. This is a behaviour change for anyone publishing today, hence your call. **5. Identity and linking.** Recommended: an identity is the provider plus the provider's user id (never the e-mail address, which can be unverified or change); one account may link several identities and passkeys; the owner name is chosen at first sign-up, first come, under the existing similarity rules; removing the last sign-in method is refused. - (a) As recommended. - (b) Something else (say what). **6. OAuth applications.** The repo work uses recorded responses and needs no credentials. At deployment you register an OAuth application at each of GitHub, GitLab and Codeberg (callback under the registry's host) and put client id and secret into the registry's environment; I would not do this myself. - (a) Proceed on that basis. - (b) Hold the OAuth slice until the applications exist. Planned split once answered (all `loop`, one PR each): foundation (sessions, identities, schema) -> OAuth for three providers; P-256 in `std/signature`; passkeys (needs foundation and P-256); TOTP and recovery codes (needs foundation); enforcement (needs passkeys and TOTP). Plan and draft criteria are in the issue body. Seed impact expected none. Answer by commenting `[owner-answer] q=<id>` with the choices (e.g. `1a 2a 3a 4a 5a 6a`), or in the TorbScript group.
Author
Collaborator

[loop:torbscript] refine: rewrote the body (goal, pinned records, six-issue split plan, seed impact expected none, draft acceptance criteria). Not ready: sign-in front, passkey algorithms (P-256 is missing in std/signature), e-mail sign-up (needs mail), the second-factor rollout and OAuth application setup are not settled by a design record, so the owner decision above (#16 (comment)) is open. The split issues are created once it is answered. The tasks tool cannot reach this repo, so no question_ask block exists; the answer is Torben's comment here or a relay from the TorbScript group. Worker usage: sonnet, roughly 90k tokens. next: waiting (owner decision)

[loop:torbscript] refine: rewrote the body (goal, pinned records, six-issue split plan, seed impact expected none, draft acceptance criteria). Not `ready`: sign-in front, passkey algorithms (P-256 is missing in `std/signature`), e-mail sign-up (needs mail), the second-factor rollout and OAuth application setup are not settled by a design record, so the owner decision above (https://git.torb.dev/torbscript/language/issues/16#issuecomment-1078) is open. The split issues are created once it is answered. The tasks tool cannot reach this repo, so no `question_ask` block exists; the answer is Torben's comment here or a relay from the TorbScript group. Worker usage: sonnet, roughly 90k tokens. next: waiting (owner decision)
Author
Collaborator

Question q1 for torben (decision, blocking): How are registry sign-in, passkeys and the second factor built?

v: 1
id: q1
to: torben
kind: decision
blocking: true
question: "How are registry sign-in, passkeys and the second factor built? Six points with options in the owner decision comment above (sign-in surface, passkey algorithms, e-mail sign-up, second-factor policy and rollout, identity linking, OAuth applications)."
options:
  - id: a
    label: "Recommended: 1a 2a 3a 4a 5a 6a"
    detail: Account page served by the registry; ES256+EdDSA+RS256 with a P-256 verifier in std/signature; no e-mail sign-up yet; passkey counts as second factor, existing tokens run to expiry; identity = provider + user id; Torben registers the OAuth apps at deployment
  - id: b
    label: "Other combination"
    detail: Answer per number, e.g. 1a 2b 3a 4b 5a 6b
recommendation: a
asked_by: orion
**Question q1** for torben (decision, blocking): How are registry sign-in, passkeys and the second factor built? ```hos-question v: 1 id: q1 to: torben kind: decision blocking: true question: "How are registry sign-in, passkeys and the second factor built? Six points with options in the owner decision comment above (sign-in surface, passkey algorithms, e-mail sign-up, second-factor policy and rollout, identity linking, OAuth applications)." options: - id: a label: "Recommended: 1a 2a 3a 4a 5a 6a" detail: Account page served by the registry; ES256+EdDSA+RS256 with a P-256 verifier in std/signature; no e-mail sign-up yet; passkey counts as second factor, existing tokens run to expiry; identity = provider + user id; Torben registers the OAuth apps at deployment - id: b label: "Other combination" detail: Answer per number, e.g. 1a 2b 3a 4b 5a 6b recommendation: a asked_by: orion ```
Author
Collaborator

[loop:torbscript] owner decision recorded (torben via WhatsApp, 2026-10-08 16:35 Europe/Berlin): "Ja, genau" (recommendation 1a 2a 3a 4a 5a 6a).

Resolution: account page served by the registry; ES256 + EdDSA + RS256 with a P-256 verifier in std/signature; no e-mail sign-up yet; a passkey counts as the second factor, existing tokens run to expiry; identity = provider + user id; Torben registers the OAuth apps at deployment. Labelled ready.

[loop:torbscript] owner decision recorded (torben via WhatsApp, 2026-10-08 16:35 Europe/Berlin): "Ja, genau" (recommendation 1a 2a 3a 4a 5a 6a). Resolution: account page served by the registry; ES256 + EdDSA + RS256 with a P-256 verifier in std/signature; no e-mail sign-up yet; a passkey counts as the second factor, existing tokens run to expiry; identity = provider + user id; Torben registers the OAuth apps at deployment. Labelled `ready`.
Author
Collaborator

[question-closed] q=q1 reason=answered-elsewhere

[question-closed] q=q1 reason=answered-elsewhere
Author
Collaborator

[loop:torbscript] claim implement by session:loop-torbscript-20261008 until 2026-10-09T03:22Z

[loop:torbscript] claim implement by session:loop-torbscript-20261008 until 2026-10-09T03:22Z
Author
Collaborator

[loop:torbscript] decided by the loop: split per the planned split, after the recorded owner decision (1a 2a 3a 4a 5a 6a). Six issues, label loop, dependencies linked:

  1. #223 registry: sessions, linked identities and the account page - no dependency - ready
  2. #224 registry: sign-in through GitHub, GitLab and Codeberg (OAuth 2.0 with PKCE) - depends on #223
  3. #225 signature: P-256 ECDSA verification, ES256 - no dependency - ready
  4. #226 registry: passkeys (WebAuthn) - depends on #223 and #225
  5. #227 registry: TOTP and recovery codes - depends on #223; refine still decides the cipher for the stored TOTP seed (std has no authenticated cipher)
  6. #228 registry: a second factor before publishing tokens - depends on #226 and #227

ready only where no dependency is open; the others get it in refine once their dependencies have landed. #16 stays open as the parent until all six are closed. Implementation of #225 starts now.

[loop:torbscript] decided by the loop: split per the planned split, after the recorded owner decision (1a 2a 3a 4a 5a 6a). Six issues, label `loop`, dependencies linked: 1. #223 registry: sessions, linked identities and the account page - no dependency - `ready` 2. #224 registry: sign-in through GitHub, GitLab and Codeberg (OAuth 2.0 with PKCE) - depends on #223 3. #225 signature: P-256 ECDSA verification, ES256 - no dependency - `ready` 4. #226 registry: passkeys (WebAuthn) - depends on #223 and #225 5. #227 registry: TOTP and recovery codes - depends on #223; refine still decides the cipher for the stored TOTP seed (`std` has no authenticated cipher) 6. #228 registry: a second factor before publishing tokens - depends on #226 and #227 `ready` only where no dependency is open; the others get it in refine once their dependencies have landed. #16 stays open as the parent until all six are closed. Implementation of #225 starts now.
Author
Collaborator

[loop:torbscript] implement: split into #223, #224, #225, #226, #227, #228, slice 3 PR #229, CI pending (head 8c7913ea: commit messages, change scope, pdisc, VS Code and bootstrap linux-x64 green; tier A, tier B, gates and the remaining lanes still queued for a runner at the worker's timeout), next: waiting (forge CI of #229 to finish, then review by the merge gate argus)

  • The previous head ran tier A fully: std tests native and VM (including all 746 Wycheproof P-256 cases), format --check, docs skill --check and doc --check green; only docs check/docs index --check were red (summary over 240 characters, index not regenerated), fixed in 8c7913ea and checked locally.
  • Seed impact: none (no native, compiler or seed change).
  • Security review before the PR: no blocking findings; two doc points folded in (bounds of the Montgomery numbers, the WebAuthn message and signature forms).
  • Worker usage: about 2 h 45 min wall time, about 0.5 M tokens including one review subagent (41 k).
[loop:torbscript] implement: split into https://git.torb.dev/torbscript/language/issues/223, https://git.torb.dev/torbscript/language/issues/224, https://git.torb.dev/torbscript/language/issues/225, https://git.torb.dev/torbscript/language/issues/226, https://git.torb.dev/torbscript/language/issues/227, https://git.torb.dev/torbscript/language/issues/228, slice 3 PR https://git.torb.dev/torbscript/language/pulls/229, CI pending (head 8c7913ea: commit messages, change scope, pdisc, VS Code and bootstrap linux-x64 green; tier A, tier B, gates and the remaining lanes still queued for a runner at the worker's timeout), next: waiting (forge CI of #229 to finish, then review by the merge gate argus) - The previous head ran tier A fully: std tests native and VM (including all 746 Wycheproof P-256 cases), `format --check`, `docs skill --check` and `doc --check` green; only `docs check`/`docs index --check` were red (summary over 240 characters, index not regenerated), fixed in 8c7913ea and checked locally. - Seed impact: none (no native, compiler or seed change). - Security review before the PR: no blocking findings; two doc points folded in (bounds of the Montgomery numbers, the WebAuthn message and signature forms). - Worker usage: about 2 h 45 min wall time, about 0.5 M tokens including one review subagent (41 k).
Author
Collaborator

[loop:torbscript] implement: parent issue; the work is split into #223 to #228 (#225 landed, #223 has PR #249 open, #224, #226 to #228 follow their dependencies). Nothing to implement here. next: waiting (slices #223 to #228) until 2026-10-10T04:00:00Z

[loop:torbscript] implement: parent issue; the work is split into #223 to #228 (#225 landed, #223 has PR #249 open, #224, #226 to #228 follow their dependencies). Nothing to implement here. next: waiting (slices #223 to #228) until 2026-10-10T04:00:00Z
Author
Collaborator

[loop:torbscript] implement: parent issue, nothing to implement here; slice #227 is in refine (cipher split out as #264), #228 follows #227. next: waiting (slices #227, #228) until 2026-10-10T04:00:00Z

[loop:torbscript] implement: parent issue, nothing to implement here; slice #227 is in refine (cipher split out as #264), #228 follows #227. next: waiting (slices #227, #228) until 2026-10-10T04:00:00Z
Author
Collaborator

[loop:torbscript] close: all six slices (#223 to #228) are closed and landed on main; nothing is left on this parent issue. Closing. Follow-up limits from review are tracked in #274.

[loop:torbscript] close: all six slices (#223 to #228) are closed and landed on main; nothing is left on this parent issue. Closing. Follow-up limits from review are tracked in #274.
orion closed this issue 2026-10-12 06:53:16 +00:00
Sign in to join this conversation.
No labels
loop
ready
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
torbscript/language#16
No description provided.