- Add UserSudoController with GET and POST /users/sudo/verify routes - Create verify.html.heex template for TOTP verification form - Only accept TOTP codes (6 numeric digits), reject recovery codes - Update grant_sudo_mode to set authenticated_at virtual field - Exclude /users/sudo paths from return_to overwriting - Add comprehensive controller tests (12 test cases) - Verify redirect behavior, error handling, and session management 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
71 lines
2.1 KiB
Elixir
71 lines
2.1 KiB
Elixir
defmodule ToweropsWeb.UserSudoController do
|
|
@moduledoc """
|
|
Controller for sudo mode verification.
|
|
|
|
Handles re-authentication for sensitive operations by requiring TOTP verification
|
|
(no recovery codes allowed).
|
|
"""
|
|
|
|
use ToweropsWeb, :controller
|
|
|
|
import ToweropsWeb.UserAuth, only: [require_authenticated_user: 2]
|
|
|
|
alias Towerops.Accounts
|
|
|
|
plug :require_authenticated_user
|
|
|
|
def verify(conn, _params) do
|
|
# Render the sudo verification form
|
|
form = Phoenix.Component.to_form(%{}, as: "user")
|
|
render(conn, :verify, form: form)
|
|
end
|
|
|
|
def create(conn, %{"user" => %{"totp_code" => totp_code}}) do
|
|
user = conn.assigns.current_scope.user
|
|
|
|
case Accounts.verify_totp_only(user, totp_code) do
|
|
{:ok, _verified_user} ->
|
|
# Grant sudo mode
|
|
case Accounts.grant_sudo_mode(user) do
|
|
{:ok, _updated_user} ->
|
|
# Redirect to return_to path or default
|
|
return_to = get_session(conn, :user_return_to) || ~p"/orgs"
|
|
|
|
conn
|
|
|> delete_session(:user_return_to)
|
|
|> redirect(to: return_to)
|
|
|
|
{:error, _changeset} ->
|
|
# Shouldn't happen, but handle gracefully
|
|
form = Phoenix.Component.to_form(%{"totp_code" => totp_code}, as: "user")
|
|
|
|
conn
|
|
|> put_flash(:error, "Failed to grant sudo mode. Please try again.")
|
|
|> render(:verify, form: form)
|
|
end
|
|
|
|
{:error, :recovery_code_not_allowed} ->
|
|
form = Phoenix.Component.to_form(%{"totp_code" => totp_code}, as: "user")
|
|
|
|
conn
|
|
|> put_flash(
|
|
:error,
|
|
"Recovery codes are not allowed for sudo mode verification. Please use your authenticator app."
|
|
)
|
|
|> render(:verify, form: form)
|
|
|
|
{:error, :invalid_code} ->
|
|
form = Phoenix.Component.to_form(%{"totp_code" => totp_code}, as: "user")
|
|
|
|
conn
|
|
|> put_flash(:error, "Invalid authentication code. Please try again.")
|
|
|> render(:verify, form: form)
|
|
end
|
|
end
|
|
|
|
def create(conn, _params) do
|
|
# Handle missing totp_code parameter
|
|
form = Phoenix.Component.to_form(%{}, as: "user")
|
|
render(conn, :verify, form: form)
|
|
end
|
|
end
|