diff --git a/api/api.go b/api/api.go index 631019b77..1f4be41a7 100644 --- a/api/api.go +++ b/api/api.go @@ -50,6 +50,7 @@ func New( enableReqLogger bool, enableMetrics bool, logsLimit uint64, + corsAllowCredsEnabled bool, ) (http.HandlerFunc, func()) { origins := strings.Split(strings.TrimSpace(allowedOrigins), ",") for i, o := range origins { @@ -102,11 +103,27 @@ func New( } handler := handlers.CompressHandler(router) - handler = handlers.CORS( + corsOptions := []handlers.CORSOption{ handlers.AllowedOrigins(origins), handlers.AllowedHeaders([]string{"content-type", "x-genesis-id"}), handlers.ExposedHeaders([]string{"x-genesis-id", "x-thorest-ver"}), - )(handler) + } + + if corsAllowCredsEnabled { + corsOptions = append(corsOptions, handlers.AllowCredentials()) + + if len(origins) == 1 && origins[0] == "*" { + // uses the origin validator when allow credentials is enabled and the allowed origins is "*". + // browsers blocks the request when allow-origin is "*" and allow-credentials is true. + // origin validator always returns true, and the CORS handler will add $http_origin to allow-origin + // header, thus pass the browser's limit. + corsOptions = append(corsOptions, handlers.AllowedOriginValidator(func(origin string) bool { + return true + })) + } + } + + handler = handlers.CORS(corsOptions...)(handler) if enableReqLogger { handler = RequestLoggerHandler(handler, log) diff --git a/api/api_test.go b/api/api_test.go new file mode 100644 index 000000000..c4218eef2 --- /dev/null +++ b/api/api_test.go @@ -0,0 +1,76 @@ +package api + +import ( + "crypto/rand" + "github.com/stretchr/testify/assert" + "github.com/vechain/thor/v2/chain" + "github.com/vechain/thor/v2/cmd/thor/solo" + "github.com/vechain/thor/v2/genesis" + "github.com/vechain/thor/v2/logdb" + "github.com/vechain/thor/v2/muxdb" + "github.com/vechain/thor/v2/state" + "github.com/vechain/thor/v2/thor" + "github.com/vechain/thor/v2/txpool" + "net/http/httptest" + "testing" +) + +func TestCORSMiddleware(t *testing.T) { + db := muxdb.NewMem() + stater := state.NewStater(db) + gene := genesis.NewDevnet() + + b, _, _, err := gene.Build(stater) + if err != nil { + t.Fatal(err) + } + repo, _ := chain.NewRepository(db, b) + + // inject some invalid data to db + data := db.NewStore("chain.data") + var blkID thor.Bytes32 + rand.Read(blkID[:]) + data.Put(blkID[:], []byte("invalid data")) + + // get summary should fail since the block data is not rlp encoded + _, err = repo.GetBlockSummary(blkID) + assert.NotNil(t, err) + + //router := mux.NewRouter() + //acc := accounts.New(repo, stater, math.MaxUint64, thor.NoFork, solo.NewBFTEngine(repo)) + //acc.Mount(router, "/accounts") + //router.PathPrefix("/metrics").Handler(metrics.HTTPHandler()) + //router.Use(metricsMiddleware) + logdb, _ := logdb.NewMem() + herp, _ := New( + repo, + stater, + &txpool.TxPool{}, + logdb, + solo.NewBFTEngine(repo), + &solo.Communicator{}, + thor.ForkConfig{}, + "*", + 0, + 0, + false, + true, + false, + false, + false, + 1000, + true, + true) + ts := httptest.NewServer(herp) + + httpGet(t, ts.URL+"/accounts/"+thor.Address{}.String()) + + // Test CORS headers + //resp, _ := httpGet(t, ts.URL+"/accounts/0x") + //assert.Equal(t, "*", resp.Header.Get("Access-Control-Allow-Origin")) + //assert.Equal(t, "true", resp.Header.Get("Access-Control-Allow-Credentials")) + // + //resp, _= httpGet(t, ts.URL+"/accounts/"+thor.Address{}.String()) + //assert.Equal(t, "*", resp.Header.Get("Access-Control-Allow-Origin")) + //assert.Equal(t, "true", resp.Header.Get("Access-Control-Allow-Credentials")) +} diff --git a/cmd/thor/flags.go b/cmd/thor/flags.go index 69267d36d..ab3188e73 100644 --- a/cmd/thor/flags.go +++ b/cmd/thor/flags.go @@ -69,6 +69,10 @@ var ( Value: 1000, Usage: "limit the number of logs returned by /logs API", } + apiCorsAllowedCredsFlag = cli.BoolFlag{ + Name: "api-cors-allow-creds", + Usage: "sets Access-Control-Allow-Credentials:true and Access-Control-Allow-Origin:$host (if api-cors='*') in the header of the API response", + } enableAPILogsFlag = cli.BoolFlag{ Name: "enable-api-logs", Usage: "enables API requests logging", diff --git a/cmd/thor/main.go b/cmd/thor/main.go index 8eef1cf95..ff807ac27 100644 --- a/cmd/thor/main.go +++ b/cmd/thor/main.go @@ -76,6 +76,7 @@ func main() { targetGasLimitFlag, apiAddrFlag, apiCorsFlag, + apiCorsAllowedCredsFlag, apiTimeoutFlag, apiCallGasLimitFlag, apiBacktraceLimitFlag, @@ -106,6 +107,7 @@ func main() { cacheFlag, apiAddrFlag, apiCorsFlag, + apiCorsAllowedCredsFlag, apiTimeoutFlag, apiCallGasLimitFlag, apiBacktraceLimitFlag, @@ -242,6 +244,8 @@ func defaultAction(ctx *cli.Context) error { ctx.Bool(enableAPILogsFlag.Name), ctx.Bool(enableMetricsFlag.Name), ctx.Uint64(apiLogsLimitFlag.Name), + ctx.Bool(apiAllowedCredsFlag.Name), + ctx.Bool(apiAllowedOriginsFlag.Name), ) defer func() { log.Info("closing API..."); apiCloser() }() @@ -381,6 +385,8 @@ func soloAction(ctx *cli.Context) error { ctx.Bool(enableAPILogsFlag.Name), ctx.Bool(enableMetricsFlag.Name), ctx.Uint64(apiLogsLimitFlag.Name), + ctx.Bool(apiAllowedCredsFlag.Name), + ctx.Bool(apiAllowedOriginsFlag.Name), ) defer func() { log.Info("closing API..."); apiCloser() }() diff --git a/docs/usage.md b/docs/usage.md index 552bc62b9..8c6ff410f 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -157,33 +157,34 @@ To show usages of all command line options: bin/thor -h ``` -| Flag | Description | -|-----------------------------|---------------------------------------------------------------------------------------------| -| `--network` | The network to join (main\|test) or path to the genesis file | -| `--data-dir` | Directory for blockchain databases | -| `--beneficiary` | Address for block rewards | -| `--api-addr` | API service listening address (default: "localhost:8669") | -| `--api-cors` | Comma-separated list of domains from which to accept cross-origin requests to API | -| `--api-timeout` | API request timeout value in milliseconds (default: 10000) | -| `--api-call-gas-limit` | Limit contract call gas (default: 50000000) | -| `--api-backtrace-limit` | Limit the distance between 'position' and best block for subscriptions APIs (default: 1000) | -| `--api-allow-custom-tracer` | Allow custom JS tracer to be used for the tracer API | -| `--enable-api-logs` | Enables API requests logging | -| `--api-logs-limit` | Limit the number of logs returned by /logs API (default: 1000) | -| `--verbosity` | Log verbosity (0-9) (default: 3) | -| `--max-peers` | Maximum number of P2P network peers (P2P network disabled if set to 0) (default: 25) | -| `--p2p-port` | P2P network listening port (default: 11235) | -| `--nat` | Port mapping mechanism (any\|none\|upnp\|pmp\|extip:) (default: "any") | -| `--bootnode` | Comma separated list of bootnode IDs | -| `--target-gas-limit` | Target block gas limit (adaptive if set to 0) (default: 0) | -| `--pprof` | Turn on go-pprof | -| `--skip-logs` | Skip writing event\|transfer logs (/logs API will be disabled) | -| `--cache` | Megabytes of RAM allocated to trie nodes cache (default: 4096) | -| `--disable-pruner` | Disable state pruner to keep all history | -| `--enable-metrics` | Enables the metrics server | -| `--metrics-addr` | Metrics service listening address | -| `--help, -h` | Show help | -| `--version, -v` | Print the version | +| Flag | Description | +|-----------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------| +| `--network` | The network to join (main\|test) or path to the genesis file | +| `--data-dir` | Directory for blockchain databases | +| `--beneficiary` | Address for block rewards | +| `--api-addr` | API service listening address (default: "localhost:8669") | +| `--api-cors-allow-creds` | Sets Access-Control-Allow-Credentials:true and Access-Control-Allow-Origin:$host (if --api-cors='*') in the header of the API response (default: false) | +| `--api-timeout` | API request timeout value in milliseconds (default: 10000) | +| `--api-call-gas-limit` | Limit contract call gas (default: 50000000) | +| `--api-backtrace-limit` | Limit the distance between 'position' and best block for subscriptions APIs (default: 1000) | +| `--api-allow-custom-tracer` | Allow custom JS tracer to be used for the tracer API | +| `--enable-api-logs` | Enables API requests logging | +| `--api-logs-limit` | Limit the number of logs returned by /logs API (default: 1000) | +| `--api-cors` | Comma-separated list of domains from which to accept cross-origin requests to API | +| `--verbosity` | Log verbosity (0-9) (default: 3) | +| `--max-peers` | Maximum number of P2P network peers (P2P network disabled if set to 0) (default: 25) | +| `--p2p-port` | P2P network listening port (default: 11235) | +| `--nat` | Port mapping mechanism (any\|none\|upnp\|pmp\|extip:) (default: "any") | +| `--bootnode` | Comma separated list of bootnode IDs | +| `--target-gas-limit` | Target block gas limit (adaptive if set to 0) (default: 0) | +| `--pprof` | Turn on go-pprof | +| `--skip-logs` | Skip writing event\|transfer logs (/logs API will be disabled) | +| `--cache` | Megabytes of RAM allocated to trie nodes cache (default: 4096) | +| `--disable-pruner` | Disable state pruner to keep all history | +| `--enable-metrics` | Enables the metrics server | +| `--metrics-addr` | Metrics service listening address | +| `--help, -h` | Show help | +| `--version, -v` | Print the version | #### Thor Solo Flags