APIs for links and references in the ATmosphere

com.atproto.identity.resolveHandle

Changed files
+97
slingshot
src
+97
slingshot/src/server.rs
··· 91 91 })) 92 92 } 93 93 94 + fn bad_request_handler_resolve_handle(err: poem::Error) -> JustDidResponse { 95 + JustDidResponse::BadRequest(Json(XrpcErrorResponseObject { 96 + error: "InvalidRequest".to_string(), 97 + message: format!("Bad request, here's some info that maybe should not be exposed: {err}"), 98 + })) 99 + } 100 + 94 101 #[derive(Object)] 95 102 #[oai(example = true)] 96 103 struct FoundRecordResponseObject { ··· 182 189 BadRequest(XrpcError), 183 190 } 184 191 192 + #[derive(Object)] 193 + #[oai(example = true)] 194 + struct FoundDidResponseObject { 195 + /// the DID, bi-directionally verified if using Slingshot 196 + did: String, 197 + } 198 + impl Example for FoundDidResponseObject { 199 + fn example() -> Self { 200 + Self { did: example_did() } 201 + } 202 + } 203 + 204 + #[derive(ApiResponse)] 205 + #[oai(bad_request_handler = "bad_request_handler_resolve_handle")] 206 + enum JustDidResponse { 207 + /// Resolution succeeded 208 + #[oai(status = 200)] 209 + Ok(Json<FoundDidResponseObject>), 210 + /// Bad request, failed to resolve, or failed to verify 211 + /// 212 + /// `error` will be one of `InvalidRequest`, `HandleNotFound`. 213 + #[oai(status = 400)] 214 + BadRequest(XrpcError), 215 + /// Something went wrong trying to complete the request 216 + #[oai(status = 500)] 217 + ServerError(XrpcError), 218 + } 219 + 185 220 struct Xrpc { 186 221 cache: HybridCache<String, CachedRecord>, 187 222 identity: Identity, ··· 312 347 cid, 313 348 ) 314 349 .await 350 + } 351 + 352 + /// com.atproto.identity.resolveHandle 353 + /// 354 + /// Resolves an atproto [`handle`](https://atproto.com/guides/glossary#handle) 355 + /// (hostname) to a [`DID`](https://atproto.com/guides/glossary#did-decentralized-id). 356 + /// 357 + /// Compatibility note: **Slingshot will _always_ bi-directionally verify 358 + /// against the DID document**, which is optional by the authoritative 359 + /// lexicon. You can trust the `DID` returned from this endpoint without 360 + /// further checks, but this may not hold if you switch from Slingshot to a 361 + /// different service offering this query. 362 + /// 363 + /// See also the [canonical `com.atproto` XRPC documentation](https://docs.bsky.app/docs/api/com-atproto-identity-resolve-handle) 364 + /// that this endpoint aims to be compatible with. 365 + #[oai( 366 + path = "/com.atproto.identity.resolveHandle", 367 + method = "get", 368 + tag = "ApiTags::ComAtproto" 369 + )] 370 + async fn resolve_handle( 371 + &self, 372 + /// The handle to resolve. 373 + #[oai(example = "example_handle")] 374 + Query(handle): Query<String>, 375 + ) -> JustDidResponse { 376 + let Ok(handle) = Handle::new(handle) else { 377 + return JustDidResponse::BadRequest(xrpc_error("InvalidRequest", "not a valid handle")); 378 + }; 379 + 380 + let Ok(alleged_did) = self.identity.handle_to_did(handle.clone()).await else { 381 + return JustDidResponse::ServerError(xrpc_error("Failed", "Could not resolve handle")); 382 + }; 383 + 384 + let Some(alleged_did) = alleged_did else { 385 + return JustDidResponse::BadRequest(xrpc_error( 386 + "HandleNotFound", 387 + "Could not resolve handle to a DID", 388 + )); 389 + }; 390 + 391 + let Ok(partial_doc) = self.identity.did_to_partial_mini_doc(&alleged_did).await else { 392 + return JustDidResponse::ServerError(xrpc_error("Failed", "Could not fetch DID doc")); 393 + }; 394 + 395 + let Some(partial_doc) = partial_doc else { 396 + return JustDidResponse::BadRequest(xrpc_error( 397 + "HandleNotFound", 398 + "Resolved handle but could not find DID doc for the DID", 399 + )); 400 + }; 401 + 402 + if partial_doc.unverified_handle != handle { 403 + return JustDidResponse::BadRequest(xrpc_error( 404 + "HandleNotFound", 405 + "Resolved handle failed bi-directional validation", 406 + )); 407 + } 408 + 409 + JustDidResponse::Ok(Json(FoundDidResponseObject { 410 + did: alleged_did.to_string(), 411 + })) 315 412 } 316 413 317 414 /// com.bad-example.identity.resolveMiniDoc