diff --git a/apps/api_web/lib/api_web/plugs/require_2factor.ex b/apps/api_web/lib/api_web/plugs/require_2factor.ex new file mode 100644 index 00000000..34392dc4 --- /dev/null +++ b/apps/api_web/lib/api_web/plugs/require_2factor.ex @@ -0,0 +1,31 @@ +defmodule ApiWeb.Plugs.Require2Factor do + @moduledoc """ + Plug enforcing a user to have 2fa enabled + """ + + # , only: [render: 3, put_view: 2] + import Phoenix.Controller + + def init(opts), do: opts + + def call(conn, _opts) do + conn + |> fetch_user() + |> authenticate(conn) + end + + defp fetch_user(conn) do + conn.assigns[:user] + end + + defp authenticate(%ApiAccounts.User{totp_enabled: true}, conn), do: conn + + defp authenticate(_, conn) do + conn + |> put_flash( + :error, + "Account does not have 2-Factor Authentication enabled. Please enable before performing administrative tasks." + ) + |> redirect(to: ApiWeb.Router.Helpers.user_path(conn, :configure_2fa)) + end +end diff --git a/apps/api_web/lib/api_web/router.ex b/apps/api_web/lib/api_web/router.ex index 2c8306d4..4a0bd3a1 100644 --- a/apps/api_web/lib/api_web/router.ex +++ b/apps/api_web/lib/api_web/router.ex @@ -60,6 +60,7 @@ defmodule ApiWeb.Router do pipeline :admin do plug(ApiWeb.Plugs.RequireAdmin) + plug(ApiWeb.Plugs.Require2Factor) end pipeline :portal_view do diff --git a/apps/api_web/lib/api_web/templates/client_portal/layout/footer.html.heex b/apps/api_web/lib/api_web/templates/client_portal/layout/footer.html.heex index 290947ec..4ca7829c 100644 --- a/apps/api_web/lib/api_web/templates/client_portal/layout/footer.html.heex +++ b/apps/api_web/lib/api_web/templates/client_portal/layout/footer.html.heex @@ -28,8 +28,13 @@ diff --git a/apps/api_web/test/api_web/plugs/require_2factor_test.exs b/apps/api_web/test/api_web/plugs/require_2factor_test.exs new file mode 100644 index 00000000..c1ac57cf --- /dev/null +++ b/apps/api_web/test/api_web/plugs/require_2factor_test.exs @@ -0,0 +1,55 @@ +defmodule ApiWeb.Plugs.Require2FactorTest do + use ApiWeb.ConnCase, async: true + + setup %{conn: conn} do + conn = + conn + |> conn_with_session() + |> bypass_through(ApiWeb.Router, [:browser, :admin]) + + {:ok, conn: conn} + end + + test "opts" do + assert ApiWeb.Plugs.Require2Factor.init([]) == [] + end + + describe ":require_2factor plug" do + test "gives 404 with no authenicated user", %{conn: conn} do + conn = get(conn, "/") + assert conn.status == 404 + assert html_response(conn, 404) =~ "not found" + end + + test "gives 404 for user without administrator role", %{conn: conn} do + conn = + conn + |> user_with_role(nil, true) + |> get("/") + + assert html_response(conn, 404) =~ "not found" + end + + test "redirects on missing 2fa, but valid admin account", %{conn: conn} do + conn = + conn + |> user_with_role("administrator", false) + |> get("/") + + assert html_response(conn, 302) + end + + test "allows user with administrator role and 2fa to proceed", %{conn: conn} do + conn = + conn + |> user_with_role("administrator", true) + |> get("/") + + refute conn.status + end + end + + defp user_with_role(conn, role, totp_enabled) do + Plug.Conn.assign(conn, :user, %ApiAccounts.User{role: role, totp_enabled: totp_enabled}) + end +end diff --git a/apps/api_web/test/api_web/plugs/require_admin_test.exs b/apps/api_web/test/api_web/plugs/require_admin_test.exs index f69be2f4..1f1d0396 100644 --- a/apps/api_web/test/api_web/plugs/require_admin_test.exs +++ b/apps/api_web/test/api_web/plugs/require_admin_test.exs @@ -41,6 +41,6 @@ defmodule ApiWeb.Plugs.RequireAdminTest do end defp user_with_role(conn, role) do - Plug.Conn.assign(conn, :user, %ApiAccounts.User{role: role}) + Plug.Conn.assign(conn, :user, %ApiAccounts.User{role: role, totp_enabled: true}) end end