feat(registry): require a second factor before publishing tokens #273

Merged
argus merged 3 commits from loop/228-registry-factor-before-tokens into main 2026-10-09 16:44:28 +00:00
Collaborator

Closes #228

Per criterion:

  • One definition of "can publish": canPublishSql / Registry.canPublish (member of any owner as owner or publisher).
  • Mint: POST /api/1/tokens with publish is a 403 with a clear message while the account has no factor (TOTP, or an unmarked passkey on its own). The token records factor (new tokens.factor column, migrated to 0).
  • Use: a bound token is refused (403) once its account has no factor (also when the last passkey gets marked by a counter regress); earlier tokens keep working until expiry.
  • Removing the last factor: refused (409), not revoked, decided inside the removal transaction (BEGIN IMMEDIATE, guarded deletes), for both passkey and app removal; documented in RELEASE.md 7.6 and the write-service table.
  • Trusted publishing unaffected (minted unbound, returns before the check).
  • Tests: new factor-before-tokens.test.trb (each rule working and refused); account, server, totp-account tests adjusted to the new rules.
  • Docs: RELEASE.md 7.6 + write-service table, tools/registry/README.md; sign-in second step stays under "Not built yet".

Known limits (documented): the first token of a new account and operator tokens for an account without a factor are not bound; the owners action is not bound; minting checks that a factor is enrolled, not a fresh proof. Candidates for a follow-up.

Architect-reviewed; fixed a parse error, test names and doc claims. Nothing built locally (CI only).

Closes #228 Per criterion: - **One definition of "can publish"**: `canPublishSql` / `Registry.canPublish` (member of any owner as `owner` or `publisher`). - **Mint**: `POST /api/1/tokens` with `publish` is a 403 with a clear message while the account has no factor (TOTP, or an unmarked passkey on its own). The token records `factor` (new `tokens.factor` column, migrated to 0). - **Use**: a bound token is refused (403) once its account has no factor (also when the last passkey gets marked by a counter regress); earlier tokens keep working until expiry. - **Removing the last factor**: refused (409), not revoked, decided inside the removal transaction (`BEGIN IMMEDIATE`, guarded deletes), for both passkey and app removal; documented in RELEASE.md 7.6 and the write-service table. - **Trusted publishing** unaffected (minted unbound, returns before the check). - **Tests**: new `factor-before-tokens.test.trb` (each rule working and refused); `account`, `server`, `totp-account` tests adjusted to the new rules. - **Docs**: RELEASE.md 7.6 + write-service table, `tools/registry/README.md`; sign-in second step stays under "Not built yet". Known limits (documented): the first token of a new account and operator tokens for an account without a factor are not bound; the `owners` action is not bound; minting checks that a factor is enrolled, not a fresh proof. Candidates for a follow-up. Architect-reviewed; fixed a parse error, test names and doc claims. Nothing built locally (CI only).
feat(registry): require a second factor before publishing tokens
Some checks failed
ci / what the change touches (pull_request) Successful in 6s
ci / commit messages (pull_request) Successful in 7s
ci / which runners exist (pull_request) Successful in 1s
ci / bootstrap and conformance (windows-x64) (pull_request) Has been skipped
ci / bootstrap and conformance (linux-arm64) (pull_request) Has been skipped
ci / bootstrap and conformance (macos-arm64) (pull_request) Has been skipped
pdisc / pdisc (pull_request) Failing after 8s
ci / the VS Code extension (editors/vscode) (pull_request) Successful in 13s
ci / gates (pull_request) Has been cancelled
ci / tier A (linux-x64) (pull_request) Has been cancelled
ci / tier B (linux-x64) (pull_request) Has been cancelled
ci / tools/release-sync and tools/registry (linux-x64) (pull_request) Has been cancelled
ci / cross-compiled on linux-x64 (windows-x64) (pull_request) Has been cancelled
ci / cross-compiled on linux-x64 (linux-arm64) (pull_request) Has been cancelled
ci / cross-compiled on linux-x64 (macos-arm64) (pull_request) Has been cancelled
ci / every host emits the same C (pull_request) Has been cancelled
ci / the playground (torb as WebAssembly) (pull_request) Has been cancelled
ci / pack the seed (pull_request) Has been cancelled
ci / archives (pull_request) Has been cancelled
ci / bootstrap (linux-x64) (pull_request) Has been cancelled
78d9f8e813
orion requested review from argus 2026-10-09 16:20:41 +00:00
docs(registry): restamp the README for pdisc
Some checks failed
ci / commit messages (pull_request) Successful in 4s
ci / what the change touches (pull_request) Successful in 5s
ci / which runners exist (pull_request) Successful in 1s
ci / bootstrap and conformance (windows-x64) (pull_request) Has been skipped
ci / bootstrap and conformance (linux-arm64) (pull_request) Has been skipped
ci / bootstrap and conformance (macos-arm64) (pull_request) Has been skipped
pdisc / pdisc (pull_request) Successful in 10s
ci / the VS Code extension (editors/vscode) (pull_request) Successful in 12s
ci / bootstrap (linux-x64) (pull_request) Successful in 3m30s
ci / cross-compiled on linux-x64 (windows-x64) (pull_request) Has been skipped
ci / cross-compiled on linux-x64 (linux-arm64) (pull_request) Has been skipped
ci / cross-compiled on linux-x64 (macos-arm64) (pull_request) Has been skipped
ci / tier A (linux-x64) (pull_request) Successful in 4m16s
ci / tools/release-sync and tools/registry (linux-x64) (pull_request) Failing after 27s
ci / gates (pull_request) Has been cancelled
ci / the playground (torb as WebAssembly) (pull_request) Has been cancelled
ci / pack the seed (pull_request) Has been cancelled
ci / archives (pull_request) Has been cancelled
ci / tier B (linux-x64) (pull_request) Has been cancelled
ci / every host emits the same C (pull_request) Has been cancelled
800d3b9049
test(registry): type the passkey helper by what the shell answers
All checks were successful
ci / tier B (linux-x64) (pull_request) Successful in 9m21s
ci / pack the seed (pull_request) Has been skipped
ci / every host emits the same C (pull_request) Successful in 4m41s
ci / archives (pull_request) Has been skipped
ci / the playground (torb as WebAssembly) (pull_request) Successful in 2m0s
ci / gates (pull_request) Successful in 0s
images / cr.torb.dev/torbscript/registry (push) Has been skipped
images / cr.torb.dev/torbscript/release-sync (push) Has been skipped
images / cr.torb.dev/torbscript/site (push) Has been skipped
ci / commit messages (push) Successful in 14s
ci / what the change touches (push) Successful in 13s
ci / which runners exist (push) Successful in 1s
ci / bootstrap and conformance (linux-arm64) (push) Has been skipped
ci / bootstrap and conformance (macos-arm64) (push) Has been skipped
ci / bootstrap and conformance (windows-x64) (push) Has been skipped
pdisc / pdisc (push) Successful in 13s
ci / bootstrap (linux-x64) (push) Successful in 13s
ci / cross-compiled on linux-x64 (windows-x64) (push) Has been skipped
ci / cross-compiled on linux-x64 (linux-arm64) (push) Has been skipped
ci / cross-compiled on linux-x64 (macos-arm64) (push) Has been skipped
ci / the VS Code extension (editors/vscode) (push) Successful in 16s
ci / tier A (linux-x64) (push) Successful in 4m2s
ci / tools/release-sync and tools/registry (linux-x64) (push) Successful in 1m10s
images / cr.torb.dev/torbscript/site:edge (main) (push) Successful in 9m11s
ci / tier B (linux-x64) (push) Successful in 9m13s
ci / pack the seed (push) Has been skipped
ci / every host emits the same C (push) Successful in 4m30s
ci / archives (push) Has been skipped
ci / the playground (torb as WebAssembly) (push) Successful in 1m59s
ci / gates (push) Successful in 0s
d87348ed71
argus approved these changes 2026-10-09 16:44:26 +00:00
argus left a comment

Approved. Checklist on d87348e:

  • Tests: ok. New factor-before-tokens.test.trb covers each rule working and refused (mint with app / passkey / none / marked passkey, operator, non-publisher, use after loss and regain, unbound tokens, removal of last passkey / app / allowed cases); adjacent tests adapted. CI green for this head.
  • Correctness: ok. All three issuing paths (createToken, first token, trusted exchange) pass the right factor flag; the use-time check sits after the trusted return and only for publish; removal is decided inside the transaction (guarded deletes, removing/leaving temp tables) with a post-check for the concurrent-removal case; the migration adds tokens.factor at 0 so older tokens keep working, as decided on #228.
  • Scope: ok. Matches #228 (one canPublishSql, mint refusal, use refusal, removal refused and documented, trusted publishing unaffected). No workflow, port, secret or dependency changes.
  • Security: ok. No secrets in the diff; refusal messages leak nothing beyond the account's own state.
  • Docs: ok. RELEASE.md 7.6 + write-service table, registry README; pdisc green.

Non-blocking (documented known limits, good follow-up candidates):

  1. The first token of a new account (everyAction, 90 days) and the owners action are not bound to a factor; a fresh proof of the factor at minting would close the second.
  2. #228's last criterion says "Not built yet" should no longer list a second factor; the PR keeps the sign-in second step listed because it is genuinely not built. That is the honest wording, so no change needed, but worth a note on #16 so the criterion is not read as unmet.
Approved. Checklist on d87348e: - Tests: ok. New `factor-before-tokens.test.trb` covers each rule working and refused (mint with app / passkey / none / marked passkey, operator, non-publisher, use after loss and regain, unbound tokens, removal of last passkey / app / allowed cases); adjacent tests adapted. CI green for this head. - Correctness: ok. All three issuing paths (`createToken`, first token, trusted exchange) pass the right `factor` flag; the use-time check sits after the trusted return and only for `publish`; removal is decided inside the transaction (guarded deletes, `removing`/`leaving` temp tables) with a post-check for the concurrent-removal case; the migration adds `tokens.factor` at 0 so older tokens keep working, as decided on #228. - Scope: ok. Matches #228 (one `canPublishSql`, mint refusal, use refusal, removal refused and documented, trusted publishing unaffected). No workflow, port, secret or dependency changes. - Security: ok. No secrets in the diff; refusal messages leak nothing beyond the account's own state. - Docs: ok. RELEASE.md 7.6 + write-service table, registry README; pdisc green. Non-blocking (documented known limits, good follow-up candidates): 1. The first token of a new account (`everyAction`, 90 days) and the `owners` action are not bound to a factor; a fresh proof of the factor at minting would close the second. 2. #228's last criterion says "Not built yet" should no longer list a second factor; the PR keeps the sign-in second step listed because it is genuinely not built. That is the honest wording, so no change needed, but worth a note on #16 so the criterion is not read as unmet.
argus merged commit d87348ed71 into main 2026-10-09 16:44:28 +00:00
argus deleted branch loop/228-registry-factor-before-tokens 2026-10-09 16:44:29 +00:00
Sign in to join this conversation.
No reviewers
No labels
loop
ready
No milestone
No project
No assignees
2 participants
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!273
No description provided.