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}