+73
-54
identity/identity.go
+73
-54
identity/identity.go
···
13
13
"github.com/bluesky-social/indigo/util"
14
14
)
15
15
16
-
func 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)
16
+
func ResolveHandleFromTXT(ctx context.Context, handle string) (string, error) {
17
+
name := fmt.Sprintf("_atproto.%s", handle)
18
+
recs, err := net.LookupTXT(name)
24
19
if err != nil {
25
-
return "", err
20
+
return "", fmt.Errorf("handle could not be resolved via txt: %w", err)
26
21
}
27
22
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
23
+
for _, rec := range recs {
24
+
if strings.HasPrefix(rec, "did=") {
25
+
maybeDid := strings.Split(rec, "did=")[1]
26
+
if _, err := syntax.ParseDID(maybeDid); err == nil {
27
+
return maybeDid, nil
34
28
}
35
29
}
36
-
} else {
37
-
fmt.Printf("erorr getting txt records: %v\n", err)
38
30
}
39
31
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
-
}
32
+
return "", fmt.Errorf("handle could not be resolved via txt: no record found")
33
+
}
50
34
51
-
resp, err := http.DefaultClient.Do(req)
52
-
if err != nil {
53
-
return "", nil
54
-
}
55
-
defer resp.Body.Close()
35
+
func ResolveHandleFromWellKnown(ctx context.Context, cli *http.Client, handle string) (string, error) {
36
+
ustr := fmt.Sprintf("https://%s/.well=known/atproto-did", handle)
37
+
req, err := http.NewRequestWithContext(
38
+
ctx,
39
+
"GET",
40
+
ustr,
41
+
nil,
42
+
)
43
+
if err != nil {
44
+
return "", fmt.Errorf("handle could not be resolved via web: %w", err)
45
+
}
56
46
57
-
if resp.StatusCode != http.StatusOK {
58
-
io.Copy(io.Discard, resp.Body)
59
-
return "", fmt.Errorf("unable to resolve handle")
60
-
}
47
+
resp, err := cli.Do(req)
48
+
if err != nil {
49
+
return "", fmt.Errorf("handle could not be resolved via web: %w", err)
50
+
}
51
+
defer resp.Body.Close()
61
52
62
-
b, err := io.ReadAll(resp.Body)
63
-
if err != nil {
64
-
return "", err
65
-
}
53
+
b, err := io.ReadAll(resp.Body)
54
+
if err != nil {
55
+
return "", fmt.Errorf("handle could not be resolved via web: %w", err)
56
+
}
66
57
67
-
maybeDid := string(b)
58
+
if resp.StatusCode != http.StatusOK {
59
+
return "", fmt.Errorf("handle could not be resolved via web: invalid status code %d", resp.StatusCode)
60
+
}
68
61
69
-
if _, err := syntax.ParseDID(maybeDid); err != nil {
70
-
return "", fmt.Errorf("unable to resolve handle")
71
-
}
62
+
maybeDid := string(b)
72
63
73
-
did = maybeDid
64
+
if _, err := syntax.ParseDID(maybeDid); err != nil {
65
+
return "", fmt.Errorf("handle could not be resolved via web: invalid did in document")
74
66
}
75
67
76
-
return did, nil
68
+
return maybeDid, nil
77
69
}
78
70
79
-
func FetchDidDoc(ctx context.Context, cli *http.Client, did string) (*DidDoc, error) {
71
+
func ResolveHandle(ctx context.Context, cli *http.Client, handle string) (string, error) {
80
72
if cli == nil {
81
73
cli = util.RobustHTTPClient()
82
74
}
83
75
84
-
var ustr string
76
+
_, err := syntax.ParseHandle(handle)
77
+
if err != nil {
78
+
return "", err
79
+
}
80
+
81
+
if maybeDidFromTxt, err := ResolveHandleFromTXT(ctx, handle); err == nil {
82
+
return maybeDidFromTxt, nil
83
+
}
84
+
85
+
if maybeDidFromWeb, err := ResolveHandleFromWellKnown(ctx, cli, handle); err == nil {
86
+
return maybeDidFromWeb, nil
87
+
}
88
+
89
+
return "", fmt.Errorf("handle could not be resolved")
90
+
}
91
+
92
+
func DidToDocUrl(did string) (string, error) {
85
93
if strings.HasPrefix(did, "did:plc:") {
86
-
ustr = fmt.Sprintf("https://plc.directory/%s", did)
94
+
return fmt.Sprintf("https://plc.directory/%s", did), nil
87
95
} else if strings.HasPrefix(did, "did:web:") {
88
-
ustr = fmt.Sprintf("https://%s/.well-known/did.json", strings.TrimPrefix(did, "did:web:"))
96
+
return fmt.Sprintf("https://%s/.well-known/did.json", strings.TrimPrefix(did, "did:web:")), nil
89
97
} else {
90
-
return nil, fmt.Errorf("did was not a supported did type")
98
+
return "", fmt.Errorf("did was not a supported did type")
99
+
}
100
+
}
101
+
102
+
func FetchDidDoc(ctx context.Context, cli *http.Client, did string) (*DidDoc, error) {
103
+
if cli == nil {
104
+
cli = util.RobustHTTPClient()
105
+
}
106
+
107
+
ustr, err := DidToDocUrl(did)
108
+
if err != nil {
109
+
return nil, err
91
110
}
92
111
93
112
req, err := http.NewRequestWithContext(ctx, "GET", ustr, nil)
···
95
114
return nil, err
96
115
}
97
116
98
-
resp, err := http.DefaultClient.Do(req)
117
+
resp, err := cli.Do(req)
99
118
if err != nil {
100
119
return nil, err
101
120
}
···
103
122
104
123
if resp.StatusCode != 200 {
105
124
io.Copy(io.Discard, resp.Body)
106
-
return nil, fmt.Errorf("could not find identity in plc registry")
125
+
return nil, fmt.Errorf("unable to find did doc at url. did: %s. url: %s", did, ustr)
107
126
}
108
127
109
128
var diddoc DidDoc
···
127
146
return nil, err
128
147
}
129
148
130
-
resp, err := http.DefaultClient.Do(req)
149
+
resp, err := cli.Do(req)
131
150
if err != nil {
132
151
return nil, err
133
152
}
+16
-5
identity/passport.go
+16
-5
identity/passport.go
···
19
19
type Passport struct {
20
20
h *http.Client
21
21
bc BackingCache
22
-
lk sync.Mutex
22
+
mu sync.RWMutex
23
23
}
24
24
25
25
func NewPassport(h *http.Client, bc BackingCache) *Passport {
···
30
30
return &Passport{
31
31
h: h,
32
32
bc: bc,
33
-
lk: sync.Mutex{},
34
33
}
35
34
}
36
35
···
38
37
skipCache, _ := ctx.Value("skip-cache").(bool)
39
38
40
39
if !skipCache {
40
+
p.mu.RLock()
41
41
cached, ok := p.bc.GetDoc(did)
42
+
p.mu.RUnlock()
43
+
42
44
if ok {
43
45
return cached, nil
44
46
}
45
47
}
46
48
47
-
p.lk.Lock() // this is pretty pathetic, and i should rethink this. but for now, fuck it
48
-
defer p.lk.Unlock()
49
-
49
+
// TODO: should coalesce requests here
50
50
doc, err := FetchDidDoc(ctx, p.h, did)
51
51
if err != nil {
52
52
return nil, err
53
53
}
54
54
55
+
p.mu.Lock()
55
56
p.bc.PutDoc(did, doc)
57
+
p.mu.Unlock()
56
58
57
59
return doc, nil
58
60
}
···
61
63
skipCache, _ := ctx.Value("skip-cache").(bool)
62
64
63
65
if !skipCache {
66
+
p.mu.RLock()
64
67
cached, ok := p.bc.GetDid(handle)
68
+
p.mu.RUnlock()
69
+
65
70
if ok {
66
71
return cached, nil
67
72
}
···
72
77
return "", err
73
78
}
74
79
80
+
p.mu.Lock()
75
81
p.bc.PutDid(handle, did)
82
+
p.mu.Unlock()
76
83
77
84
return did, nil
78
85
}
79
86
80
87
func (p *Passport) BustDoc(ctx context.Context, did string) error {
88
+
p.mu.Lock()
89
+
defer p.mu.Unlock()
81
90
return p.bc.BustDoc(did)
82
91
}
83
92
84
93
func (p *Passport) BustDid(ctx context.Context, handle string) error {
94
+
p.mu.Lock()
95
+
defer p.mu.Unlock()
85
96
return p.bc.BustDid(handle)
86
97
}