Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/vole.git

Write over SSH with a registered key:

git clone ssh://git@rodent.software:2222/vole.git

A free and open-source forge that lives in your Git repo.

#260

Let people rename and remove their own passkeys

merged · by keenan 3h ago · merged into main at 4b8c9e1d by keenan 3h ago

keenan opened this 3h ago

Closes #215. The Account page listed and added passkeys and could do nothing else: a lost or shared device, or a label typo, needed an instance admin and the CLI. Both are the person’s own credential, so both belong on the page.

What changed

  • Rename. identity.RenameCredential writes the label in one transaction and audits identity.credential-rename with the passkey and its new label. A label is display text, so the credential id and key are untouched, and the cap is the one the ceremony already uses (MaxCredentialLabel). An empty label is allowed and the page calls it a passkey.
  • Remove. identity.RemoveCredential now deletes that passkey’s sessions in the same transaction. The foreign key cascades already; saying it out loud makes it the intent rather than a schema accident, and it means the session the person is reading the page from ends with the passkey that signed it in.
  • The last passkey is refused, with an explanation, while the account has no SSH key either. With an SSH key it is a way in for Git, so an account that has one loses only its browser sign-in. With neither, the only way back is an admin invite, and losing that should never be a click that silently succeeded. Two passkeys are enough to rotate, so the rule protects the last one rather than every one.
  • The page gets a rename and a remove form per passkey, each carrying the CSRF token, the removal with a confirmation, plus a line saying what removing one does. Handlers are scoped by the session account, so a form cannot name somebody else’s credential, and an unknown one is a 404 rather than a silent no-op.

Decisions

  • Refuse the last passkey rather than allow and warn. The issue left both open. The cost of refusing is one extra click (register a replacement, then remove the old one); the cost of allowing is an account nobody can reach without an operator, which is exactly the silent lockout the issue asks us to avoid.
  • An SSH key counts as a way in even though it cannot sign in to the browser: the forge’s job is Git, and an account with a key is still usable without an admin.
  • Two audit records per write, as the SSH key forms already do: the identity store records the change to the roster (identity.credential-*), and the web surface records the action that made it (web.passkey.rename, web.passkey.remove).

Testing

  • Identity: the label changes and the credential does not; whitespace is trimmed; an empty label clears it; an over-long one is refused where it is accepted; another account’s passkey is not renameable; a removed passkey takes its sessions with it.
  • Server, end to end over the account page: rename one of two (the other keeps its label), remove one of several, a post without the token is refused, and the last passkey is refused with a message until an SSH key is added — after which the same removal succeeds.

vole workflow run verify passes (format, vet, build, licenses, govulncheck, packages, deployment files, links, race suite). Docs: docs/accounts-and-access.md covers renaming, the last-passkey rule, and the audit actions.

Discussion (0)

No discussion yet.