From cd19739347f267d8a6ad81bbba2dbdad08bcc134 Mon Sep 17 00:00:00 2001 From: Nikita Pozdniakov Date: Mon, 17 Jul 2023 15:33:55 +0700 Subject: [PATCH 1/3] Put API v2 rate limit token to cookies --- apps/block_scout_web/config/config.exs | 3 ++- .../lib/block_scout_web/api_key_v2_router.ex | 4 +--- .../controllers/api/v2/api_key_controller.ex | 16 +++++++++++----- .../lib/block_scout_web/views/access_helper.ex | 17 +++++++++-------- 4 files changed, 23 insertions(+), 17 deletions(-) diff --git a/apps/block_scout_web/config/config.exs b/apps/block_scout_web/config/config.exs index bb5f87a1dc..e0daba023f 100644 --- a/apps/block_scout_web/config/config.exs +++ b/apps/block_scout_web/config/config.exs @@ -12,7 +12,8 @@ config :block_scout_web, cookie_domain: System.get_env("SESSION_COOKIE_DOMAIN"), # 604800 seconds, 1 week session_cookie_ttl: 60 * 60 * 24 * 7, - invalid_session_key: "invalid_session" + invalid_session_key: "invalid_session", + api_v2_temp_token_key: "api_v2_temp_client_key" config :block_scout_web, admin_panel_enabled: System.get_env("ADMIN_PANEL_ENABLED", "") == "true" 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 3a302b8a47..a1b9943f9b 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 @@ -9,8 +9,6 @@ defmodule BlockScoutWeb.APIKeyV2Router do plug(Logger, application: :api_v2) plug(:accepts, ["json"]) plug(CheckApiV2) - plug(:fetch_session) - plug(:protect_from_forgery) end scope "/", as: :api_v2 do @@ -18,6 +16,6 @@ defmodule BlockScoutWeb.APIKeyV2Router do alias BlockScoutWeb.API.V2 - get("/", V2.APIKeyController, :get_key) + post("/", V2.APIKeyController, :get_key) end end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/api_key_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/api_key_controller.ex index 9b1fc2869b..255b138723 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/api_key_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/api_key_controller.ex @@ -2,10 +2,13 @@ defmodule BlockScoutWeb.API.V2.APIKeyController do use BlockScoutWeb, :controller alias BlockScoutWeb.AccessHelper - alias Plug.Crypto + + @api_v2_temp_token_key Application.compile_env(:block_scout_web, :api_v2_temp_token_key) action_fallback(BlockScoutWeb.API.V2.FallbackController) + plug(:fetch_cookies, signed: [@api_v2_temp_token_key]) + def get_key(conn, params) do helper = Application.get_env(:block_scout_web, :captcha_helper) ttl = Application.get_env(:block_scout_web, :api_rate_limit)[:api_v2_token_ttl_seconds] @@ -14,11 +17,14 @@ defmodule BlockScoutWeb.API.V2.APIKeyController do {:recaptcha, false} <- {:recaptcha, is_nil(recaptcha_response)}, {:recaptcha, true} <- {:recaptcha, helper.recaptcha_passed?(recaptcha_response)} do conn + |> put_resp_cookie(@api_v2_temp_token_key, %{ip: AccessHelper.conn_to_ip_string(conn)}, + max_age: ttl, + sign: true, + same_site: "Lax", + domain: Application.get_env(:block_scout_web, :cookie_domain) + ) |> json(%{ - key: - Crypto.sign(conn.secret_key_base, conn.secret_key_base, %{ip: AccessHelper.conn_to_ip_string(conn)}, - max_age: ttl - ) + message: "OK" }) end end diff --git a/apps/block_scout_web/lib/block_scout_web/views/access_helper.ex b/apps/block_scout_web/lib/block_scout_web/views/access_helper.ex index d481945f60..d0fb8d0c57 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/access_helper.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/access_helper.ex @@ -11,7 +11,7 @@ defmodule BlockScoutWeb.AccessHelper do alias BlockScoutWeb.WebRouter.Helpers alias Explorer.AccessHelper alias Explorer.Account.Api.Key, as: ApiKey - alias Plug.{Conn, Crypto} + alias Plug.Conn alias RemoteIp @@ -77,7 +77,7 @@ defmodule BlockScoutWeb.AccessHelper do ip_string = conn_to_ip_string(conn) plan = get_plan(conn.query_params) - token = get_ui_v2_token(conn, conn.query_params, ip_string) + token = get_ui_v2_token(conn, ip_string) user_agent = get_user_agent(conn) @@ -167,18 +167,19 @@ defmodule BlockScoutWeb.AccessHelper do to_string(:inet_parse.ntoa(ip)) end - defp get_ui_v2_token(conn, %{"token" => token}, ip_string) do - case is_api_v2_request?(conn) && Crypto.verify(conn.secret_key_base, conn.secret_key_base, token) do - {:ok, %{ip: ^ip_string}} -> - token + defp get_ui_v2_token(conn, ip_string) do + api_v2_temp_token_key = Application.get_env(:block_scout_web, :api_v2_temp_token_key) + conn = Conn.fetch_cookies(conn, signed: [api_v2_temp_token_key]) + + case is_api_v2_request?(conn) && conn.cookies[api_v2_temp_token_key] do + %{ip: ^ip_string} -> + conn.req_cookies[api_v2_temp_token_key] _ -> nil end end - defp get_ui_v2_token(_conn, _params, _ip_string), do: nil - defp get_user_agent(conn) do case Conn.get_req_header(conn, "user-agent") do [agent] -> From f86e6b2b86f462a5c5c14a7ba97358b885d600ea Mon Sep 17 00:00:00 2001 From: Nikita Pozdniakov Date: Mon, 17 Jul 2023 15:36:38 +0700 Subject: [PATCH 2/3] Changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ae959948d..c6496657ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Features +- [#7946](https://github.com/blockscout/blockscout/pull/7946) - API v2 rate limit: Put token to cookies & change /api/v2/key method - [#7888](https://github.com/blockscout/blockscout/pull/7888) - Add token balances info to watchlist address response - [#7898](https://github.com/blockscout/blockscout/pull/7898) - Add possibility to add extra headers with JSON RPC URL - [#7836](https://github.com/blockscout/blockscout/pull/7836) - Improve unverified email flow From 01426d70a1647b7677c90f5cff5f2fa30c5d7beb Mon Sep 17 00:00:00 2001 From: Nikita Pozdniakov Date: Mon, 17 Jul 2023 22:49:15 +0700 Subject: [PATCH 3/3] Process review comments --- apps/block_scout_web/config/config.exs | 2 +- .../block_scout_web/controllers/api/v2/api_key_controller.ex | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/block_scout_web/config/config.exs b/apps/block_scout_web/config/config.exs index e0daba023f..19182c508d 100644 --- a/apps/block_scout_web/config/config.exs +++ b/apps/block_scout_web/config/config.exs @@ -13,7 +13,7 @@ config :block_scout_web, # 604800 seconds, 1 week session_cookie_ttl: 60 * 60 * 24 * 7, invalid_session_key: "invalid_session", - api_v2_temp_token_key: "api_v2_temp_client_key" + api_v2_temp_token_key: "api_v2_temp_token" config :block_scout_web, admin_panel_enabled: System.get_env("ADMIN_PANEL_ENABLED", "") == "true" diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/api_key_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/api_key_controller.ex index 255b138723..4135a6bba9 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/api_key_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/api_key_controller.ex @@ -9,6 +9,10 @@ defmodule BlockScoutWeb.API.V2.APIKeyController do plug(:fetch_cookies, signed: [@api_v2_temp_token_key]) + @doc """ + Function to handle POST requests to `/api/v2/key` endpoint. It expects body with `recaptcha_response`. And puts cookie with temporary API v2 token. Which is handled here: https://github.com/blockscout/blockscout/blob/cd19739347f267d8a6ad81bbba2dbdad08bcc134/apps/block_scout_web/lib/block_scout_web/views/access_helper.ex#L170 + """ + @spec get_key(Plug.Conn.t(), nil | map) :: {:recaptcha, any} | Plug.Conn.t() def get_key(conn, params) do helper = Application.get_env(:block_scout_web, :captcha_helper) ttl = Application.get_env(:block_scout_web, :api_rate_limit)[:api_v2_token_ttl_seconds]