From 27edc65e923f97a2bdc9392ce1e23b2cd9c5b9db Mon Sep 17 00:00:00 2001 From: Jun Luo <4catcode@gmail.com> Date: Wed, 8 Jul 2026 11:00:27 +0800 Subject: [PATCH] fix(serve): allow disabling client auth with empty public keys --- README.md | 2 ++ internal/serve/serve.go | 20 +++++++++++++--- internal/serve/serve_test.go | 44 ++++++++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 3 deletions(-) create mode 100644 internal/serve/serve_test.go diff --git a/README.md b/README.md index 2fe54f718..9f7e4dc3b 100644 --- a/README.md +++ b/README.md @@ -171,6 +171,8 @@ The JWT is signed using an Ed25519 private key derived from a Stellar secret see The server can be configured to accept multiple (comma-separated) public keys through the `CLIENT_AUTH_PUBLIC_KEYS` environment variable. +If `CLIENT_AUTH_PUBLIC_KEYS` is not set or empty, request authentication is disabled. + ### JWT Claims The JWT payload field should contain the following fields: diff --git a/internal/serve/serve.go b/internal/serve/serve.go index 87b5db6a9..8324bd777 100644 --- a/internal/serve/serve.go +++ b/internal/serve/serve.go @@ -176,11 +176,10 @@ func initHandlerDeps(ctx context.Context, cfg Configs) (handlerDeps, error) { return handlerDeps{}, fmt.Errorf("creating models for Serve: %w", err) } - jwtTokenParser, err := auth.NewMultiJWTTokenParser(time.Duration(cfg.ClientAuthMaxTimeoutSeconds)*time.Second, cfg.ClientAuthPublicKeys...) + requestAuthVerifier, err := buildRequestAuthVerifier(ctx, cfg) if err != nil { - return handlerDeps{}, fmt.Errorf("instantiating multi JWT token parser: %w", err) + return handlerDeps{}, err } - requestAuthVerifier := auth.NewHTTPRequestVerifier(jwtTokenParser, int64(cfg.ClientAuthMaxBodySizeBytes)) httpClient := http.Client{Timeout: 30 * time.Second} rpcService, err := services.NewRPCService(cfg.RPCURL, cfg.NetworkPassphrase, &httpClient, m.RPC) @@ -206,6 +205,21 @@ func initHandlerDeps(ctx context.Context, cfg Configs) (handlerDeps, error) { }, nil } +// buildRequestAuthVerifier returns nil when no client auth public keys are +// configured, which makes handler() skip the authentication middleware entirely. +func buildRequestAuthVerifier(ctx context.Context, cfg Configs) (auth.HTTPRequestVerifier, error) { + if len(cfg.ClientAuthPublicKeys) == 0 { + log.Ctx(ctx).Warn("client-auth-public-keys is empty: request authentication is disabled") + return nil, nil + } + + jwtTokenParser, err := auth.NewMultiJWTTokenParser(time.Duration(cfg.ClientAuthMaxTimeoutSeconds)*time.Second, cfg.ClientAuthPublicKeys...) + if err != nil { + return nil, fmt.Errorf("instantiating multi JWT token parser: %w", err) + } + return auth.NewHTTPRequestVerifier(jwtTokenParser, int64(cfg.ClientAuthMaxBodySizeBytes)), nil +} + func handler(deps handlerDeps) http.Handler { mux := supporthttp.NewAPIMux(log.DefaultLogger) mux.NotFound(httperror.ErrorHandler{Error: httperror.NotFound}.ServeHTTP) diff --git a/internal/serve/serve_test.go b/internal/serve/serve_test.go new file mode 100644 index 000000000..eea49ec74 --- /dev/null +++ b/internal/serve/serve_test.go @@ -0,0 +1,44 @@ +package serve + +import ( + "context" + "testing" + + "github.com/stellar/go-stellar-sdk/keypair" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestBuildRequestAuthVerifier(t *testing.T) { + baseCfg := Configs{ + ClientAuthMaxTimeoutSeconds: 15, + ClientAuthMaxBodySizeBytes: 102_400, + } + + t.Run("auth disabled when no public keys are provided", func(t *testing.T) { + cfg := baseCfg + cfg.ClientAuthPublicKeys = nil + + verifier, err := buildRequestAuthVerifier(context.Background(), cfg) + require.NoError(t, err) + assert.Nil(t, verifier) + }) + + t.Run("auth enabled when public keys are provided", func(t *testing.T) { + cfg := baseCfg + cfg.ClientAuthPublicKeys = []string{keypair.MustRandom().Address()} + + verifier, err := buildRequestAuthVerifier(context.Background(), cfg) + require.NoError(t, err) + assert.NotNil(t, verifier) + }) + + t.Run("invalid public key returns error", func(t *testing.T) { + cfg := baseCfg + cfg.ClientAuthPublicKeys = []string{"not-a-stellar-public-key"} + + verifier, err := buildRequestAuthVerifier(context.Background(), cfg) + require.ErrorContains(t, err, "instantiating multi JWT token parser") + assert.Nil(t, verifier) + }) +}