Rate limit e-mail changes (#3667)

This commit is contained in:
hq1 2024-01-04 14:34:57 +01:00 committed by GitHub
parent 5c8f39ac4c
commit 9cb44291f7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 51 additions and 9 deletions

View File

@ -300,6 +300,8 @@ defmodule PlausibleWeb.AuthController do
@login_interval 60_000
@login_limit 5
@email_change_limit 2
@email_change_interval :timer.hours(1)
defp check_ip_rate_limit(conn) do
ip_address = PlausibleWeb.RemoteIp.get(conn)
@ -550,20 +552,42 @@ defmodule PlausibleWeb.AuthController do
def update_email(conn, %{"user" => user_params}) do
user = conn.assigns.current_user
changes = Auth.User.email_changeset(user, user_params)
case Repo.update(changes) do
{:ok, user} ->
if user.email_verified do
handle_email_updated(conn)
else
Auth.EmailVerification.issue_code(user)
redirect(conn, to: Routes.auth_path(conn, :activate_form))
case RateLimit.check_rate(
"email-change:user:#{user.id}",
@email_change_interval,
@email_change_limit
) do
{:allow, _} ->
changes = Auth.User.email_changeset(user, user_params)
case Repo.update(changes) do
{:ok, user} ->
if user.email_verified do
handle_email_updated(conn)
else
Auth.EmailVerification.issue_code(user)
redirect(conn, to: Routes.auth_path(conn, :activate_form))
end
{:error, changeset} ->
settings_changeset = Auth.User.settings_changeset(user)
render_settings(conn,
settings_changeset: settings_changeset,
email_changeset: changeset
)
end
{:error, changeset} ->
{:deny, _} ->
settings_changeset = Auth.User.settings_changeset(user)
{:error, changeset} =
user
|> Auth.User.email_changeset(user_params)
|> Ecto.Changeset.add_error(:email, "too many requests, try again in an hour")
|> Ecto.Changeset.apply_action(:validate)
render_settings(conn,
settings_changeset: settings_changeset,
email_changeset: changeset

View File

@ -1193,6 +1193,24 @@ defmodule PlausibleWeb.AuthControllerTest do
assert subject =~ "is your Plausible email verification code"
end
test "renders an error on third change attempt (allows 2 per hour)", %{conn: conn, user: user} do
payload = %{
"user" => %{"email" => "new" <> user.email, "password" => "badpass"}
}
resp1 = conn |> put("/settings/email", payload) |> html_response(200)
assert resp1 =~ "is invalid"
refute resp1 =~ "too many requests, try again in an hour"
resp2 = conn |> put("/settings/email", payload) |> html_response(200)
assert resp2 =~ "is invalid"
refute resp2 =~ "too many requests, try again in an hour"
resp3 = conn |> put("/settings/email", payload) |> html_response(200)
assert resp3 =~ "is invalid"
assert resp3 =~ "too many requests, try again in an hour"
end
test "renders form with error on no fields filled", %{conn: conn} do
conn = put(conn, "/settings/email", %{"user" => %{}})