totp fixes

This commit is contained in:
Graham McIntire 2026-01-28 16:16:59 -06:00
parent b13de895f7
commit d29fb8cfd7
8 changed files with 42 additions and 15 deletions

View file

@ -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:

View file

@ -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"},

View file

@ -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"},

View file

@ -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)

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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