fix(identity)!: authorise the ATProto binding write and move it to fyi.xnet - #589
Merged
Conversation
…i.xnet The OAuth client declared `"scope": "atproto"`, which is identity-only and grants no data access, while the ceremony performed `com.atproto.repo.putRecord` to write the identity binding. Nothing failed loudly — the write was simply never authorised. Corroborated live: the binding was held by 0 DIDs network-wide. Two fixes, taken together because the second makes the first's cost zero: - Declare the granular `repo:` scope the write actually needs, narrowed to `action=create&action=update` (putRecord needs no delete). Older self-hosted PDSes reject granular scope syntax in client-metadata.json outright, and the scope lives *in* that document, so a single document cannot carry both. A second client_id serving `atproto transition:generic` provides the fallback, with the ceremony retrying against it only on a scope-syntax rejection — never on user cancellation or a transport failure. - Move the binding collection from `net.x.identity.binding` to `fyi.xnet.identity.binding`. NSIDs are DNS-rooted, so authority over `net.x.*` requires control of `x.net`, which belongs to IANA and can never be ours. Migration cost is nil precisely because the scope defect meant no record was ever written. A regression test ties the declared scope to the exported collection constant, so a rename on either side fails in CI rather than in production. Verified by mutation: reverting the scope, using a prefix wildcard, or renaming the collection alone each fail the suite. BREAKING CHANGE: `ATPROTO_BINDING_COLLECTION` changes from `net.x.identity.binding` to `fyi.xnet.identity.binding`. The name appears in the record `$type`, in every binding's `at://` URI, and in the hub's verification path. No records existed under the old name. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: xNet Test <test@xnet.dev>
Contributor
🖼️ UI changes in this PRNo visual differences detected in the changed UI. |
…ange `check:api-report` runs inside the typecheck job and flagged `packages/react/etc/react.api.md`. The only delta is a bundled chunk's content hash in a trailing warning comment, shifted by the doc-comment edit in `onboarding/atproto-ceremony.ts` — no public API surface changed. Regenerated with the repo's own command (`pnpm --filter @xnetjs/react api:update`) rather than hand-editing. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: xNet Test <test@xnet.dev>
Contributor
|
Preview removed for PR #589. |
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.
The defect
site/public/oauth/atproto-client.jsondeclared"scope": "atproto"— identity only, no data access — whileapps/web/src/identity/atproto-ceremony.tsperformedcom.atproto.repo.putRecordto write the identity binding. The write was never authorised, and nothing failed loudly.Confirmed against live systems rather than docs alone:
"scope": "atproto"(curl https://xnet.fyi/oauth/atproto-client.json)com.atproto.sync.listReposByCollectiononrelay1.us-west.bsky.networkreturned{"repos":[]}— 0 DIDs held the bindingContext: exploration 0371, defects D1 and D2.
What changed
Granular scope. Now
atproto repo:fyi.xnet.identity.binding?action=create&action=update— narrowed to the two actionsputRecordneeds, rather than barerepo:<nsid>which would also grantdelete.A second client_id for the fallback. The scope lives in the metadata document, and older self-hosted PDSes reject granular syntax in that document outright (#4118). One document therefore cannot carry both: a PDS that chokes on
repo:chokes on it whatever we go on to request.atproto-client-compat.jsonservesatproto transition:generic, and the ceremony retries against it only on a scope-syntax rejection — never on user cancellation or a transport failure, since reopening a popup the user just dismissed would be worse than failing.Namespace rename.
net.x.identity.binding→fyi.xnet.identity.binding. NSID authority is DNS-rooted, sonet.x.*requires control ofx.net— IANA's, never ours. Migration cost is nil precisely because the scope defect meant no record was ever written.Verification
The regression test ties the declared scope to the exported
ATPROTO_BINDING_COLLECTIONconstant, so a rename on either side fails in CI. Mutation-tested rather than trusted green — reverting the scope to bareatproto, usingrepo:fyi.xnet.*, and renaming the collection without updating the scope each fail it; restored, 10/10 pass.Not verified: the real putRecord
A live
putRecordhas not been exercised. The PDS fetches client metadata from the deployedhttps://xnet.fyi/...URL, so local edits are invisible to it and production still serves the old scope. Verification is only possible after this merges andsite/public/oauth/deploys: sign in with a test handle, then re-query the relay forfyi.xnet.identity.binding. A non-empty result is the proof.Two caveats worth knowing
bsky.socialadvertises onlyatprotoandtransition:*inscopes_supported. Granular scopes are implemented there — parameterized scopes simply aren't enumerable in that list — but discovery cannot confirm support, so the compat path may carry more traffic than "old self-hosted PDSes only" implies.transition:genericrather than breaking sign-in.Follow-up (not in scope here)
_lexicon.xnet.fyihas no TXT record, so the new lexicon isn't resolvable yet. Publication is "strongly advised" but not required — natural next step.Release
Changeset is major: the collection name is a wire contract appearing in the record
$type, in every binding'sat://URI, and in the hub's verification path.🤖 Generated with Claude Code