add button for mods to unlock account (derpibooru/philomena#173)

This commit is contained in:
byte[] 2020-09-05 22:53:55 -04:00
parent dbfdd22ea9
commit 6915d2ed45
6 changed files with 80 additions and 7 deletions

View file

@ -234,7 +234,7 @@ defmodule Philomena.Users do
If the token matches, the user is marked as unlocked
and the token is deleted.
"""
def unlock_user(token) do
def unlock_user_by_token(token) do
with {:ok, query} <- UserToken.verify_email_token_query(token, "unlock"),
%User{} = user <- Repo.one(query),
{:ok, %{user: user}} <- Repo.transaction(unlock_user_multi(user)) do
@ -252,6 +252,15 @@ defmodule Philomena.Users do
|> Ecto.Multi.delete_all(:tokens, UserToken.user_and_contexts_query(user, ["unlock"]))
end
@doc """
Unconditionally unlocks the given user.
"""
def unlock_user(user) do
user
|> User.unlock_changeset()
|> Repo.update()
end
@doc """
Delivers the unlock instructions to the given user.

View file

@ -0,0 +1,24 @@
defmodule PhilomenaWeb.Admin.User.UnlockController do
use PhilomenaWeb, :controller
alias Philomena.Users.User
alias Philomena.Users
plug :verify_authorized
plug :load_resource, model: User, id_name: "user_id", id_field: "slug", persisted: true
def create(conn, _params) do
{:ok, user} = Users.unlock_user(conn.assigns.user)
conn
|> put_flash(:info, "User was unlocked.")
|> redirect(to: Routes.profile_path(conn, :show, user))
end
defp verify_authorized(conn, _opts) do
case Canada.Can.can?(conn.assigns.current_user, :index, User) do
true -> conn
_false -> PhilomenaWeb.NotAuthorizedPlug.call(conn)
end
end
end

View file

@ -30,7 +30,7 @@ defmodule PhilomenaWeb.UnlockController do
# Do not log in the user after unlocking to avoid a
# leaked token giving the user access to the account.
def show(conn, %{"id" => token}) do
case Users.unlock_user(token) do
case Users.unlock_user_by_token(token) do
{:ok, _} ->
conn
|> put_flash(:info, "Account unlocked successfully. You may now log in.")

View file

@ -365,6 +365,7 @@ defmodule PhilomenaWeb.Router do
only: [:create, :delete],
singleton: true
resources "/unlock", User.UnlockController, only: [:create], singleton: true
resources "/api_key", User.ApiKeyController, only: [:delete], singleton: true
resources "/downvotes", User.DownvoteController, only: [:delete], singleton: true
resources "/votes", User.VoteController, only: [:delete], singleton: true

View file

@ -31,6 +31,18 @@
' Two factor auth:
strong = enabled_text(@user.otp_required_for_login)
br
= if @user.locked_at do
i.fas.fa-fw.fa-lock>
strong.comment_deleted>
' Account locked,
=> @user.failed_attempts
' failed login attempts
- else
i.fas.fa-fw.fa-unlock>
' Not currently locked
br
a.label.label--primary.label--block href="#" data-click-toggle=".js-admin__options__toggle" title="Toggle Controls"
@ -87,7 +99,7 @@ a.label.label--primary.label--block href="#" data-click-toggle=".js-admin__optio
= if @forced do
li
= link to: Routes.admin_user_force_filter_path(@conn, :delete, @user), data: [confirm: "Are you really, really sure?", method: "delete"] do
i.fas.faw-fw.fa-filter
i.fas.fa-fw.fa-filter
span.admin__button Remove Force Filter
= if @user.deleted_at do
@ -101,6 +113,12 @@ a.label.label--primary.label--block href="#" data-click-toggle=".js-admin__optio
i.fa.fa-fw.fa-times
span.admin__button Deactivate Account
= if @user.locked_at do
li
= link to: Routes.admin_user_unlock_path(@conn, :create, @user), data: [method: "post"] do
i.fas.fa-fw.fa-unlock
span.admin__button Unlock Account
li
= link to: Routes.admin_user_wipe_path(@conn, :create, @user), data: [confirm: "This is irreversible, destroying all identifying information including email. Are you sure?", method: "post"] do
i.fas.fa-fw.fa-eraser

View file

@ -501,7 +501,7 @@ defmodule Philomena.UsersTest do
end
end
describe "unlock_user/2" do
describe "unlock_user_by_token/1" do
setup do
user = locked_user_fixture()
@ -514,26 +514,47 @@ defmodule Philomena.UsersTest do
end
test "unlocks the user with a valid token", %{user: user, token: token} do
assert {:ok, unlocked_user} = Users.unlock_user(token)
assert {:ok, unlocked_user} = Users.unlock_user_by_token(token)
refute unlocked_user.locked_at
refute Repo.get!(User, user.id).locked_at
refute Repo.get_by(UserToken, user_id: user.id)
end
test "does not confirm with invalid token", %{user: user} do
assert Users.unlock_user("oops") == :error
assert Users.unlock_user_by_token("oops") == :error
assert Repo.get!(User, user.id).locked_at
assert Repo.get_by(UserToken, user_id: user.id)
end
test "does not unlocked if token expired", %{user: user, token: token} do
{1, nil} = Repo.update_all(UserToken, set: [created_at: ~N[2020-01-01 00:00:00]])
assert Users.unlock_user(token) == :error
assert Users.unlock_user_by_token(token) == :error
assert Repo.get!(User, user.id).locked_at
assert Repo.get_by(UserToken, user_id: user.id)
end
end
describe "unlock_user/1" do
setup do
user = user_fixture()
locked_user = locked_user_fixture()
%{user: user, locked_user: locked_user}
end
test "unlocks the user when locked", %{locked_user: locked_user} do
assert {:ok, unlocked_user} = Users.unlock_user(locked_user)
refute unlocked_user.locked_at
refute Repo.get!(User, unlocked_user.id).locked_at
end
test "does nothing when not locked", %{user: user} do
assert {:ok, unlocked_user} = Users.unlock_user(user)
refute unlocked_user.locked_at
refute Repo.get!(User, unlocked_user.id).locked_at
end
end
describe "deliver_user_reset_password_instructions/2" do
setup do
%{user: user_fixture()}