Skip to content
Merged
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: 1 addition & 1 deletion extra/lib/plausible_web/live/verification.ex
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ defmodule PlausibleWeb.Live.Verification do

private = Map.get(socket.private.connect_info, :private, %{})

super_admin? = Plausible.Auth.is_super_admin?(current_user)
super_admin? = Plausible.Auth.super_admin?(current_user)
has_pageviews? = has_pageviews?(site)

custom_url_input? = params["custom_url"] == "true"
Expand Down
71 changes: 61 additions & 10 deletions lib/plausible/auth/auth.ex
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,33 @@ defmodule Plausible.Auth do

require Logger

if Mix.env() == :e2e_test do
@ip_rate_limit 100_000
@user_rate_limit 100_000
else
@ip_rate_limit 5
@user_rate_limit 5
case Mix.env() do
:e2e_test ->
@ip_rate_limit 100_000
@user_rate_limit 100_000
@activation_limit 100_000
@activation_ip_limit 100_000
@activation_request_limit 100_000
@totp_setup_limit 100_000
@totp_setup_ip_limit 100_000

env when env in [:test, :ce_test] ->
@ip_rate_limit 5
@user_rate_limit 5
@activation_limit 10
@totp_setup_limit 10
@activation_ip_limit 100_000
@totp_setup_ip_limit 100_000
@activation_request_limit 100_000
Comment thread
cnkk marked this conversation as resolved.

_ ->
@ip_rate_limit 5
@user_rate_limit 5
@activation_limit 10
@totp_setup_limit 10
@activation_ip_limit 2
@totp_setup_ip_limit 2
@activation_request_limit 5
end

@rate_limits %{
Expand All @@ -43,6 +64,36 @@ defmodule Plausible.Auth do
prefix: "password-change:user",
limit: 5,
interval: :timer.minutes(20)
},
activation_ip: %{
prefix: "activation:ip",
limit: @activation_ip_limit,
interval: :timer.minutes(1)
},
activation_user: %{
prefix: "activation:user",
limit: @activation_limit,
interval: :timer.minutes(5)
},
activation_request_ip: %{
prefix: "activation-request:ip",
limit: @activation_request_limit,
interval: :timer.minutes(1)
Comment thread
cnkk marked this conversation as resolved.
},
activation_request_user: %{
prefix: "activation-request:user",
limit: @activation_request_limit,
interval: :timer.minutes(10)
},
totp_setup_ip: %{
prefix: "totp-setup:ip",
limit: @totp_setup_ip_limit,
interval: :timer.minutes(1)
},
totp_setup_user: %{
prefix: "totp-setup:user",
limit: @totp_setup_limit,
interval: :timer.minutes(5)
}
}

Expand Down Expand Up @@ -181,14 +232,14 @@ defmodule Plausible.Auth do
end

on_ee do
def is_super_admin?(nil), do: false
def is_super_admin?(%Plausible.Auth.User{id: id}), do: is_super_admin?(id)
def super_admin?(nil), do: false
def super_admin?(%Plausible.Auth.User{id: id}), do: super_admin?(id)

def is_super_admin?(user_id) when is_integer(user_id) do
def super_admin?(user_id) when is_integer(user_id) do
user_id in Application.get_env(:plausible, :super_admin_user_ids)
end
else
def is_super_admin?(_), do: always(false)
def super_admin?(_), do: always(false)
end

@spec list_api_keys(Auth.User.t(), Teams.Team.t() | nil) :: [Auth.ApiKey.t()]
Expand Down
4 changes: 2 additions & 2 deletions lib/plausible/sites.ex
Original file line number Diff line number Diff line change
Expand Up @@ -459,7 +459,7 @@ defmodule Plausible.Sites do
include_consolidated? = Keyword.fetch!(opts, :include_consolidated?)

site =
if :super_admin in roles and Plausible.Auth.is_super_admin?(user.id) do
if :super_admin in roles and Plausible.Auth.super_admin?(user.id) do
get_by_domain!(domain, include_consolidated?: include_consolidated?)
else
user.id
Expand All @@ -475,7 +475,7 @@ defmodule Plausible.Sites do
roles = Keyword.fetch!(opts, :roles)
include_consolidated? = Keyword.fetch!(opts, :include_consolidated?)

if :super_admin in roles and Plausible.Auth.is_super_admin?(user.id) do
if :super_admin in roles and Plausible.Auth.super_admin?(user.id) do
get_by_domain(domain, include_consolidated?: include_consolidated?)
else
user.id
Expand Down
2 changes: 1 addition & 1 deletion lib/plausible_web/controllers/api/internal_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ defmodule PlausibleWeb.Api.InternalController do
site <- Sites.get_by_domain(domain),
true <-
Plausible.Teams.Memberships.has_editor_access?(site, user) ||
Auth.is_super_admin?(user_id),
Auth.super_admin?(user_id),
{:ok, mod} <- Map.fetch(@features, feature),
{:ok, _site} <- mod.toggle(site, user, override: false) do
json(conn, "ok")
Expand Down
51 changes: 42 additions & 9 deletions lib/plausible_web/controllers/auth_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,20 @@ defmodule PlausibleWeb.AuthController do
def activate(conn, %{"code" => code}) do
user = conn.assigns[:current_user]

with :ok <- Auth.rate_limit(:activation_ip, conn),
:ok <- Auth.rate_limit(:activation_user, user) do
do_activate(conn, user, code)
else
{:error, {:rate_limit, _}} ->
render_error(
conn,
429,
"Too many activation attempts. Wait a few minutes before trying again."
)
end
end

defp do_activate(conn, user, code) do
has_any_invitations? = Plausible.Teams.Users.has_sites?(user, include_pending?: true)
has_any_memberships? = Plausible.Teams.Users.has_sites?(user, include_pending?: false)

Expand Down Expand Up @@ -167,11 +181,20 @@ defmodule PlausibleWeb.AuthController do

def request_activation_code(conn, _params) do
user = conn.assigns.current_user
Auth.EmailVerification.issue_code(user)

conn
|> put_flash(:success, "Activation code was sent to #{user.email}")
|> redirect(to: Routes.auth_path(conn, :activate_form))
with :ok <- Auth.rate_limit(:activation_request_ip, conn),
:ok <- Auth.rate_limit(:activation_request_user, user) do
Auth.EmailVerification.issue_code(user)

conn
|> put_flash(:success, "Activation code was sent to #{user.email}")
|> redirect(to: Routes.auth_path(conn, :activate_form))
else
{:error, {:rate_limit, _}} ->
conn
|> put_flash(:error, "Too many code requests. Please wait before requesting another.")
|> redirect(to: Routes.auth_path(conn, :activate_form))
end
end

def password_reset_request_form(conn, _) do
Expand Down Expand Up @@ -396,11 +419,21 @@ defmodule PlausibleWeb.AuthController do
end

def verify_2fa_setup(conn, %{"code" => code}) do
case Auth.TOTP.enable(conn.assigns.current_user, code) do
{:ok, _, %{recovery_codes: codes}} ->
conn
|> put_flash(:success, "Two-Factor Authentication is fully enabled")
|> render("generate_2fa_recovery_codes.html", recovery_codes: codes, from_setup: true)
user = conn.assigns.current_user

with :ok <- Auth.rate_limit(:totp_setup_ip, conn),
:ok <- Auth.rate_limit(:totp_setup_user, user),
{:ok, _, %{recovery_codes: codes}} <- Auth.TOTP.enable(user, code) do
conn
|> put_flash(:success, "Two-Factor Authentication is fully enabled")
|> render("generate_2fa_recovery_codes.html", recovery_codes: codes, from_setup: true)
else
{:error, {:rate_limit, _}} ->
render_error(
conn,
429,
"Too many attempts. Wait a minute before trying again."
Comment thread
cnkk marked this conversation as resolved.
)

{:error, :invalid_code} ->
conn
Expand Down
4 changes: 2 additions & 2 deletions lib/plausible_web/controllers/stats_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ defmodule PlausibleWeb.StatsController do
consolidated_view? = Plausible.Sites.consolidated?(site)

exploration_available? =
on_ee(do: Plausible.Auth.is_super_admin?(current_user), else: false)
on_ee(do: Plausible.Auth.super_admin?(current_user), else: false)

consolidated_view_available? =
on_ee(do: Plausible.ConsolidatedView.ok_to_display?(site.team), else: false)
Expand Down Expand Up @@ -473,7 +473,7 @@ defmodule PlausibleWeb.StatsController do
flags = get_flags(current_user, shared_link.site)

exploration_available? =
on_ee(do: Plausible.Auth.is_super_admin?(current_user), else: false)
on_ee(do: Plausible.Auth.super_admin?(current_user), else: false)

limited_to_segment_id =
if Plausible.Site.SharedLink.limited_to_segment?(shared_link) do
Expand Down
6 changes: 3 additions & 3 deletions lib/plausible_web/plugs/authorize_public_api.ex
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ defmodule PlausibleWeb.Plugs.AuthorizePublicAPI do
team_role_result = Plausible.Teams.Memberships.team_role(team, api_key.user)

cond do
Auth.is_super_admin?(api_key.user) ->
Auth.super_admin?(api_key.user) ->
:pass

team_role_result == {:ok, :guest} ->
Expand Down Expand Up @@ -263,7 +263,7 @@ defmodule PlausibleWeb.Plugs.AuthorizePublicAPI do
team = Repo.preload(site, :team).team

is_member? = Plausible.Teams.Memberships.site_member?(site, api_key.user)
is_super_admin? = Auth.is_super_admin?(api_key.user_id)
is_super_admin? = Auth.super_admin?(api_key.user_id)

cond do
Plausible.Sites.consolidated?(site) && !allow_consolidated_views ->
Expand Down Expand Up @@ -291,7 +291,7 @@ defmodule PlausibleWeb.Plugs.AuthorizePublicAPI do

defp verify_team_access(api_key, team, feature) do
is_member? = Plausible.Teams.Memberships.team_member?(team, api_key.user)
is_super_admin? = Auth.is_super_admin?(api_key.user_id)
is_super_admin? = Auth.super_admin?(api_key.user_id)

cond do
is_super_admin? ->
Expand Down
2 changes: 1 addition & 1 deletion lib/plausible_web/plugs/authorize_site_access.ex
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ defmodule PlausibleWeb.Plugs.AuthorizeSiteAccess do
membership_role ->
membership_role

Plausible.Auth.is_super_admin?(current_user) ->
Plausible.Auth.super_admin?(current_user) ->
:super_admin

site.public ->
Expand Down
2 changes: 1 addition & 1 deletion lib/plausible_web/plugs/super_admin_only_plug.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ defmodule PlausibleWeb.SuperAdminOnlyPlug do
def call(conn, _opts) do
current_user = conn.assigns[:current_user]

if current_user && Plausible.Auth.is_super_admin?(current_user) do
if current_user && Plausible.Auth.super_admin?(current_user) do
conn
else
conn
Expand Down
2 changes: 1 addition & 1 deletion lib/plausible_web/templates/layout/_header.html.heex
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
<ul class="flex items-center gap-2 w-full sm:w-auto">
<li :if={
ee?() && @conn.assigns[:site] &&
Plausible.Auth.is_super_admin?(@conn.assigns[:current_user])
Plausible.Auth.super_admin?(@conn.assigns[:current_user])
}>
<.styled_link
class="text-sm font-medium"
Expand Down
20 changes: 20 additions & 0 deletions test/plausible_web/controllers/auth_controller_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,26 @@ defmodule PlausibleWeb.AuthControllerTest do

refute Repo.get_by(Auth.EmailActivationCode, user_id: user.id)
end

test "limits activation attempts to 10 per 5 minutes", %{conn: conn} do
conn = put_req_header(conn, "x-forwarded-for", "10.9.8.7")

response =
eventually(
fn ->
Enum.each(1..10, fn _ ->
post(conn, "/activate", %{code: "1111"})
end)

conn = post(conn, "/activate", %{code: "1111"})

{conn.status == 429, conn}
end,
500
)

assert html_response(response, 429) =~ "Too many activation attempts"
end
end

describe "GET /login_form" do
Expand Down
Loading