Caddy module to require at-proto authentication and restrict routes to DIDs
3

Configure Feed

Select the types of activity you want to include in your feed.

at main 4.0 kB View raw
1package resolver 2 3import ( 4 "context" 5 "fmt" 6 "net" 7 "net/http" 8 "strings" 9 "time" 10 11 "github.com/bluesky-social/indigo/atproto/identity" 12 "github.com/bluesky-social/indigo/atproto/syntax" 13) 14 15// Resolver handles AT Protocol identity resolution (Handles, DID:PLC, DID:Web). 16type Resolver struct { 17 dir identity.Directory 18} 19 20// New initializes a new identity resolver using the default Bluesky directory. 21// This handles DNS TXT, HTTPS well-known, PLC, and DID:Web lookups with caching. 22func New() *Resolver { 23 return &Resolver{ 24 dir: identity.DefaultDirectory(), 25 } 26} 27 28// NewWithAllowedCIDRs initializes a new identity resolver with a custom HTTP client 29// that allows connections to the specified private CIDRs. 30func NewWithAllowedCIDRs(allowedCIDRs []net.IPNet) *Resolver { 31 base := identity.BaseDirectory{ 32 HTTPClient: createCustomHTTPClient(allowedCIDRs), 33 } 34 35 cached := identity.NewCacheDirectory(&base, 250000, time.Hour*24, time.Minute*2, time.Minute*5) 36 37 return &Resolver{ 38 dir: cached, 39 } 40} 41 42func createCustomHTTPClient(allowedCIDRs []net.IPNet) http.Client { 43 if len(allowedCIDRs) == 0 { 44 return http.Client{} 45 } 46 47 transport := http.DefaultTransport.(*http.Transport).Clone() 48 originalDial := transport.DialContext 49 50 transport.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) { 51 host, _, err := net.SplitHostPort(addr) 52 if err != nil { 53 host = addr 54 } 55 56 ip := net.ParseIP(host) 57 if ip == nil { 58 addrs, err := net.DefaultResolver.LookupIP(ctx, "ip", host) 59 if err != nil { 60 return nil, fmt.Errorf("failed to resolve %s: %w", host, err) 61 } 62 for _, a := range addrs { 63 if isPublicIP(a) { 64 return originalDial(ctx, network, addr) 65 } 66 for _, cidr := range allowedCIDRs { 67 if cidr.Contains(a) { 68 return originalDial(ctx, network, addr) 69 } 70 } 71 } 72 return nil, fmt.Errorf("resolved IP for %s is not public and not in allowed private CIDRs", host) 73 } 74 75 if isPublicIP(ip) { 76 return originalDial(ctx, network, addr) 77 } 78 79 for _, cidr := range allowedCIDRs { 80 if cidr.Contains(ip) { 81 return originalDial(ctx, network, addr) 82 } 83 } 84 85 return nil, fmt.Errorf("address %s is not public and not in allowed private CIDRs", addr) 86 } 87 88 return http.Client{ 89 Transport: transport, 90 Timeout: 10 * time.Second, 91 } 92} 93 94func isPublicIP(ip net.IP) bool { 95 if ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsUnspecified() { 96 return false 97 } 98 if ip4 := ip.To4(); ip4 != nil { 99 return (ip4[0] != 10) && (ip4[0] != 172 || ip4[1] < 16 || ip4[1] > 31) && (ip4[0] != 192 || ip4[1] != 168) 100 } 101 _, ipv6private, _ := net.ParseCIDR("fc00::/7") 102 return ipv6private == nil || !ipv6private.Contains(ip) 103} 104 105// ResolveIdentifier converts either a handle (e.g., @user.com) or a DID into 106// a verified DID, validating it against the directory. 107func (r *Resolver) ResolveIdentifier(ctx context.Context, ident string) (string, error) { 108 ident = strings.TrimPrefix(ident, "@") 109 110 atID, err := syntax.ParseAtIdentifier(ident) 111 if err != nil { 112 return "", fmt.Errorf("invalid identifier '%s': %w", ident, err) 113 } 114 115 id, err := r.dir.Lookup(ctx, atID) 116 if err != nil { 117 return "", fmt.Errorf("failed to resolve identity '%s': %w", ident, err) 118 } 119 120 if id == nil { 121 return "", fmt.Errorf("identity '%s' not found", ident) 122 } 123 124 return id.DID.String(), nil 125} 126 127// GetPDSEndpoint returns the Personal Data Server (PDS) URL for a given DID or handle. 128func (r *Resolver) GetPDSEndpoint(ctx context.Context, ident string) (string, error) { 129 ident = strings.TrimPrefix(ident, "@") 130 131 atID, err := syntax.ParseAtIdentifier(ident) 132 if err != nil { 133 return "", fmt.Errorf("invalid identifier '%s': %w", ident, err) 134 } 135 136 id, err := r.dir.Lookup(ctx, atID) 137 if err != nil { 138 return "", fmt.Errorf("failed to resolve identity '%s': %w", ident, err) 139 } 140 141 if id == nil { 142 return "", fmt.Errorf("identity '%s' not found", ident) 143 } 144 145 pds := id.PDSEndpoint() 146 if pds == "" { 147 return "", fmt.Errorf("no PDS endpoint found for identity '%s'", ident) 148 } 149 150 return pds, nil 151}