From 420429019d3bf7dd543c07e728af2c5ead56416f Mon Sep 17 00:00:00 2001
From: Eli Mallon <eli@iame.li>
Date: Fri, 30 Jan 2026 13:41:13 +0100
Subject: [PATCH] add ability to pass in *http.Client

---
 pkg/oatproxy/oatproxy.go          |  9 +++++++-
 pkg/oatproxy/oauth_1_par.go       |  2 +-
 pkg/oatproxy/oauth_2_authorize.go |  6 ++---
 pkg/oatproxy/oauth_3_return.go    |  2 +-
 pkg/oatproxy/resolution.go        | 38 +++++++++++++++++++------------
 5 files changed, 37 insertions(+), 20 deletions(-)

diff --git a/pkg/oatproxy/oatproxy.go b/pkg/oatproxy/oatproxy.go
index 96d4d70..5a59128 100644
--- a/pkg/oatproxy/oatproxy.go
+++ b/pkg/oatproxy/oatproxy.go
@@ -23,6 +23,7 @@ type OATProxy struct {
 	clientMetadata      *OAuthClientMetadata
 	defaultPDS          string
 	public              bool
+	httpClient          *http.Client
 }
 
 type Config struct {
@@ -40,6 +41,7 @@ type Config struct {
 	ClientMetadata *OAuthClientMetadata
 	DefaultPDS     string
 	Public         bool
+	HTTPClient     *http.Client
 }
 
 func New(conf *Config) *OATProxy {
@@ -62,6 +64,11 @@ func New(conf *Config) *OATProxy {
 		defaultPDS:          conf.DefaultPDS,
 		public:              conf.Public,
 	}
+	if conf.HTTPClient != nil {
+		o.httpClient = conf.HTTPClient
+	} else {
+		o.httpClient = http.DefaultClient
+	}
 	if conf.Lock != nil {
 		o.lock = conf.Lock
 	} else {
@@ -77,7 +84,7 @@ func New(conf *Config) *OATProxy {
 
 	o.Echo.GET("/.well-known/oauth-authorization-server", o.HandleOAuthAuthorizationServer)
 	o.Echo.GET("/.well-known/oauth-protected-resource", o.HandleOAuthProtectedResource)
-	o.Echo.GET("/xrpc/com.atproto.identity.resolveHandle", HandleComAtprotoIdentityResolveHandle)
+	o.Echo.GET("/xrpc/com.atproto.identity.resolveHandle", o.HandleComAtprotoIdentityResolveHandle)
 	o.Echo.POST("/oauth/par", o.HandleOAuthPAR)
 	o.Echo.GET("/oauth/authorize", o.HandleOAuthAuthorize)
 	o.Echo.GET("/oauth/return", o.HandleOAuthReturn)
diff --git a/pkg/oatproxy/oauth_1_par.go b/pkg/oatproxy/oauth_1_par.go
index 4c91521..3b381b2 100644
--- a/pkg/oatproxy/oauth_1_par.go
+++ b/pkg/oatproxy/oauth_1_par.go
@@ -73,7 +73,7 @@ func (o *OATProxy) NewPAR(ctx context.Context, c echo.Context, par *PAR, dpopHea
 	var expectedURL *url.URL
 	var upstreamAuthServerURL string
 	if o.public {
-		_, service, httpErr := ResolveHandleAndService(ctx, par.LoginHint)
+		_, service, httpErr := ResolveHandleAndServiceWithClient(ctx, par.LoginHint, o.httpClient)
 		if httpErr != nil {
 			return nil, httpErr
 		}
diff --git a/pkg/oatproxy/oauth_2_authorize.go b/pkg/oatproxy/oauth_2_authorize.go
index a322e03..83dd22a 100644
--- a/pkg/oatproxy/oauth_2_authorize.go
+++ b/pkg/oatproxy/oauth_2_authorize.go
@@ -111,17 +111,17 @@ func (o *OATProxy) Authorize(ctx context.Context, requestURI, clientID string) (
 			service = session.Handle
 		} else {
 			var httpErr *echo.HTTPError
-			did, service, httpErr = ResolveHandleAndService(ctx, session.Handle)
+			did, service, httpErr = ResolveHandleAndServiceWithClient(ctx, session.Handle, o.httpClient)
 			if httpErr != nil {
 				return "", httpErr
 			}
-			did, err = ResolveHandle(ctx, session.Handle)
+			did, err = ResolveHandleWithClient(ctx, session.Handle, o.httpClient)
 			if err != nil {
 				return "", echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("failed to resolve handle '%s': %s", session.Handle, err))
 			}
 
 			var handle2 string
-			service, handle2, err = ResolveService(ctx, did)
+			service, handle2, err = ResolveServiceWithClient(ctx, did, o.httpClient)
 			if err != nil {
 				return "", echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("failed to resolve service for DID '%s': %s", did, err))
 			}
diff --git a/pkg/oatproxy/oauth_3_return.go b/pkg/oatproxy/oauth_3_return.go
index 206e037..99c969e 100644
--- a/pkg/oatproxy/oauth_3_return.go
+++ b/pkg/oatproxy/oauth_3_return.go
@@ -113,7 +113,7 @@ func (o *OATProxy) Return(ctx context.Context, code string, iss string, state st
 	session.DownstreamAuthorizationCode = downstreamCode
 	session.UpstreamScope = itResp.Scope
 	if session.DID == "" {
-		_, handle, err := ResolveService(ctx, itResp.Sub)
+		_, handle, err := ResolveServiceWithClient(ctx, itResp.Sub, o.httpClient)
 		if err != nil {
 			return "", echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("failed to resolve service for DID '%s': %s", itResp.Sub, err))
 		}
diff --git a/pkg/oatproxy/resolution.go b/pkg/oatproxy/resolution.go
index 1488e64..23e7037 100644
--- a/pkg/oatproxy/resolution.go
+++ b/pkg/oatproxy/resolution.go
@@ -15,8 +15,12 @@ import (
 	"go.opentelemetry.io/otel"
 )
 
+// func ResolveHandle(ctx context.Context, handle string) (string, error) {
+// 	return ResolveHandleWithClient(ctx, handle, http.DefaultClient)
+// }
+
 // mostly borrowed from github.com/streamplace/atproto-oauth-golang, MIT license
-func ResolveHandle(ctx context.Context, handle string) (string, error) {
+func ResolveHandleWithClient(ctx context.Context, handle string, client *http.Client) (string, error) {
 	var did string
 
 	_, err := syntax.ParseHandle(handle)
@@ -39,13 +43,15 @@ func ResolveHandle(ctx context.Context, handle string) (string, error) {
 			ctx,
 			"GET",
 			fmt.Sprintf("https://%s/.well-known/atproto-did", handle),
+			// "https://webhook.site/ce546544-f7ef-4880-9cbe-c2bf15ad9840",
 			nil,
 		)
+		req.Header.Del("accept-encoding")
 		if err != nil {
-			return "", err
+			return "", fmt.Errorf("unable to resolve handle: failed to create request: %s", err)
 		}
 
-		resp, err := http.DefaultClient.Do(req)
+		resp, err := client.Do(req)
 		if err != nil {
 			return "", err
 		}
@@ -53,18 +59,18 @@ func ResolveHandle(ctx context.Context, handle string) (string, error) {
 
 		if resp.StatusCode != http.StatusOK {
 			io.Copy(io.Discard, resp.Body)
-			return "", fmt.Errorf("unable to resolve handle")
+			return "", fmt.Errorf("unable to resolve handle, got http status %d", resp.StatusCode)
 		}
 
 		b, err := io.ReadAll(resp.Body)
 		if err != nil {
-			return "", err
+			return "", fmt.Errorf("unable to resolve handle: failed to read response body: %s", err)
 		}
 
 		maybeDid := strings.TrimSpace(string(b))
 
 		if _, err := syntax.ParseDID(maybeDid); err != nil {
-			return "", fmt.Errorf("unable to resolve handle")
+			return "", fmt.Errorf("unable to resolve handle: failed to parse DID: %s", err)
 		}
 
 		did = maybeDid
@@ -73,7 +79,11 @@ func ResolveHandle(ctx context.Context, handle string) (string, error) {
 	return did, nil
 }
 
-func ResolveService(ctx context.Context, did string) (string, string, error) {
+// func ResolveService(ctx context.Context, did string) (string, string, error) {
+// 	return ResolveServiceWithClient(ctx, did, http.DefaultClient)
+// }
+
+func ResolveServiceWithClient(ctx context.Context, did string, client *http.Client) (string, string, error) {
 	type Identity struct {
 		AlsoKnownAs []string `json:"alsoKnownAs"`
 		Service     []struct {
@@ -137,14 +147,14 @@ func ResolveService(ctx context.Context, did string) (string, string, error) {
 }
 
 // returns did, service
-func ResolveHandleAndService(ctx context.Context, handle string) (string, string, *echo.HTTPError) {
-	did, err := ResolveHandle(ctx, handle)
+func ResolveHandleAndServiceWithClient(ctx context.Context, handle string, client *http.Client) (string, string, *echo.HTTPError) {
+	did, err := ResolveHandleWithClient(ctx, handle, client)
 	if err != nil {
 		return "", "", echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("failed to resolve handle '%s': %s", handle, err))
 	}
 
 	var handle2 string
-	service, handle2, err := ResolveService(ctx, did)
+	service, handle2, err := ResolveServiceWithClient(ctx, did, client)
 	if err != nil {
 		return "", "", echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("failed to resolve service for DID '%s': %s", did, err))
 	}
@@ -154,22 +164,22 @@ func ResolveHandleAndService(ctx context.Context, handle string) (string, string
 	return did, service, nil
 }
 
-func HandleComAtprotoIdentityResolveHandle(c echo.Context) error {
+func (o *OATProxy) HandleComAtprotoIdentityResolveHandle(c echo.Context) error {
 	ctx, span := otel.Tracer("server").Start(c.Request().Context(), "HandleComAtprotoIdentityResolveHandle")
 	defer span.End()
 	handle := c.QueryParam("handle")
 	var out *comatprototypes.IdentityResolveHandle_Output
 	var handleErr error
 	// func (s *Server) handleComAtprotoIdentityResolveHandle(ctx context.Context,handle string) (*comatprototypes.IdentityResolveHandle_Output, error)
-	out, handleErr = handleComAtprotoIdentityResolveHandle(ctx, handle)
+	out, handleErr = handleComAtprotoIdentityResolveHandle(ctx, handle, o.httpClient)
 	if handleErr != nil {
 		return handleErr
 	}
 	return c.JSON(200, out)
 }
 
-func handleComAtprotoIdentityResolveHandle(ctx context.Context, handle string) (*comatprototypes.IdentityResolveHandle_Output, error) {
-	did, err := ResolveHandle(ctx, handle)
+func handleComAtprotoIdentityResolveHandle(ctx context.Context, handle string, client *http.Client) (*comatprototypes.IdentityResolveHandle_Output, error) {
+	did, err := ResolveHandleWithClient(ctx, handle, client)
 	if err != nil {
 		return nil, err
 	}
