···11+package identity
22+33+type DIDDocument struct {
44+ DID syntax.DID `json:"id"`
55+ AlsoKnownAs []string `json:"alsoKnownAs,omitempty"`
66+ VerificationMethod []DocVerificationMethod `json:"alsoKnownAs,omitempty"`
77+ Service []DocService `json:"alsoKnownAs,omitempty"`
88+}
99+1010+type DocVerificationMethod struct {
1111+ ID `json:"id"`
1212+ Type `json:"type"`
1313+ Controller `json:"controller"`
1414+ PublicKeyMultibase `json:"publicKeyMultibase"`
1515+}
1616+1717+type DocService struct {
1818+ ID `json:"id"`
1919+ Type `json:"type"`
2020+ ServiceEndpoint `json:"serviceEndpoint"`
2121+}
2222+2323+var ErrDIDNotFound = error.New("DID not found")
2424+2525+// WARNING: this does *not* bi-directionally verify account metadata; it only implements direct DID-to-DID-document lookup for the supported DID methods, and parses the resulting DID Doc into an Account struct
2626+func ResolveDID(ctx context.Context, did identifier.DID) (*DIDDocument, error) {
2727+ switch did.Method() {
2828+ case "web":
2929+ panic("NOT IMPLEMENTED")
3030+ case "plc":
3131+ panic("NOT IMPLEMENTED")
3232+ default:
3333+ return nil, fmt.Errorf("DID method not supported: %s", did.Method())
3434+ }
3535+}
3636+3737+func ResolveDIDWeb(ctx context.Context, did identifier.DID) (*DIDDocument, error) {
3838+ // XXX
3939+}
4040+4141+func ResolvePLC(ctx context.Context, did identifier.DID) (*DIDDocument, error) {
4242+ // XXX
4343+}
4444+4545+func (d *DIDDocument) Account() Account {
4646+ // XXX
4747+}
4848+4949+// "Renders" a DID Document
5050+func (a *Account) DIDDocument() DIDDocument {
5151+ // XXX
5252+}
+5
atproto/identity/doc.go
···11+/* Package identity provides types and routines for resolving handles and DIDs from the network
22+33+Much of the code in this SDK is based on existing code in indigo:api/extra.go
44+*/
55+package identity
+59
atproto/identity/handle.go
···11+package identity
22+33+import (
44+ "net"
55+ "context"
66+)
77+88+var ErrHandleNotFound = error.New("handle not found")
99+1010+// Does not cross-verify, just does the handle resolution step.
1111+func ResolveHandleDNS(ctx context.Context, handle identifier.Handle) (identifier.DID, error) {
1212+1313+ res, err := net.LookupTXT("_atproto." + handle)
1414+ if err != nil {
1515+ return "", fmt.Errorf("handle DNS resolution failed: %w", err)
1616+ }
1717+1818+ for _, s := range res {
1919+ if strings.HasPrefix(s, "did=") {
2020+ parts := strings.SplitN(s, "=", 2)
2121+ did, err := syntax.ParseDID(parts[1])
2222+ if err != nil {
2323+ return "", fmt.Errorf("invalid DID in handle DNS record: %w", err)
2424+ }
2525+ return did, nil
2626+ }
2727+ }
2828+ return "", ErrHandleNotFound
2929+}
3030+3131+func ResolveHandleWellKnown(ctx context.Context, handle identifier.Handle) (identifier.DID, error) {
3232+ // NOTE: could pull a client or transport from context
3333+ c := http.DefaultClient
3434+3535+ req, err := http.NewRequest("GET", fmt.Sprintf("https://%s/.well-known/atproto-did", handle), nil)
3636+ if err != nil {
3737+ return "", err
3838+ }
3939+ req = req.WithContext(ctx)
4040+4141+ resp, err := c.Do(req)
4242+ if err != nil {
4343+ return "", fmt.Errorf("failed to resolve handle (%s) through HTTP well-known route: %s", handle, err)
4444+ }
4545+ if resp.StatusCode != 200 {
4646+ return "", fmt.Errorf("failed to resolve handle (%s) through HTTP well-known route: status=%d", handle, resp.StatusCode)
4747+ }
4848+4949+ if resp.ContentLength > 2048 {
5050+ return "", fmt.Errorf("HTTP well-known route returned too much data during handle resolution")
5151+ }
5252+5353+ b, err := io.ReadAll(io.LimitReader(resp.Body, 2048))
5454+ if err != nil {
5555+ return "", fmt.Errorf("HTTP well-known response fail to read: %w", err)
5656+ }
5757+ line := strings.TrimSpace(string(b))
5858+ return syntax.ParseDID(line)
5959+}
+65
atproto/identity/identity.go
···11+package identity
22+33+import (
44+ "fmt"
55+66+ "github.com/bluesky-social/indigo/atproto/syntax"
77+)
88+99+// API for doing account lookups by DID or handle, with bi-directional verification handled automatically.
1010+//
1111+// Handles which fail to resolve or don't match DID alsoKnownAs are an error. DIDs which resolve but the handle does not resolve back to the DID return an Account where the Handle is the special `handle.invalid` value.
1212+//
1313+// Some example implementations of this interface would be:
1414+// - naive direct resolution on every call
1515+// - API client, which just makes requests to PDS (or other remote service)
1616+// - simple in-memory caching wrapper layer to reduce network hits
1717+// - services with backing datastore to do sophisticated caching, TTL, auto-refresh, etc
1818+type AccountCatalog interface {
1919+ LookupHandle(h syntax.Handle) (*Account, error)
2020+ LookupDID(d syntax.DID) (*Account, error)
2121+}
2222+2323+// Represents an atproto identity. Could be a regular user account, or a service account (eg, feed generator)
2424+type Account struct {
2525+ // these fields are required and non-nullable
2626+ DID syntax.DID
2727+ Handle syntax.Handle
2828+2929+ // these fields are nullable
3030+ AlsoKnownAs []string
3131+ Services map[string]Service
3232+ Keys map[string]Key
3333+}
3434+3535+type Key struct {
3636+ Type string
3737+ PublicKeyMultibase string
3838+}
3939+4040+type Service struct {
4141+ Type string
4242+ URL string
4343+}
4444+4545+func (a *Account) SigningKey() *Key {
4646+ if a.Keys == nil {
4747+ return nil
4848+ }
4949+ atp := a.Keys["atproto"]
5050+ if atp == nil {
5151+ return nil
5252+ }
5353+ return &atp
5454+}
5555+5656+func (a *Account) PDS() string {
5757+ if a.Services == nil {
5858+ return nil
5959+ }
6060+ atp := a.Services["atproto_pds"]
6161+ if atp == nil {
6262+ return nil
6363+ }
6464+ return &atp
6565+}