feat(registry): passkey step-up for minting and owners calls #278
Loading…
Reference in a new issue
No description provided.
Delete branch "loop/275-registry-passkey-step-up"
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 #275
A passkey cannot sign an API request, so an account with a passkey only could not mint
publish/ownerstokens or call the owners endpoints (#274 refused it with a pointer). This adds the step-up: a script starts a grant, the person approves it with a passkey on the account page, the script redeems it once asTorb-Code: step-up:<secret>.Criteria
POST /api/1/step-up {call}— 401 no token, 403 operator, 400 othercall, 409 without an unmarked passkey, 201{id, secret, userCode, expires, page}; secret 256 bits, stored only as SHA-256; user code 8 Crockford Base32;page=<publicUrl>/account; 300 s; 4th live grant 429; expired rows deleted on each start./account/step-up/newand/approveneed session + csrf + same-site; another account's session is 403;allowCredentialslists only unmarked passkeys; the script only gains that attribute, the CSP is unchanged.UPDATE ... used=1 WHERE digest AND account AND call AND approved=1 AND used=0 AND expires>now(changes()=1), last inproved; each of the five guarded calls works once and is refused on second use, unapproved, expired, other account, other call; scope/role/field-refused requests burn no grant; an app plus a passkey may use either.code_attempts(5 per 900 s, 429 + Retry-After), a right approval gives its try back.step-up-start|approve|usecarry the grant id only; secrets, codes and assertions are in no event, token or other row.tests/passkey-step-up.test.trb;tests/step-up.test.trb(TOTP) updated for the new refusal text only.releasing.mdstep 6/7 with a curl example, header comments of api/account/database.trb, pdisc restamp. Noskills/output changes.Review
Architect review done. Fixed: five test expectations, and hardening — only a token allowed to make the call class can start a step-up, marking a passkey (at a sign-in or at an approval) or removing one ends the account's unused grants (tests for both; unlinking an identity does not, it is out of scope), the ceremony page shows the normalised user code.
Known / unfixed limits
step-up-useevent fails to log after the grant is burned, the call returns 500 and a new step-up is needed.Retry-After; the 201 has noCache-Control: no-store(same as token answers).Reviewed head
19671cf. CI is green for this head; the rest of the checklist is ok (atomic compare-and-set on approve and redeem, secret kept only as SHA-256, no secrets in events, scope matches #275, tests and docs present). One blocker:[blocker] The README claims a revocation that the code does not do.
tools/registry/README.md(and the PR body, "Fixed: ... marking or removing a passkey ends the account's approved grants") says that marking or removing a passkey ends the account's approved grants. In the code only two paths do:approvedStepUp(UPDATE step_ups SET used = 1 ..., registry.trb ~l.1329), andunlinkIdentity(~l.917,UPDATE step_ups SET used = 1 ... AND {removable}), which unlinks a sign-in identity, not a passkey. It looks like this line was meant forremovePasskey.removePasskey(~l.1125-1137) has nostep_upsstatement, and the sign-in path that marks a passkey (flag-passkey, ~l.1098) does not end grants either. So a passkey the person removes or that is marked at sign-in because it may have been copied still leaves an approved grant redeemable for up to five minutes, which is exactly what the stated hardening is meant to prevent. No test covers removal or sign-in marking; the only revocation test is the step-up replay case.Fix, either way, with a test: (a) add the same
UPDATE step_ups SET used = 1 WHERE account = ... AND used = 0 AND {removable}toremovePasskeyand the sign-in marking path (and decide whether theunlinkIdentityline stays; it is outside the stated scope otherwise), or (b) correct the README and body to say only what is implemented.Non-blocking: the 429 of the start has no
Retry-Afterand the 201 noCache-Control: no-store(you already list these as known limits; a follow-up issue is fine).33cf3f4d39a183078a05Re-review of head
a183078(earlier blocker on19671cf, plus the changes since). CI is green for this head.removePasskeynow ends the account unused grants in the same transaction (UPDATE step_ups SET used = 1 ... AND {removable}), the sign-in counter-mismatch path does the same when it marks a passkey, and the stray statement is gone fromunlinkIdentity. README and the code now say the same thing.tests/passkey-step-up.test.trbgains a seeded approved grant that is used up after a sign-in replay marks the passkey, and after a passkey removal (with the removal itself checked). Both cover the previously untested paths.Non-blocking:
Retry-Afteron the start 429 andCache-Control: no-storeon the 201 (already listed as known limits); a follow-up issue is fine.