A community based topic aggregation platform built on atproto
1package identity 2 3import ( 4 "context" 5 "fmt" 6 "net/http" 7 "strings" 8 "time" 9 10 indigoIdentity "github.com/bluesky-social/indigo/atproto/identity" 11 "github.com/bluesky-social/indigo/atproto/syntax" 12) 13 14// baseResolver implements Resolver using Indigo's identity resolution 15type baseResolver struct { 16 directory indigoIdentity.Directory 17} 18 19// newBaseResolver creates a new base resolver using Indigo 20func newBaseResolver(plcURL string, httpClient *http.Client) Resolver { 21 // Create Indigo's BaseDirectory which handles DNS and HTTPS resolution 22 dir := &indigoIdentity.BaseDirectory{ 23 PLCURL: plcURL, 24 HTTPClient: *httpClient, 25 // Indigo will use default DNS resolver if not specified 26 } 27 28 return &baseResolver{ 29 directory: dir, 30 } 31} 32 33// Resolve resolves a handle or DID to complete identity information 34func (r *baseResolver) Resolve(ctx context.Context, identifier string) (*Identity, error) { 35 identifier = strings.TrimSpace(identifier) 36 37 if identifier == "" { 38 return nil, &ErrInvalidIdentifier{ 39 Identifier: identifier, 40 Reason: "identifier cannot be empty", 41 } 42 } 43 44 // Parse the identifier (could be handle or DID) 45 atID, err := syntax.ParseAtIdentifier(identifier) 46 if err != nil { 47 return nil, &ErrInvalidIdentifier{ 48 Identifier: identifier, 49 Reason: fmt.Sprintf("invalid identifier format: %v", err), 50 } 51 } 52 53 // Resolve using Indigo's directory 54 ident, err := r.directory.Lookup(ctx, *atID) 55 if err != nil { 56 // Check if it's a "not found" error 57 errStr := err.Error() 58 if strings.Contains(errStr, "not found") || 59 strings.Contains(errStr, "NoRecordsFound") || 60 strings.Contains(errStr, "404") { 61 return nil, &ErrNotFound{ 62 Identifier: identifier, 63 Reason: errStr, 64 } 65 } 66 67 return nil, &ErrResolutionFailed{ 68 Identifier: identifier, 69 Reason: errStr, 70 } 71 } 72 73 // Extract PDS URL from identity 74 pdsURL := ident.PDSEndpoint() 75 76 return &Identity{ 77 DID: ident.DID.String(), 78 Handle: ident.Handle.String(), 79 PDSURL: pdsURL, 80 ResolvedAt: time.Now().UTC(), 81 Method: MethodHTTPS, // Default - Indigo doesn't expose which method was used 82 }, nil 83} 84 85// ResolveHandle specifically resolves a handle to DID and PDS URL 86func (r *baseResolver) ResolveHandle(ctx context.Context, handle string) (did, pdsURL string, err error) { 87 ident, err := r.Resolve(ctx, handle) 88 if err != nil { 89 return "", "", err 90 } 91 92 return ident.DID, ident.PDSURL, nil 93} 94 95// ResolveDID retrieves a DID document and extracts the PDS endpoint 96func (r *baseResolver) ResolveDID(ctx context.Context, didStr string) (*DIDDocument, error) { 97 did, err := syntax.ParseDID(didStr) 98 if err != nil { 99 return nil, &ErrInvalidIdentifier{ 100 Identifier: didStr, 101 Reason: fmt.Sprintf("invalid DID format: %v", err), 102 } 103 } 104 105 ident, err := r.directory.LookupDID(ctx, did) 106 if err != nil { 107 return nil, &ErrResolutionFailed{ 108 Identifier: didStr, 109 Reason: err.Error(), 110 } 111 } 112 113 // Construct our DID document from Indigo's identity 114 doc := &DIDDocument{ 115 DID: ident.DID.String(), 116 Service: []Service{}, 117 } 118 119 // Extract PDS service endpoint 120 pdsURL := ident.PDSEndpoint() 121 if pdsURL != "" { 122 doc.Service = append(doc.Service, Service{ 123 ID: "#atproto_pds", 124 Type: "AtprotoPersonalDataServer", 125 ServiceEndpoint: pdsURL, 126 }) 127 } 128 129 return doc, nil 130} 131 132// Purge is a no-op for base resolver (no caching) 133func (r *baseResolver) Purge(ctx context.Context, identifier string) error { 134 // Base resolver doesn't cache, so nothing to purge 135 return nil 136}