CLI app for developers prototyping atproto functionality
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Make synthetic handle resolvable under atproto rules

The interactive mode's printed handle was
`fake-client-under-test.<host>`, but DNS for that subdomain is not
something the operator controls (e.g. on a tunnelled funnel host).
A real public client typing the printed handle would fail both
atproto handle-resolution paths: DNS TXT on `_atproto.<subdomain>`
and HTTPS GET on `https://<subdomain>/.well-known/atproto-did` both
go nowhere.

The handle is now the active base URL's host, which by definition
routes to the fake AS, and the AS serves
`/.well-known/atproto-did` returning the synthetic DID as plain
UTF-8. The HTTPS-fallback resolution path now succeeds end-to-end
against the fake AS itself.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

+28 -1
+19
src/commands/test/oauth/client/fake_as/endpoints.rs
··· 141 141 pub fn build_router(state: Arc<AppState>) -> Router { 142 142 Router::new() 143 143 .route("/.well-known/did.json", get(did_json)) 144 + .route("/.well-known/atproto-did", get(atproto_did)) 144 145 .route("/.well-known/oauth-protected-resource", get(prm)) 145 146 .route("/.well-known/oauth-authorization-server", get(as_metadata)) 146 147 .route("/oauth/par", post(par)) ··· 158 159 ) -> Response { 159 160 log_request(&s, &method, &uri, &headers, &body); 160 161 Json(s.identity.did_document.clone()).into_response() 162 + } 163 + 164 + async fn atproto_did( 165 + State(s): State<Arc<AppState>>, 166 + method: Method, 167 + uri: Uri, 168 + headers: HeaderMap, 169 + body: Bytes, 170 + ) -> Response { 171 + log_request(&s, &method, &uri, &headers, &body); 172 + // Per atproto handle resolution, the HTTPS-fallback path returns 173 + // the DID as plain UTF-8 with no JSON wrapper or trailing newline. 174 + ( 175 + StatusCode::OK, 176 + [("content-type", "text/plain; charset=utf-8")], 177 + s.identity.did.clone(), 178 + ) 179 + .into_response() 161 180 } 162 181 163 182 async fn prm(
+9 -1
src/commands/test/oauth/client/fake_as/identity.rs
··· 24 24 } else { 25 25 format!("did:web:{host}") 26 26 }; 27 - let handle = format!("fake-client-under-test.{host}"); 27 + // The handle is the active base URL's host. atproto handle 28 + // resolution per the spec attempts DNS TXT 29 + // `_atproto.<handle>` then HTTPS GET 30 + // `https://<handle>/.well-known/atproto-did`; we serve the 31 + // latter from the fake AS itself, so the host of the active 32 + // base URL (the only host that routes to this server) 33 + // resolves to the synthetic DID. A subdomain prefix on the 34 + // handle would require DNS the operator does not control. 35 + let handle = host.to_owned(); 28 36 29 37 let base_with_slash = ensure_trailing_slash(base); 30 38 let base_no_slash = base.as_str().trim_end_matches('/');