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 56 if err != nil { 57 // Check if it's a "not found" error 58 errStr := err.Error() 59 if strings.Contains(errStr, "not found") || 60 strings.Contains(errStr, "NoRecordsFound") || 61 strings.Contains(errStr, "404") { 62 return nil, &ErrNotFound{ 63 Identifier: identifier, 64 Reason: errStr, 65 } 66 } 67 68 return nil, &ErrResolutionFailed{ 69 Identifier: identifier, 70 Reason: errStr, 71 } 72 } 73 74 // Extract PDS URL from identity 75 pdsURL := ident.PDSEndpoint() 76 77 return &Identity{ 78 DID: ident.DID.String(), 79 Handle: ident.Handle.String(), 80 PDSURL: pdsURL, 81 ResolvedAt: time.Now().UTC(), 82 Method: MethodHTTPS, // Default - Indigo doesn't expose which method was used 83 }, nil 84} 85 86// ResolveHandle specifically resolves a handle to DID and PDS URL 87func (r *baseResolver) ResolveHandle(ctx context.Context, handle string) (did, pdsURL string, err error) { 88 ident, err := r.Resolve(ctx, handle) 89 if err != nil { 90 return "", "", err 91 } 92 93 return ident.DID, ident.PDSURL, nil 94} 95 96// ResolveDID retrieves a DID document and extracts the PDS endpoint 97func (r *baseResolver) ResolveDID(ctx context.Context, didStr string) (*DIDDocument, error) { 98 did, err := syntax.ParseDID(didStr) 99 if err != nil { 100 return nil, &ErrInvalidIdentifier{ 101 Identifier: didStr, 102 Reason: fmt.Sprintf("invalid DID format: %v", err), 103 } 104 } 105 106 ident, err := r.directory.LookupDID(ctx, did) 107 if err != nil { 108 return nil, &ErrResolutionFailed{ 109 Identifier: didStr, 110 Reason: err.Error(), 111 } 112 } 113 114 // Construct our DID document from Indigo's identity 115 doc := &DIDDocument{ 116 DID: ident.DID.String(), 117 Service: []Service{}, 118 } 119 120 // Extract PDS service endpoint 121 pdsURL := ident.PDSEndpoint() 122 if pdsURL != "" { 123 doc.Service = append(doc.Service, Service{ 124 ID: "#atproto_pds", 125 Type: "AtprotoPersonalDataServer", 126 ServiceEndpoint: pdsURL, 127 }) 128 } 129 130 return doc, nil 131} 132 133// Purge is a no-op for base resolver (no caching) 134func (r *baseResolver) Purge(ctx context.Context, identifier string) error { 135 // Base resolver doesn't cache, so nothing to purge 136 return nil 137}