4.7 KiB · text History 6280797
defmodule GitGudWeb.SecondFactorReauthTest do
use GitGudWeb.ConnCase, async: true
import Phoenix.LiveViewTest
alias GitGud.Accounts
alias GitGud.Accounts.TwoFactor
# Log in with a stale `authenticated_at` so the user is outside the
# sudo window — exactly the state that bounces them to /users/log-in.
@stale_auth DateTime.utc_now(:second) |> DateTime.add(-60, :minute)
setup %{conn: conn} do
user = GitGud.AccountsFixtures.user_fixture()
secret = TwoFactor.new_secret()
{:ok, _codes} = TwoFactor.enable(user, secret, NimbleTOTP.verification_code(secret))
user = Accounts.get_user!(user.id)
%{
conn: log_in_user(conn, user, token_authenticated_at: @stale_auth),
user: user,
secret: secret
}
end
defp opt_in(user) do
{:ok, user} = Accounts.set_second_factor_reauth(user, true)
user
end
test "settings bounces a stale session to the login page", %{conn: conn} do
assert {:error, {:redirect, %{to: "/users/log-in"}}} = live(conn, ~p"/users/settings")
end
test "login page offers the second factor only once opted in", %{conn: conn, user: user} do
{:ok, _lv, html} = live(conn, ~p"/users/log-in")
refute html =~ "reauth_second_factor_form"
opt_in(user)
{:ok, _lv, html} = live(conn, ~p"/users/log-in")
assert html =~ "reauth_second_factor_form"
assert html =~ "Confirm with code"
end
test "a valid TOTP code re-opens sudo mode", %{conn: conn, user: user, secret: secret} do
opt_in(user)
conn =
post(conn, ~p"/users/reauth/second-factor", %{
"code" => NimbleTOTP.verification_code(secret)
})
assert redirected_to(conn) == ~p"/users/settings"
# The refreshed session token carries a new `authenticated_at`, so
# the sudo-gated page now mounts.
assert {:ok, _lv, _html} = live(conn, ~p"/users/settings")
end
test "a bad TOTP code does not grant sudo mode", %{conn: conn, user: user} do
opt_in(user)
conn = post(conn, ~p"/users/reauth/second-factor", %{"code" => "000000"})
assert redirected_to(conn) == ~p"/users/log-in"
assert Phoenix.Flash.get(conn.assigns.flash, :error) =~ "didn't match"
assert {:error, {:redirect, %{to: "/users/log-in"}}} = live(conn, ~p"/users/settings")
end
test "a valid code is rejected while the flag is off", %{conn: conn, secret: secret} do
conn =
post(conn, ~p"/users/reauth/second-factor", %{
"code" => NimbleTOTP.verification_code(secret)
})
assert redirected_to(conn) == ~p"/users/log-in"
assert {:error, {:redirect, %{to: "/users/log-in"}}} = live(conn, ~p"/users/settings")
end
test "the webauthn sudo ceremony is refused while the flag is off", %{conn: conn} do
conn = post(conn, ~p"/api/webauthn/sudo/start", %{})
assert json_response(conn, 401) == %{"error" => "not_available"}
end
test "a logged-out visitor gets no second-factor shortcut", %{} do
conn = build_conn()
{:ok, _lv, html} = live(conn, ~p"/users/log-in")
refute html =~ "reauth_second_factor_form"
conn = post(conn, ~p"/users/reauth/second-factor", %{"code" => "123456"})
assert redirected_to(conn) == ~p"/users/log-in"
end
test "ssh key management is sudo-gated and returns you there", %{
conn: conn,
user: user,
secret: secret
} do
assert {:error, {:redirect, %{to: to}}} = live(conn, ~p"/users/settings/ssh-keys")
assert to == "/users/log-in?return_to=%2Fusers%2Fsettings%2Fssh-keys"
opt_in(user)
{:ok, _lv, html} = live(conn, to)
assert html =~ ~s(value="/users/settings/ssh-keys")
conn =
post(conn, ~p"/users/reauth/second-factor", %{
"code" => NimbleTOTP.verification_code(secret),
"return_to" => "/users/settings/ssh-keys"
})
assert redirected_to(conn) == ~p"/users/settings/ssh-keys"
assert {:ok, _lv, _html} = live(conn, ~p"/users/settings/ssh-keys")
end
test "an off-site return_to is discarded", %{conn: conn, user: user, secret: secret} do
opt_in(user)
conn =
post(conn, ~p"/users/reauth/second-factor", %{
"code" => NimbleTOTP.verification_code(secret),
"return_to" => "//evil.example.com/steal"
})
assert redirected_to(conn) == ~p"/users/settings"
end
test "a fresh session reaches ssh keys without re-auth", %{user: user} do
conn = log_in_user(build_conn(), user)
assert {:ok, _lv, html} = live(conn, ~p"/users/settings/ssh-keys")
assert html =~ "SSH keys"
end
test "availability tracks the user still having a factor", %{user: user} do
user = opt_in(user)
assert Accounts.second_factor_reauth_available?(user)
{:ok, _user} = TwoFactor.disable(user)
refute Accounts.second_factor_reauth_available?(Accounts.get_user!(user.id))
end
end