Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
20 changes: 17 additions & 3 deletions internal/serve/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down
44 changes: 44 additions & 0 deletions internal/serve/serve_test.go
Original file line number Diff line number Diff line change
@@ -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)
})
}