@@ -91,6 +91,13 @@ fn bad_request_handler_resolve_mini(err: poem::Error) -> ResolveMiniIDResponse {
9191 } ) )
9292}
9393
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+
94101#[ derive( Object ) ]
95102#[ oai( example = true ) ]
96103struct FoundRecordResponseObject {
@@ -182,6 +189,34 @@ enum ResolveMiniIDResponse {
182189 BadRequest ( XrpcError ) ,
183190}
184191
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+
185220struct Xrpc {
186221 cache : HybridCache < String , CachedRecord > ,
187222 identity : Identity ,
@@ -314,6 +349,68 @@ impl Xrpc {
314349 . await
315350 }
316351
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+ } ) )
412+ }
413+
317414 /// com.bad-example.identity.resolveMiniDoc
318415 ///
319416 /// Like [com.atproto.identity.resolveIdentity](https://docs.bsky.app/docs/api/com-atproto-identity-resolve-identity)
0 commit comments