An atproto PDS written in Go
1package identity 2 3import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "io" 8 "net" 9 "net/http" 10 "strings" 11 12 "github.com/bluesky-social/indigo/atproto/syntax" 13 "github.com/bluesky-social/indigo/util" 14) 15 16func ResolveHandle(ctx context.Context, cli *http.Client, handle string) (string, error) { 17 if cli == nil { 18 cli = util.RobustHTTPClient() 19 } 20 21 var did string 22 23 _, err := syntax.ParseHandle(handle) 24 if err != nil { 25 return "", err 26 } 27 28 recs, err := net.LookupTXT(fmt.Sprintf("_atproto.%s", handle)) 29 if err == nil { 30 for _, rec := range recs { 31 if strings.HasPrefix(rec, "did=") { 32 did = strings.Split(rec, "did=")[1] 33 break 34 } 35 } 36 } else { 37 fmt.Printf("erorr getting txt records: %v\n", err) 38 } 39 40 if did == "" { 41 req, err := http.NewRequestWithContext( 42 ctx, 43 "GET", 44 fmt.Sprintf("https://%s/.well-known/atproto-did", handle), 45 nil, 46 ) 47 if err != nil { 48 return "", nil 49 } 50 51 resp, err := http.DefaultClient.Do(req) 52 if err != nil { 53 return "", nil 54 } 55 defer resp.Body.Close() 56 57 if resp.StatusCode != http.StatusOK { 58 io.Copy(io.Discard, resp.Body) 59 return "", fmt.Errorf("unable to resolve handle") 60 } 61 62 b, err := io.ReadAll(resp.Body) 63 if err != nil { 64 return "", err 65 } 66 67 maybeDid := string(b) 68 69 if _, err := syntax.ParseDID(maybeDid); err != nil { 70 return "", fmt.Errorf("unable to resolve handle") 71 } 72 73 did = maybeDid 74 } 75 76 return did, nil 77} 78 79func FetchDidDoc(ctx context.Context, cli *http.Client, did string) (*DidDoc, error) { 80 if cli == nil { 81 cli = util.RobustHTTPClient() 82 } 83 84 var ustr string 85 if strings.HasPrefix(did, "did:plc:") { 86 ustr = fmt.Sprintf("https://plc.directory/%s", did) 87 } else if strings.HasPrefix(did, "did:web:") { 88 ustr = fmt.Sprintf("https://%s/.well-known/did.json", strings.TrimPrefix(did, "did:web:")) 89 } else { 90 return nil, fmt.Errorf("did was not a supported did type") 91 } 92 93 req, err := http.NewRequestWithContext(ctx, "GET", ustr, nil) 94 if err != nil { 95 return nil, err 96 } 97 98 resp, err := http.DefaultClient.Do(req) 99 if err != nil { 100 return nil, err 101 } 102 defer resp.Body.Close() 103 104 if resp.StatusCode != 200 { 105 io.Copy(io.Discard, resp.Body) 106 return nil, fmt.Errorf("could not find identity in plc registry") 107 } 108 109 var diddoc DidDoc 110 if err := json.NewDecoder(resp.Body).Decode(&diddoc); err != nil { 111 return nil, err 112 } 113 114 return &diddoc, nil 115} 116 117func FetchDidData(ctx context.Context, cli *http.Client, did string) (*DidData, error) { 118 if cli == nil { 119 cli = util.RobustHTTPClient() 120 } 121 122 var ustr string 123 ustr = fmt.Sprintf("https://plc.directory/%s/data", did) 124 125 req, err := http.NewRequestWithContext(ctx, "GET", ustr, nil) 126 if err != nil { 127 return nil, err 128 } 129 130 resp, err := http.DefaultClient.Do(req) 131 if err != nil { 132 return nil, err 133 } 134 defer resp.Body.Close() 135 136 if resp.StatusCode != 200 { 137 io.Copy(io.Discard, resp.Body) 138 return nil, fmt.Errorf("could not find identity in plc registry") 139 } 140 141 var diddata DidData 142 if err := json.NewDecoder(resp.Body).Decode(&diddata); err != nil { 143 return nil, err 144 } 145 146 return &diddata, nil 147} 148 149func FetchDidAuditLog(ctx context.Context, cli *http.Client, did string) (DidAuditLog, error) { 150 if cli == nil { 151 cli = util.RobustHTTPClient() 152 } 153 154 var ustr string 155 ustr = fmt.Sprintf("https://plc.directory/%s/log/audit", did) 156 157 req, err := http.NewRequestWithContext(ctx, "GET", ustr, nil) 158 if err != nil { 159 return nil, err 160 } 161 162 resp, err := http.DefaultClient.Do(req) 163 if err != nil { 164 return nil, err 165 } 166 defer resp.Body.Close() 167 168 if resp.StatusCode != 200 { 169 io.Copy(io.Discard, resp.Body) 170 return nil, fmt.Errorf("could not find identity in plc registry") 171 } 172 173 var didlog DidAuditLog 174 if err := json.NewDecoder(resp.Body).Decode(&didlog); err != nil { 175 return nil, err 176 } 177 178 return didlog, nil 179} 180 181func ResolveService(ctx context.Context, cli *http.Client, did string) (string, error) { 182 if cli == nil { 183 cli = util.RobustHTTPClient() 184 } 185 186 diddoc, err := FetchDidDoc(ctx, cli, did) 187 if err != nil { 188 return "", err 189 } 190 191 var service string 192 for _, svc := range diddoc.Service { 193 if svc.Id == "#atproto_pds" { 194 service = svc.ServiceEndpoint 195 } 196 } 197 198 if service == "" { 199 return "", fmt.Errorf("could not find atproto_pds service in identity services") 200 } 201 202 return service, nil 203}