From 99acd80f2e2f20fb6aeae91f89e7b7b8f26fdb90 Mon Sep 17 00:00:00 2001 From: bryan newbold Date: Sat, 22 Feb 2025 17:44:49 -0800 Subject: [PATCH 1/3] identity: clearer atid argument name --- atproto/identity/base_directory.go | 2 +- atproto/identity/cache_directory.go | 6 +++--- atproto/identity/directory.go | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/atproto/identity/base_directory.go b/atproto/identity/base_directory.go index ef6c56767..a69c05647 100644 --- a/atproto/identity/base_directory.go +++ b/atproto/identity/base_directory.go @@ -107,7 +107,7 @@ func (d *BaseDirectory) Lookup(ctx context.Context, a syntax.AtIdentifier) (*Ide return nil, fmt.Errorf("at-identifier neither a Handle nor a DID") } -func (d *BaseDirectory) Purge(ctx context.Context, a syntax.AtIdentifier) error { +func (d *BaseDirectory) Purge(ctx context.Context, atid syntax.AtIdentifier) error { // BaseDirectory itself does not implement caching return nil } diff --git a/atproto/identity/cache_directory.go b/atproto/identity/cache_directory.go index 95c5629cb..0ecc6c292 100644 --- a/atproto/identity/cache_directory.go +++ b/atproto/identity/cache_directory.go @@ -250,14 +250,14 @@ func (d *CacheDirectory) Lookup(ctx context.Context, a syntax.AtIdentifier) (*Id return nil, fmt.Errorf("at-identifier neither a Handle nor a DID") } -func (d *CacheDirectory) Purge(ctx context.Context, a syntax.AtIdentifier) error { - handle, err := a.AsHandle() +func (d *CacheDirectory) Purge(ctx context.Context, atid syntax.AtIdentifier) error { + handle, err := atid.AsHandle() if nil == err { // if not an error, is a handle handle = handle.Normalize() d.handleCache.Remove(handle) return nil } - did, err := a.AsDID() + did, err := atid.AsDID() if nil == err { // if not an error, is a DID d.identityCache.Remove(did) return nil diff --git a/atproto/identity/directory.go b/atproto/identity/directory.go index 8af6c4a71..b5333d2cf 100644 --- a/atproto/identity/directory.go +++ b/atproto/identity/directory.go @@ -27,7 +27,7 @@ type Directory interface { Lookup(ctx context.Context, atid syntax.AtIdentifier) (*Identity, error) // Flushes any cache of the indicated identifier. If directory is not using caching, can ignore this. - Purge(ctx context.Context, i syntax.AtIdentifier) error + Purge(ctx context.Context, atid syntax.AtIdentifier) error } // 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. From 20824735e7c3049d8a20850ab129ac256723463f Mon Sep 17 00:00:00 2001 From: bryan newbold Date: Sat, 22 Feb 2025 17:48:01 -0800 Subject: [PATCH 2/3] identity: ensure full body is read/closed in more situations --- atproto/identity/did.go | 4 ++++ atproto/identity/handle.go | 10 ++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/atproto/identity/did.go b/atproto/identity/did.go index 36c094933..68fcaa076 100644 --- a/atproto/identity/did.go +++ b/atproto/identity/did.go @@ -112,9 +112,11 @@ func (d *BaseDirectory) resolveDIDWeb(ctx context.Context, did syntax.DID) ([]by } defer resp.Body.Close() if resp.StatusCode == http.StatusNotFound { + io.Copy(io.Discard, resp.Body) return nil, fmt.Errorf("%w: did:web HTTP status 404", ErrDIDNotFound) } if resp.StatusCode != http.StatusOK { + io.Copy(io.Discard, resp.Body) return nil, fmt.Errorf("%w: did:web HTTP status %d", ErrDIDResolutionFailed, resp.StatusCode) } @@ -147,9 +149,11 @@ func (d *BaseDirectory) resolveDIDPLC(ctx context.Context, did syntax.DID) ([]by } defer resp.Body.Close() if resp.StatusCode == http.StatusNotFound { + io.Copy(io.Discard, resp.Body) return nil, fmt.Errorf("%w: PLC directory 404", ErrDIDNotFound) } if resp.StatusCode != http.StatusOK { + io.Copy(io.Discard, resp.Body) return nil, fmt.Errorf("%w: PLC directory status %d", ErrDIDResolutionFailed, resp.StatusCode) } diff --git a/atproto/identity/handle.go b/atproto/identity/handle.go index 72ac70969..02107c40e 100644 --- a/atproto/identity/handle.go +++ b/atproto/identity/handle.go @@ -144,17 +144,19 @@ func (d *BaseDirectory) ResolveHandleWellKnown(ctx context.Context, handle synta return "", fmt.Errorf("%w: HTTP well-known request error: %w", ErrHandleResolutionFailed, err) } defer resp.Body.Close() + if resp.ContentLength > 2048 { + // NOTE: intentionally not draining body + return "", fmt.Errorf("%w: HTTP well-known body too large for %s", ErrHandleResolutionFailed, handle) + } if resp.StatusCode == http.StatusNotFound { + io.Copy(io.Discard, resp.Body) return "", fmt.Errorf("%w: HTTP 404 for %s", ErrHandleNotFound, handle) } if resp.StatusCode != http.StatusOK { + io.Copy(io.Discard, resp.Body) return "", fmt.Errorf("%w: HTTP well-known status %d for %s", ErrHandleResolutionFailed, resp.StatusCode, handle) } - if resp.ContentLength > 2048 { - return "", fmt.Errorf("%w: HTTP well-known body too large for %s", ErrHandleResolutionFailed, handle) - } - b, err := io.ReadAll(io.LimitReader(resp.Body, 2048)) if err != nil { return "", fmt.Errorf("%w: HTTP well-known body read for %s: %w", ErrHandleResolutionFailed, handle, err) From e952d24bde516bf8f902f9e23014c0ef8209ad47 Mon Sep 17 00:00:00 2001 From: bryan newbold Date: Sat, 22 Feb 2025 17:54:40 -0800 Subject: [PATCH 3/3] identity: add configurable User-Agent header in requests --- atproto/identity/base_directory.go | 2 ++ atproto/identity/did.go | 8 ++++++++ atproto/identity/directory.go | 3 +++ atproto/identity/handle.go | 3 +++ 4 files changed, 16 insertions(+) diff --git a/atproto/identity/base_directory.go b/atproto/identity/base_directory.go index a69c05647..42d59bb7f 100644 --- a/atproto/identity/base_directory.go +++ b/atproto/identity/base_directory.go @@ -33,6 +33,8 @@ type BaseDirectory struct { // // The intended use-case for this flag is as an optimization for services which do not care about handles, but still want to use the `Directory` interface (instead of `ResolveDID`). For example, relay implementations, or services validating inter-service auth requests. SkipHandleVerification bool + // User-Agent header for HTTP requests. Optional (ignored if empty string). + UserAgent string } var _ Directory = (*BaseDirectory)(nil) diff --git a/atproto/identity/did.go b/atproto/identity/did.go index 68fcaa076..982fd4925 100644 --- a/atproto/identity/did.go +++ b/atproto/identity/did.go @@ -98,6 +98,10 @@ func (d *BaseDirectory) resolveDIDWeb(ctx context.Context, did syntax.DID) ([]by if err != nil { return nil, fmt.Errorf("constructing HTTP request for did:web resolution: %w", err) } + if d.UserAgent != "" { + req.Header.Set("User-Agent", d.UserAgent) + } + resp, err := d.HTTPClient.Do(req) // look for NXDOMAIN @@ -143,6 +147,10 @@ func (d *BaseDirectory) resolveDIDPLC(ctx context.Context, did syntax.DID) ([]by if err != nil { return nil, fmt.Errorf("constructing HTTP request for did:plc resolution: %w", err) } + if d.UserAgent != "" { + req.Header.Set("User-Agent", d.UserAgent) + } + resp, err := d.HTTPClient.Do(req) if err != nil { return nil, fmt.Errorf("%w: PLC directory lookup: %w", ErrDIDResolutionFailed, err) diff --git a/atproto/identity/directory.go b/atproto/identity/directory.go index b5333d2cf..e9e0992da 100644 --- a/atproto/identity/directory.go +++ b/atproto/identity/directory.go @@ -8,6 +8,8 @@ import ( "time" "github.com/bluesky-social/indigo/atproto/syntax" + + "github.com/carlmjohnson/versioninfo" ) // Ergonomic interface for atproto identity lookup, by DID or handle. @@ -80,6 +82,7 @@ func DefaultDirectory() Directory { TryAuthoritativeDNS: true, // primary Bluesky PDS instance only supports HTTP resolution method SkipDNSDomainSuffixes: []string{".bsky.social"}, + UserAgent: "indigo-identity/" + versioninfo.Short(), } cached := NewCacheDirectory(&base, 250_000, time.Hour*24, time.Minute*2, time.Minute*5) return &cached diff --git a/atproto/identity/handle.go b/atproto/identity/handle.go index 02107c40e..6ebd7dd4f 100644 --- a/atproto/identity/handle.go +++ b/atproto/identity/handle.go @@ -131,6 +131,9 @@ func (d *BaseDirectory) ResolveHandleWellKnown(ctx context.Context, handle synta if err != nil { return "", fmt.Errorf("constructing HTTP request for handle resolution: %w", err) } + if d.UserAgent != "" { + req.Header.Set("User-Agent", d.UserAgent) + } resp, err := d.HTTPClient.Do(req) if err != nil {