diff --git a/apps/block_scout_web/lib/block_scout_web/admin_router.ex b/apps/block_scout_web/lib/block_scout_web/admin_router.ex index 7a1c328ba3..213d4abff4 100644 --- a/apps/block_scout_web/lib/block_scout_web/admin_router.ex +++ b/apps/block_scout_web/lib/block_scout_web/admin_router.ex @@ -9,6 +9,15 @@ defmodule BlockScoutWeb.AdminRouter do alias BlockScoutWeb.Plug.Admin.{CheckOwnerRegistered, RequireAdminRole} pipeline :browser do + plug( + Plug.Parsers, + parsers: [:urlencoded, :multipart, :json], + length: 10_000, + query_string_length: 5_000, + pass: ["*/*"], + json_decoder: Poison + ) + plug(:accepts, ["html"]) plug(:fetch_session) plug(:fetch_flash) diff --git a/apps/block_scout_web/lib/block_scout_web/api_key_v2_router.ex b/apps/block_scout_web/lib/block_scout_web/api_key_v2_router.ex index a1b9943f9b..29b6fe114f 100644 --- a/apps/block_scout_web/lib/block_scout_web/api_key_v2_router.ex +++ b/apps/block_scout_web/lib/block_scout_web/api_key_v2_router.ex @@ -6,6 +6,15 @@ defmodule BlockScoutWeb.APIKeyV2Router do alias BlockScoutWeb.Plug.{CheckApiV2, Logger} pipeline :api_v2 do + plug( + Plug.Parsers, + parsers: [:urlencoded, :multipart, :json], + length: 10_000, + query_string_length: 5_000, + pass: ["*/*"], + json_decoder: Poison + ) + plug(Logger, application: :api_v2) plug(:accepts, ["json"]) plug(CheckApiV2) diff --git a/apps/block_scout_web/lib/block_scout_web/api_router.ex b/apps/block_scout_web/lib/block_scout_web/api_router.ex index 475d896416..e7ad4338e2 100644 --- a/apps/block_scout_web/lib/block_scout_web/api_router.ex +++ b/apps/block_scout_web/lib/block_scout_web/api_router.ex @@ -16,22 +16,52 @@ defmodule BlockScoutWeb.ApiRouter do alias BlockScoutWeb.{AddressTransactionController, APIKeyV2Router, SmartContractsApiV2Router, UtilsApiV2Router} alias BlockScoutWeb.Plug.{CheckAccountAPI, CheckApiV2, RateLimit} + @max_query_string_length 5_000 + forward("/v2/smart-contracts", SmartContractsApiV2Router) forward("/v2/key", APIKeyV2Router) forward("/v2/utils", UtilsApiV2Router) pipeline :api do + plug( + Plug.Parsers, + parsers: [:urlencoded, :multipart, :json], + length: 20_000_000, + query_string_length: @max_query_string_length, + pass: ["*/*"], + json_decoder: Poison + ) + plug(BlockScoutWeb.Plug.Logger, application: :api) plug(:accepts, ["json"]) end pipeline :account_api do + plug( + Plug.Parsers, + parsers: [:urlencoded, :multipart, :json], + length: 100_000, + query_string_length: @max_query_string_length, + pass: ["*/*"], + json_decoder: Poison + ) + + plug(BlockScoutWeb.Plug.Logger, application: :api) + plug(:accepts, ["json"]) plug(:fetch_session) plug(:protect_from_forgery) plug(CheckAccountAPI) end pipeline :api_v2 do + plug( + Plug.Parsers, + parsers: [:urlencoded, :multipart, :json], + query_string_length: @max_query_string_length, + pass: ["*/*"], + json_decoder: Poison + ) + plug(BlockScoutWeb.Plug.Logger, application: :api_v2) plug(:accepts, ["json"]) plug(CheckApiV2) @@ -41,6 +71,14 @@ defmodule BlockScoutWeb.ApiRouter do end pipeline :api_v2_no_session do + plug( + Plug.Parsers, + parsers: [:urlencoded, :multipart, :json], + query_string_length: @max_query_string_length, + pass: ["*/*"], + json_decoder: Poison + ) + plug(BlockScoutWeb.Plug.Logger, application: :api_v2) plug(:accepts, ["json"]) plug(CheckApiV2) @@ -48,6 +86,13 @@ defmodule BlockScoutWeb.ApiRouter do end pipeline :api_v1_graphql do + plug( + Plug.Parsers, + parsers: [:json, Absinthe.Plug.Parser], + json_decoder: Poison, + body_reader: {BlockScoutWeb.GraphQL.BodyReader, :read_body, []} + ) + plug(BlockScoutWeb.Plug.Logger, application: :api) plug(:accepts, ["json"]) plug(RateLimit, graphql?: true) @@ -57,7 +102,6 @@ defmodule BlockScoutWeb.ApiRouter do alias BlockScoutWeb.API.V2 scope "/account/v2", as: :account_v2 do - pipe_through(:api) pipe_through(:account_api) get("/authenticate", AuthenticateController, :authenticate_get) diff --git a/apps/block_scout_web/lib/block_scout_web/endpoint.ex b/apps/block_scout_web/lib/block_scout_web/endpoint.ex index 10d8d99f36..734d1e9eee 100644 --- a/apps/block_scout_web/lib/block_scout_web/endpoint.ex +++ b/apps/block_scout_web/lib/block_scout_web/endpoint.ex @@ -43,15 +43,6 @@ defmodule BlockScoutWeb.Endpoint do plug(Plug.RequestId) - plug( - Plug.Parsers, - parsers: [:urlencoded, :multipart, :json], - length: 20_000_000, - query_string_length: 1_000_000, - pass: ["*/*"], - json_decoder: Poison - ) - plug(Plug.MethodOverride) plug(Plug.Head) diff --git a/apps/block_scout_web/lib/block_scout_web/graphql/body_reader.ex b/apps/block_scout_web/lib/block_scout_web/graphql/body_reader.ex new file mode 100644 index 0000000000..fe12ebfb1c --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/graphql/body_reader.ex @@ -0,0 +1,35 @@ +defmodule BlockScoutWeb.GraphQL.BodyReader do + @moduledoc """ + This module is responsible for reading the body of a graphql request and counting the number of queries in the body. + """ + + alias Plug.Conn + + @max_number_of_queries 1 + + def read_body(conn, opts) do + {:ok, body, conn} = Conn.read_body(conn, opts) + updated_conn = update_in(conn.assigns[:raw_body], &[body | &1 || []]) + + json_body = Jason.decode!(body) + + json_body_length = + if is_list(json_body) do + Enum.count(json_body) + else + 1 + end + + error = %{errors: [%{message: "Max batch size is 1"}]} + + if json_body_length > @max_number_of_queries do + {:ok, "", + updated_conn + |> Conn.put_resp_content_type("application/json") + |> Conn.resp(400, Jason.encode!(error)) + |> Conn.halt()} + else + {:ok, body, updated_conn} + end + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/router.ex b/apps/block_scout_web/lib/block_scout_web/router.ex index 86da395f13..5e29a520d4 100644 --- a/apps/block_scout_web/lib/block_scout_web/router.ex +++ b/apps/block_scout_web/lib/block_scout_web/router.ex @@ -4,11 +4,22 @@ defmodule BlockScoutWeb.Router do alias BlockScoutWeb.Plug.{GraphQL, RateLimit} alias BlockScoutWeb.{ApiRouter, WebRouter} + @max_query_string_length 5_000 + if Application.compile_env(:block_scout_web, :admin_panel_enabled) do forward("/admin", BlockScoutWeb.AdminRouter) end pipeline :browser do + plug( + Plug.Parsers, + parsers: [:urlencoded, :multipart, :json], + length: 100_000, + query_string_length: @max_query_string_length, + pass: ["*/*"], + json_decoder: Poison + ) + plug(BlockScoutWeb.Plug.Logger, application: :block_scout_web) plug(:accepts, ["html"]) plug(:fetch_session) @@ -18,11 +29,27 @@ defmodule BlockScoutWeb.Router do end pipeline :api do + plug( + Plug.Parsers, + parsers: [:urlencoded, :multipart, :json], + length: 20_000_000, + query_string_length: @max_query_string_length, + pass: ["*/*"], + json_decoder: Poison + ) + plug(BlockScoutWeb.Plug.Logger, application: :api) plug(:accepts, ["json"]) end pipeline :api_v1_graphql do + plug( + Plug.Parsers, + parsers: [:json, Absinthe.Plug.Parser], + json_decoder: Poison, + body_reader: {BlockScoutWeb.GraphQL.BodyReader, :read_body, []} + ) + plug(BlockScoutWeb.Plug.Logger, application: :api) plug(:accepts, ["json"]) plug(RateLimit, graphql?: true) diff --git a/apps/block_scout_web/lib/block_scout_web/smart_contracts_api_v2_router.ex b/apps/block_scout_web/lib/block_scout_web/smart_contracts_api_v2_router.ex index aad961f117..86ef4f49ff 100644 --- a/apps/block_scout_web/lib/block_scout_web/smart_contracts_api_v2_router.ex +++ b/apps/block_scout_web/lib/block_scout_web/smart_contracts_api_v2_router.ex @@ -7,6 +7,15 @@ defmodule BlockScoutWeb.SmartContractsApiV2Router do alias BlockScoutWeb.Plug.{CheckApiV2, RateLimit} pipeline :api_v2_no_forgery_protect do + plug( + Plug.Parsers, + parsers: [:urlencoded, :multipart, :json], + length: 20_000_000, + query_string_length: 5_000, + pass: ["*/*"], + json_decoder: Poison + ) + plug(BlockScoutWeb.Plug.Logger, application: :api_v2) plug(:accepts, ["json"]) plug(CheckApiV2) diff --git a/apps/block_scout_web/lib/block_scout_web/utils_api_v2_router.ex b/apps/block_scout_web/lib/block_scout_web/utils_api_v2_router.ex index 572133156d..b251f928d7 100644 --- a/apps/block_scout_web/lib/block_scout_web/utils_api_v2_router.ex +++ b/apps/block_scout_web/lib/block_scout_web/utils_api_v2_router.ex @@ -7,6 +7,15 @@ defmodule BlockScoutWeb.UtilsApiV2Router do alias BlockScoutWeb.Plug.{CheckApiV2, RateLimit} pipeline :api_v2_no_forgery_protect do + plug( + Plug.Parsers, + parsers: [:urlencoded, :multipart, :json], + length: 100_000, + query_string_length: 5_000, + pass: ["*/*"], + json_decoder: Poison + ) + plug(BlockScoutWeb.Plug.Logger, application: :api_v2) plug(:accepts, ["json"]) plug(CheckApiV2) diff --git a/apps/block_scout_web/lib/block_scout_web/web_router.ex b/apps/block_scout_web/lib/block_scout_web/web_router.ex index 3f3dbed7d1..2793fd8103 100644 --- a/apps/block_scout_web/lib/block_scout_web/web_router.ex +++ b/apps/block_scout_web/lib/block_scout_web/web_router.ex @@ -7,7 +7,18 @@ defmodule BlockScoutWeb.WebRouter do alias BlockScoutWeb.Plug.CheckAccountWeb + @max_query_string_length 5_000 + pipeline :browser do + plug( + Plug.Parsers, + parsers: [:urlencoded, :multipart, :json], + length: 20_000_000, + query_string_length: @max_query_string_length, + pass: ["*/*"], + json_decoder: Poison + ) + plug(BlockScoutWeb.Plug.Logger, application: :block_scout_web) plug(:accepts, ["html"]) plug(:fetch_session) @@ -18,6 +29,15 @@ defmodule BlockScoutWeb.WebRouter do end pipeline :account do + plug( + Plug.Parsers, + parsers: [:urlencoded, :multipart, :json], + length: 100_000, + query_string_length: @max_query_string_length, + pass: ["*/*"], + json_decoder: Poison + ) + plug(BlockScoutWeb.Plug.Logger, application: :block_scout_web) plug(:accepts, ["html"]) plug(:fetch_session) diff --git a/config/runtime.exs b/config/runtime.exs index 6aacf275b0..32b26f0ecc 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -117,9 +117,6 @@ config :block_scout_web, Api.GraphQL, "0x69e3923eef50eada197c3336d546936d0c994211492c9f947a24c02827568f9f" ), enabled: ConfigHelper.parse_bool_env_var("API_GRAPHQL_ENABLED", "true"), - token_limit: ConfigHelper.parse_integer_env_var("API_GRAPHQL_TOKEN_LIMIT", 1000), - # Needs to be 215 to support the schema introspection for graphiql - max_complexity: ConfigHelper.parse_integer_env_var("API_GRAPHQL_MAX_COMPLEXITY", 215), rate_limit_disabled?: ConfigHelper.parse_bool_env_var("API_GRAPHQL_RATE_LIMIT_DISABLED"), global_limit: ConfigHelper.parse_integer_env_var("API_GRAPHQL_RATE_LIMIT", default_graphql_rate_limit), limit_by_key: ConfigHelper.parse_integer_env_var("API_GRAPHQL_RATE_LIMIT_BY_KEY", default_graphql_rate_limit),