A community based topic aggregation platform built on atproto
at main 3.0 kB view raw
1//go:build dev 2 3package oauth 4 5import ( 6 "context" 7 "encoding/json" 8 "fmt" 9 "log/slog" 10 "net/http" 11 "net/url" 12 "strings" 13 "time" 14) 15 16// DevHandleResolver resolves handles via local PDS for development 17// This is needed because local handles (e.g., user.local.coves.dev) can't be 18// resolved via standard DNS/HTTP well-known methods - they only exist on the local PDS. 19type DevHandleResolver struct { 20 pdsURL string 21 httpClient *http.Client 22} 23 24// NewDevHandleResolver creates a resolver that queries local PDS for handle resolution 25func NewDevHandleResolver(pdsURL string, allowPrivateIPs bool) *DevHandleResolver { 26 return &DevHandleResolver{ 27 pdsURL: strings.TrimSuffix(pdsURL, "/"), 28 httpClient: NewSSRFSafeHTTPClient(allowPrivateIPs), 29 } 30} 31 32// ResolveHandle queries the local PDS to resolve a handle to a DID 33// Returns the DID if successful, or empty string if not found 34func (r *DevHandleResolver) ResolveHandle(ctx context.Context, handle string) (string, error) { 35 if r.pdsURL == "" { 36 return "", fmt.Errorf("PDS URL not configured") 37 } 38 39 // Build the resolve handle URL 40 resolveURL := fmt.Sprintf("%s/xrpc/com.atproto.identity.resolveHandle?handle=%s", 41 r.pdsURL, url.QueryEscape(handle)) 42 43 // Create request with context and timeout 44 ctx, cancel := context.WithTimeout(ctx, 10*time.Second) 45 defer cancel() 46 47 req, err := http.NewRequestWithContext(ctx, "GET", resolveURL, nil) 48 if err != nil { 49 return "", fmt.Errorf("failed to create request: %w", err) 50 } 51 req.Header.Set("User-Agent", "Coves/1.0") 52 53 // Execute request 54 resp, err := r.httpClient.Do(req) 55 if err != nil { 56 return "", fmt.Errorf("failed to query PDS: %w", err) 57 } 58 defer resp.Body.Close() 59 60 // Check response status 61 if resp.StatusCode == http.StatusNotFound || resp.StatusCode == http.StatusBadRequest { 62 return "", nil // Handle not found 63 } 64 if resp.StatusCode != http.StatusOK { 65 return "", fmt.Errorf("PDS returned status %d", resp.StatusCode) 66 } 67 68 // Parse response 69 var result struct { 70 DID string `json:"did"` 71 } 72 if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { 73 return "", fmt.Errorf("failed to parse PDS response: %w", err) 74 } 75 76 if result.DID == "" { 77 return "", nil // No DID in response 78 } 79 80 slog.Debug("resolved handle via local PDS", 81 "handle", handle, 82 "did", result.DID, 83 "pds_url", r.pdsURL) 84 85 return result.DID, nil 86} 87 88// ResolveIdentifier attempts to resolve a handle to DID, or returns the DID if already provided 89// This is the main entry point for the handlers 90func (r *DevHandleResolver) ResolveIdentifier(ctx context.Context, identifier string) (string, error) { 91 // If it's already a DID, return as-is 92 if strings.HasPrefix(identifier, "did:") { 93 return identifier, nil 94 } 95 96 // Try to resolve the handle via local PDS 97 did, err := r.ResolveHandle(ctx, identifier) 98 if err != nil { 99 return "", fmt.Errorf("failed to resolve handle via PDS: %w", err) 100 } 101 if did == "" { 102 return "", fmt.Errorf("handle not found on local PDS: %s", identifier) 103 } 104 105 return did, nil 106}