Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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 lib/plausible/teams.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
session: %{"mode" => "team-management"}
)}
</.tile>
<.tile :if={@current_team_role == :owner} docs="2fa">
<.tile :if={@current_team_role == :owner} docs="2fa#require-all-team-members-to-enable-2fa">
<:title>
<a id="force-2fa">Force Two-Factor Authentication (2FA)</a>
</:title>
Expand Down
28 changes: 22 additions & 6 deletions test/plausible_web/controllers/settings_controller_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -1749,20 +1749,36 @@ 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.each(1..2, fn _ ->
assert assert_delivered_email_matches(%{
subject: "Your team now requires 2FA",
to: [{_, email}]
})

assert email in [member1.email, member2.email]
end)

assert_email_delivered_with(
# 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
Expand Down
Loading