From 1a5eba85e711510809c43c3e11083d55eb486698 Mon Sep 17 00:00:00 2001 From: Adrian Gruntkowski Date: Wed, 5 Nov 2025 09:05:41 +0100 Subject: [PATCH] Reduce noise in 2FA enforce notifications and update docs link (#5869) * Do not send email notification to users who already enabled 2FA * Update docs link * Improve email notification assertion --- lib/plausible/teams.ex | 2 +- .../templates/settings/team_general.html.heex | 2 +- .../controllers/settings_controller_test.exs | 30 +++++++++++++++---- 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/lib/plausible/teams.ex b/lib/plausible/teams.ex index 853dbdc877..0d375f5a0c 100644 --- a/lib/plausible/teams.ex +++ b/lib/plausible/teams.ex @@ -334,7 +334,7 @@ defmodule Plausible.Teams do team |> Teams.Memberships.all(exclude_guests?: true) |> Enum.each(fn membership -> - if membership.user.id != user.id do + if membership.user.id != user.id and not Auth.TOTP.enabled?(membership.user) do team |> PlausibleWeb.Email.force_2fa_enabled(membership.user, user) |> Plausible.Mailer.deliver_later() diff --git a/lib/plausible_web/templates/settings/team_general.html.heex b/lib/plausible_web/templates/settings/team_general.html.heex index f084bb19a2..4d77915b79 100644 --- a/lib/plausible_web/templates/settings/team_general.html.heex +++ b/lib/plausible_web/templates/settings/team_general.html.heex @@ -43,7 +43,7 @@ session: %{"mode" => "team-management"} )} - <.tile :if={@current_team_role == :owner} docs="2fa"> + <.tile :if={@current_team_role == :owner} docs="2fa#require-all-team-members-to-enable-2fa"> <:title> Force Two-Factor Authentication (2FA) diff --git a/test/plausible_web/controllers/settings_controller_test.exs b/test/plausible_web/controllers/settings_controller_test.exs index 1e305c4be7..05963295b8 100644 --- a/test/plausible_web/controllers/settings_controller_test.exs +++ b/test/plausible_web/controllers/settings_controller_test.exs @@ -1749,20 +1749,38 @@ defmodule PlausibleWeb.SettingsControllerTest do member1 = add_member(team, role: :viewer) member2 = add_member(team, role: :owner) + + member_with_2fa = add_member(team, role: :editor) + + # enable 2FA + {:ok, member_with_2fa, _} = Plausible.Auth.TOTP.initiate(member_with_2fa) + code = NimbleTOTP.verification_code(member_with_2fa.totp_secret) + {:ok, _member_with_2fa, _} = Plausible.Auth.TOTP.enable(member_with_2fa, code) + guest = add_guest(site, role: :viewer) conn = post(conn, Routes.settings_path(conn, :enable_team_force_2fa)) assert redirected_to(conn, 302) == Routes.settings_path(conn, :team_general) - assert_email_delivered_with( - subject: "Your team now requires 2FA", - to: [nil: member1.email] - ) + # The email come in order in which they are sent. + # As the logic sending them does not force any order, + # we have to match them in order-independent way. + Enum.reduce(1..2, [member1.email, member2.email], fn _, emails -> + assert assert_delivered_email_matches(%{ + subject: "Your team now requires 2FA", + to: [{_, email}] + }) - assert_email_delivered_with( + assert email in emails + + List.delete(emails, email) + end) + + # member with 2FA already enabled is not notified + refute_email_delivered_with( subject: "Your team now requires 2FA", - to: [nil: member2.email] + to: [nil: member_with_2fa.email] ) # guests are not notified because they are not affected