fix(server): conflict on an ssh key the caller already enrolled - #6850
Merged
Conversation
Enrolling a key the caller already holds returned the existing record with a 200, which the frontend cannot tell apart from a successful creation, so the person saw no feedback at all. Report it as a duplicate instead, matching how CreatePublicKey answers, and give the drawer the 409 it already renders. The approval flow can legitimately replay a confirmation for the same key, so it enrolls through reenrollSSHIdentity, which keeps returning the existing binding for the same principal. Fixes: #6846
|
Claude finished @otavio's task in 1m 9s —— View job Code Review CompleteReviewed 3 files across code quality, security, testing, language patterns, and architecture — no new issues found. The code looks good as-is. A few things I verified while reviewing:
The To request another review round, comment |
The consent flow asks the server whether this browser's key is registered before enrolling it, so a second tab enrolling the same key in between now answers 409 and the dialog refuses the connection the person just consented to. Treat that conflict as the enrollment it reports and carry on connecting. The check the identity drawer already made against 409 moves to the same helper, so both callers read the status through one name.
Member
Author
|
/review |
|
Claude finished @otavio's task in 3m 18s —— View job Code Review CompleteReviewed 7 files across code quality, security, testing, language patterns, and architecture — no new issues found. The code looks good as-is. Highlights verified this round:
To request another review round, comment |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Enrolling an SSH public key that the caller already holds now answers
409 Conflictinstead of200with the pre-existing record.Why
enrollSSHIdentitytreated same-fingerprint-same-principal as an idempotent success, returning the existing*models.SSHIdentitywith a nil error. The handler then responded200with the old record — indistinguishable from a real creation, so the frontend had nothing to key off and the person saw no feedback at all. This aligns the endpoint withCreatePublicKey, which already returnsErrPublicKeyDuplicatedfor a taken fingerprint.Closes #6846
Changes
Server
enrollSSHIdentity: strict. Any fingerprint already taken in the namespace — by this principal or another — returnsNewErrSSHIdentityDuplicated, which the existingErrCodeDuplicated->http.StatusConflictmapping inconverter.FromErrServiceToHTTPStatusturns into a 409.reenrollSSHIdentity: new, and the reason the strict change is safe. An SSH approval can legitimately be confirmed twice for the same key, soapplySSHApprovalenrolls through this variant, which still returns the existing binding for the same principal. A fingerprint held by a different principal conflicts on both paths.resolveEnrolledSSHIdentity/persistSSHIdentity: the shared halves both paths need. Each enroll path performs exactly one store lookup — the split adds no query.UI
The identity drawer already branched on 409 to show "This key is already an identity in this namespace.", so the reported symptom needed no frontend change. But
ConnectDrawer's browser-key consent flow did: it asks the server whether the key is registered before enrolling, and a second tab enrolling the same key in between previously fell into the idempotent 200. Under the strict server it would have hit 409 and refused the connection the person had just consented to.isAlreadyEnrolled: one named predicate for the status, replacing the drawer's inlineisSdkError(err) && err.status === 409.enrollAndConnect: swallows that specific conflict and connects. The key is one only this browser holds privately, so a 409 on it can only be an enrollment of the caller's own.No API-surface change was needed —
openapi/spec/paths/api@ssh-identities.yamlalready documented409.Testing
Reviewers should focus on the enroll/reenroll split rather than the error mapping.
TestReenrollSSHIdentityasserts the returnedID, not just a nil error, so it cannot pass onnil, nil.ConnectDrawerchange itself is untested. That component has no test harness and standing one up needs WebCrypto Ed25519 plus IndexedDB fakes; the extracted predicate is covered instead. Worth exercising by hand: open the connect drawer in two tabs on a browser with no enrolled key and confirm consent in both — the second should connect rather than show "Could not register this browser."Server suite (27 packages) and
golangci-lint run ./...clean; console build, lint, and 3000 tests clean.