feat: add Accounts.grant_sudo_mode/1 function
This commit is contained in:
parent
303196bb8e
commit
ada851e7ed
4 changed files with 82 additions and 0 deletions
|
|
@ -620,6 +620,24 @@ defmodule Towerops.Accounts do
|
|||
|
||||
def sudo_mode?(_user, _minutes), do: false
|
||||
|
||||
@doc """
|
||||
Grants sudo mode to a user by updating their last_sudo_at timestamp.
|
||||
|
||||
Returns `{:ok, user}` on success or `{:error, changeset}` on failure.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> grant_sudo_mode(user)
|
||||
{:ok, %User{last_sudo_at: ~U[2026-02-01 12:00:00Z]}}
|
||||
"""
|
||||
def grant_sudo_mode(%User{} = user) do
|
||||
now = DateTime.utc_now()
|
||||
|
||||
user
|
||||
|> User.sudo_changeset(%{last_sudo_at: now})
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns an `%Ecto.Changeset{}` for changing the user email.
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ defmodule Towerops.Accounts.User do
|
|||
field :privacy_policy_consent, :boolean, virtual: true
|
||||
field :terms_of_service_consent, :boolean, virtual: true
|
||||
field :password_breach_count, :integer, virtual: true
|
||||
field :last_sudo_at, :utc_datetime
|
||||
|
||||
has_many :memberships, Membership
|
||||
has_many :organizations, through: [:memberships, :organization]
|
||||
|
|
@ -49,6 +50,7 @@ defmodule Towerops.Accounts.User do
|
|||
timezone: String.t(),
|
||||
totp_secret: binary() | nil,
|
||||
totp_verified_at: DateTime.t() | nil,
|
||||
last_sudo_at: DateTime.t() | nil,
|
||||
memberships: NotLoaded.t() | [Membership.t()],
|
||||
organizations: NotLoaded.t() | [Towerops.Organizations.Organization.t()],
|
||||
inserted_at: DateTime.t(),
|
||||
|
|
@ -229,6 +231,15 @@ defmodule Towerops.Accounts.User do
|
|||
change(user, confirmed_at: now)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for updating sudo mode timestamp.
|
||||
"""
|
||||
def sudo_changeset(user, attrs) do
|
||||
user
|
||||
|> cast(attrs, [:last_sudo_at])
|
||||
|> validate_required([:last_sudo_at])
|
||||
end
|
||||
|
||||
@doc """
|
||||
Verifies the password.
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
defmodule Towerops.Repo.Migrations.AddLastSudoAtToUsers do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
alter table(:users) do
|
||||
add :last_sudo_at, :utc_datetime
|
||||
end
|
||||
end
|
||||
end
|
||||
44
test/towerops/accounts/grant_sudo_mode_test.exs
Normal file
44
test/towerops/accounts/grant_sudo_mode_test.exs
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
defmodule Towerops.Accounts.GrantSudoModeTest do
|
||||
use Towerops.DataCase, async: true
|
||||
|
||||
import Towerops.AccountsFixtures
|
||||
|
||||
alias Towerops.Accounts
|
||||
alias Towerops.Accounts.User
|
||||
|
||||
describe "grant_sudo_mode/1" do
|
||||
test "updates user's last_sudo_at timestamp" do
|
||||
user = user_fixture()
|
||||
assert is_nil(user.last_sudo_at)
|
||||
|
||||
{:ok, updated_user} = Accounts.grant_sudo_mode(user)
|
||||
|
||||
assert %DateTime{} = updated_user.last_sudo_at
|
||||
assert DateTime.diff(DateTime.utc_now(), updated_user.last_sudo_at, :second) < 2
|
||||
end
|
||||
|
||||
test "updates an already set last_sudo_at timestamp" do
|
||||
user = user_fixture()
|
||||
|
||||
# First grant
|
||||
{:ok, user_with_sudo} = Accounts.grant_sudo_mode(user)
|
||||
first_timestamp = user_with_sudo.last_sudo_at
|
||||
|
||||
# Wait to ensure different timestamp (at least 1 second)
|
||||
Process.sleep(1100)
|
||||
|
||||
# Second grant should update the timestamp
|
||||
{:ok, updated_user} = Accounts.grant_sudo_mode(user_with_sudo)
|
||||
assert DateTime.after?(updated_user.last_sudo_at, first_timestamp)
|
||||
end
|
||||
|
||||
test "validates changeset" do
|
||||
user = user_fixture()
|
||||
|
||||
# Test the changeset validation directly
|
||||
changeset = User.sudo_changeset(user, %{})
|
||||
refute changeset.valid?
|
||||
assert "can't be blank" in errors_on(changeset).last_sudo_at
|
||||
end
|
||||
end
|
||||
end
|
||||
Loading…
Add table
Reference in a new issue