1package identity
2
3import (
4 "context"
5 "errors"
6 "net"
7 "net/http"
8 "time"
9
10 "github.com/bluesky-social/indigo/atproto/syntax"
11
12 "github.com/carlmjohnson/versioninfo"
13)
14
15// Ergonomic interface for atproto identity lookup, by DID or handle.
16//
17// The "Lookup" methods resolve identities (handle and DID), and return results in a compact, opinionated struct (`Identity`). They do bi-directional handle/DID verification by default. Clients and services should use these methods by default, instead of resolving handles or DIDs separately.
18//
19// Looking up a handle which fails to resolve, or don't match DID alsoKnownAs, returns an error. When looking up a DID, if the handle does not resolve back to the DID, the lookup succeeds and returns an `Identity` where the Handle is the special `handle.invalid` value.
20//
21// Some example implementations of this interface could be:
22// - basic direct resolution on every call
23// - local in-memory caching layer to reduce network hits
24// - API client, which just makes requests to PDS (or other remote service)
25// - client for shared network cache (eg, Redis)
26type Directory interface {
27 LookupHandle(ctx context.Context, handle syntax.Handle) (*Identity, error)
28 LookupDID(ctx context.Context, did syntax.DID) (*Identity, error)
29 Lookup(ctx context.Context, atid syntax.AtIdentifier) (*Identity, error)
30
31 // Flushes any cache of the indicated identifier. If directory is not using caching, can ignore this.
32 Purge(ctx context.Context, atid syntax.AtIdentifier) error
33}
34
35// Indicates that handle resolution failed. A wrapped error may provide more context. This is only returned when looking up a handle, not when looking up a DID.
36var ErrHandleResolutionFailed = errors.New("handle resolution failed")
37
38// Indicates that resolution process completed successfully, but handle does not exist. This is only returned when looking up a handle, not when looking up a DID.
39var ErrHandleNotFound = errors.New("handle not found")
40
41// Indicates that resolution process completed successfully, handle mapped to a different DID. This is only returned when looking up a handle, not when looking up a DID.
42var ErrHandleMismatch = errors.New("handle/DID mismatch")
43
44// Indicates that DID document did not include any handle ("alsoKnownAs"). This is only returned when looking up a handle, not when looking up a DID.
45var ErrHandleNotDeclared = errors.New("DID document did not declare a handle")
46
47// Handle top-level domain (TLD) is one of the special "Reserved" suffixes, and not allowed for atproto use
48var ErrHandleReservedTLD = errors.New("handle top-level domain is disallowed")
49
50// Indicates that resolution process completed successfully, but the DID does not exist.
51var ErrDIDNotFound = errors.New("DID not found")
52
53// Indicates that DID resolution process failed. A wrapped error may provide more context.
54var ErrDIDResolutionFailed = errors.New("DID resolution failed")
55
56// Indicates that DID document did not include a public key with the specified ID
57var ErrKeyNotDeclared = errors.New("DID document did not declare a relevant public key")
58
59// Handle was invalid, in a situation where a valid handle is required.
60var ErrInvalidHandle = errors.New("invalid handle")
61
62var DefaultPLCURL = "https://plc.directory"
63
64// Returns a reasonable Directory implementation for applications
65func DefaultDirectory() Directory {
66 base := BaseDirectory{
67 PLCURL: DefaultPLCURL,
68 HTTPClient: http.Client{
69 Timeout: time.Second * 10,
70 Transport: &http.Transport{
71 // would want this around 100ms for services doing lots of handle resolution. Impacts PLC connections as well, but not too bad.
72 IdleConnTimeout: time.Millisecond * 1000,
73 MaxIdleConns: 100,
74 },
75 },
76 Resolver: net.Resolver{
77 Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
78 d := net.Dialer{Timeout: time.Second * 3}
79 return d.DialContext(ctx, network, address)
80 },
81 },
82 TryAuthoritativeDNS: true,
83 // primary Bluesky PDS instance only supports HTTP resolution method
84 SkipDNSDomainSuffixes: []string{".bsky.social"},
85 UserAgent: "indigo-identity/" + versioninfo.Short(),
86 }
87 cached := NewCacheDirectory(&base, 250_000, time.Hour*24, time.Minute*2, time.Minute*5)
88 return &cached
89}