From d29fb8cfd71dd42fbabfe2c14731cc57583579d2 Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Wed, 28 Jan 2026 16:16:59 -0600 Subject: [PATCH] totp fixes --- k8s/deployment.yaml | 4 ++-- mix.exs | 2 +- mix.lock | 2 +- test/support/fixtures/accounts_fixtures.ex | 20 ++++++++++++++++++- test/towerops/accounts_test.exs | 3 ++- .../user_registration_controller_test.exs | 8 +++++--- .../user_session_controller_test.exs | 15 +++++++++----- .../account_live/totp_enrollment_test.exs | 3 ++- 8 files changed, 42 insertions(+), 15 deletions(-) diff --git a/k8s/deployment.yaml b/k8s/deployment.yaml index 7fcc19a2..2e04b6c4 100644 --- a/k8s/deployment.yaml +++ b/k8s/deployment.yaml @@ -113,10 +113,10 @@ spec: name: towerops-aws resources: requests: - memory: "1Gi" + memory: "1.5Gi" cpu: "100m" limits: - memory: "2Gi" + memory: "3Gi" cpu: "500m" startupProbe: httpGet: diff --git a/mix.exs b/mix.exs index 198debfd..c9ee30c9 100644 --- a/mix.exs +++ b/mix.exs @@ -43,7 +43,7 @@ defmodule Towerops.MixProject do [ {:bcrypt_elixir, "~> 3.0"}, {:nimble_totp, "~> 1.0"}, - {:eqrcode, "~> 0.1.10"}, + {:eqrcode, "~> 0.2.1"}, {:phoenix, "~> 1.8.3"}, {:phoenix_ecto, "~> 4.5"}, {:ecto_sql, "~> 3.13"}, diff --git a/mix.lock b/mix.lock index 2798886b..7c8cd341 100644 --- a/mix.lock +++ b/mix.lock @@ -14,7 +14,7 @@ "ecto_psql_extras": {:hex, :ecto_psql_extras, "0.8.8", "aa02529c97f69aed5722899f5dc6360128735a92dd169f23c5d50b1f7fdede08", [:mix], [{:ecto_sql, "~> 3.7", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:postgrex, "> 0.16.0", [hex: :postgrex, repo: "hexpm", optional: false]}, {:table_rex, "~> 3.1.1 or ~> 4.0", [hex: :table_rex, repo: "hexpm", optional: false]}], "hexpm", "04c63d92b141723ad6fed2e60a4b461ca00b3594d16df47bbc48f1f4534f2c49"}, "ecto_sql": {:hex, :ecto_sql, "3.13.4", "b6e9d07557ddba62508a9ce4a484989a5bb5e9a048ae0e695f6d93f095c25d60", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.13.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2b38cf0749ca4d1c5a8bcbff79bbe15446861ca12a61f9fba604486cb6b62a14"}, "elixir_make": {:hex, :elixir_make, "0.9.0", "6484b3cd8c0cee58f09f05ecaf1a140a8c97670671a6a0e7ab4dc326c3109726", [:mix], [], "hexpm", "db23d4fd8b757462ad02f8aa73431a426fe6671c80b200d9710caf3d1dd0ffdb"}, - "eqrcode": {:hex, :eqrcode, "0.1.10", "6294fece9d68ad64eef1c3c92cf111cfd6469f4fbf230a2d4cc905a682178f3f", [:mix], [], "hexpm", "da30e373c36a0fd37ab6f58664b16029919896d6c45a68a95cc4d713e81076f1"}, + "eqrcode": {:hex, :eqrcode, "0.2.1", "d12838813e8fc87b8940cc05f9baadb189031f6009facdc56ff074375ec73b6e", [:mix], [], "hexpm", "d5828a222b904c68360e7dc2a40c3ef33a1328b7c074583898040f389f928025"}, "erlex": {:hex, :erlex, "0.2.8", "cd8116f20f3c0afe376d1e8d1f0ae2452337729f68be016ea544a72f767d9c12", [:mix], [], "hexpm", "9d66ff9fedf69e49dc3fd12831e12a8a37b76f8651dd21cd45fcf5561a8a7590"}, "esbuild": {:hex, :esbuild, "0.10.0", "b0aa3388a1c23e727c5a3e7427c932d89ee791746b0081bbe56103e9ef3d291f", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "468489cda427b974a7cc9f03ace55368a83e1a7be12fba7e30969af78e5f8c70"}, "expo": {:hex, :expo, "1.1.1", "4202e1d2ca6e2b3b63e02f69cfe0a404f77702b041d02b58597c00992b601db5", [:mix], [], "hexpm", "5fb308b9cb359ae200b7e23d37c76978673aa1b06e2b3075d814ce12c5811640"}, diff --git a/test/support/fixtures/accounts_fixtures.ex b/test/support/fixtures/accounts_fixtures.ex index ab551c02..3afca93f 100644 --- a/test/support/fixtures/accounts_fixtures.ex +++ b/test/support/fixtures/accounts_fixtures.ex @@ -32,14 +32,32 @@ defmodule Towerops.AccountsFixtures do end def user_fixture(attrs \\ %{}) do + # Extract enable_totp option before passing to register_user + enable_totp = get_option(attrs, :enable_totp, true) + {:ok, user} = attrs + |> remove_option(:enable_totp) |> valid_user_attributes() |> Accounts.register_user() - user + # Enable TOTP by default for all test users + # Tests can explicitly pass enable_totp: false to skip this + if enable_totp do + secret = Accounts.generate_totp_secret() + {:ok, user} = Accounts.enable_totp(user, secret) + user + else + user + end end + defp get_option(attrs, key, default) when is_map(attrs), do: Map.get(attrs, key, default) + defp get_option(attrs, key, default) when is_list(attrs), do: Keyword.get(attrs, key, default) + + defp remove_option(attrs, key) when is_map(attrs), do: Map.delete(attrs, key) + defp remove_option(attrs, key) when is_list(attrs), do: Keyword.delete(attrs, key) + def user_scope_fixture do user = user_fixture() user_scope_fixture(user) diff --git a/test/towerops/accounts_test.exs b/test/towerops/accounts_test.exs index 8d3b7f2a..fc900efc 100644 --- a/test/towerops/accounts_test.exs +++ b/test/towerops/accounts_test.exs @@ -394,7 +394,8 @@ defmodule Towerops.AccountsTest do describe "TOTP enrollment" do setup do - %{user: user_fixture()} + # Create user without TOTP for enrollment tests + %{user: user_fixture(enable_totp: false)} end test "generate_totp_secret/0 generates a valid secret" do diff --git a/test/towerops_web/controllers/user_registration_controller_test.exs b/test/towerops_web/controllers/user_registration_controller_test.exs index 13642b3b..2abee35c 100644 --- a/test/towerops_web/controllers/user_registration_controller_test.exs +++ b/test/towerops_web/controllers/user_registration_controller_test.exs @@ -13,6 +13,7 @@ defmodule ToweropsWeb.UserRegistrationControllerTest do end test "redirects if already logged in", %{conn: conn} do + # user_fixture now creates users with TOTP enabled by default conn = conn |> log_in_user(user_fixture()) |> get(~p"/users/register") assert redirected_to(conn) == ~p"/orgs" @@ -30,8 +31,8 @@ defmodule ToweropsWeb.UserRegistrationControllerTest do }) assert get_session(conn, :user_token) - # New users with no organizations get redirected to /devices - assert redirected_to(conn) == ~p"/devices" + # New users redirected to TOTP enrollment (TOTP is mandatory) + assert redirected_to(conn) == ~p"/account/totp-enrollment" assert conn.assigns.flash["info"] =~ "Account created successfully" end @@ -71,7 +72,8 @@ defmodule ToweropsWeb.UserRegistrationControllerTest do }) assert get_session(conn, :user_token) - assert redirected_to(conn) == ~p"/devices" + # User redirected to TOTP enrollment (TOTP is mandatory) + assert redirected_to(conn) == ~p"/account/totp-enrollment" # Verify organization was created with the provided name user = Towerops.Accounts.get_user_by_email(email) diff --git a/test/towerops_web/controllers/user_session_controller_test.exs b/test/towerops_web/controllers/user_session_controller_test.exs index 84b0163b..a6586599 100644 --- a/test/towerops_web/controllers/user_session_controller_test.exs +++ b/test/towerops_web/controllers/user_session_controller_test.exs @@ -6,7 +6,8 @@ defmodule ToweropsWeb.UserSessionControllerTest do alias Towerops.Accounts setup do - %{unconfirmed_user: unconfirmed_user_fixture(), user: user_fixture()} + # Create users without TOTP for login flow tests (they'll be prompted to enroll) + %{unconfirmed_user: unconfirmed_user_fixture(), user: user_fixture(enable_totp: false)} end describe "GET /users/log-in" do @@ -42,7 +43,8 @@ defmodule ToweropsWeb.UserSessionControllerTest do end) conn = get(conn, ~p"/users/log-in/#{token}") - assert redirected_to(conn) == ~p"/orgs" + # User redirected to TOTP enrollment (TOTP is mandatory) + assert redirected_to(conn) == ~p"/account/totp-enrollment" assert get_session(conn, :user_token) refute Phoenix.Flash.get(conn.assigns.flash, :info) assert conn.resp_cookies["_towerops_web_user_remember_me"] @@ -65,7 +67,8 @@ defmodule ToweropsWeb.UserSessionControllerTest do }) assert get_session(conn, :user_token) - assert redirected_to(conn) == ~p"/orgs" + # User redirected to TOTP enrollment (TOTP is mandatory) + assert redirected_to(conn) == ~p"/account/totp-enrollment" end test "logs the user in with remember me", %{conn: conn, user: user} do @@ -79,7 +82,8 @@ defmodule ToweropsWeb.UserSessionControllerTest do }) assert conn.resp_cookies["_towerops_web_user_remember_me"] - assert redirected_to(conn) == ~p"/orgs" + # User redirected to TOTP enrollment (TOTP is mandatory) + assert redirected_to(conn) == ~p"/account/totp-enrollment" end test "logs the user in with return to", %{conn: conn, user: user} do @@ -128,7 +132,8 @@ defmodule ToweropsWeb.UserSessionControllerTest do }) assert get_session(conn, :user_token) - assert redirected_to(conn) == ~p"/orgs" + # User redirected to TOTP enrollment (TOTP is mandatory) + assert redirected_to(conn) == ~p"/account/totp-enrollment" end test "emits error message when magic link is invalid", %{conn: conn} do diff --git a/test/towerops_web/live/account_live/totp_enrollment_test.exs b/test/towerops_web/live/account_live/totp_enrollment_test.exs index 9a325e1d..c4bfce1f 100644 --- a/test/towerops_web/live/account_live/totp_enrollment_test.exs +++ b/test/towerops_web/live/account_live/totp_enrollment_test.exs @@ -8,7 +8,8 @@ defmodule ToweropsWeb.AccountLive.TotpEnrollmentTest do describe "TOTP Enrollment Page - unenrolled user" do setup do - user = user_fixture() + # Create user without TOTP enabled (to test enrollment flow) + user = user_fixture(enable_totp: false) %{conn: log_in_user(build_conn(), user), user: user} end