Merge branch 'issues/184' into 'develop'
[#184] small refactoring reset password See merge request pleroma/pleroma!1301
This commit is contained in:
commit
d80859731e
14 changed files with 142 additions and 60 deletions
|
@ -204,9 +204,9 @@ def run(["reset_password", nickname]) do
|
|||
|
||||
IO.puts(
|
||||
"URL: #{
|
||||
Pleroma.Web.Router.Helpers.util_url(
|
||||
Pleroma.Web.Router.Helpers.reset_password_url(
|
||||
Pleroma.Web.Endpoint,
|
||||
:show_password_reset,
|
||||
:reset,
|
||||
token.token
|
||||
)
|
||||
}"
|
||||
|
|
|
@ -23,13 +23,8 @@ defp recipient(email, nil), do: email
|
|||
defp recipient(email, name), do: {name, email}
|
||||
defp recipient(%Pleroma.User{} = user), do: recipient(user.email, user.name)
|
||||
|
||||
def password_reset_email(user, password_reset_token) when is_binary(password_reset_token) do
|
||||
password_reset_url =
|
||||
Router.Helpers.util_url(
|
||||
Endpoint,
|
||||
:show_password_reset,
|
||||
password_reset_token
|
||||
)
|
||||
def password_reset_email(user, token) when is_binary(token) do
|
||||
password_reset_url = Router.Helpers.reset_password_url(Endpoint, :reset, token)
|
||||
|
||||
html_body = """
|
||||
<h3>Reset your password at #{instance_name()}</h3>
|
||||
|
|
|
@ -37,6 +37,7 @@ def used_changeset(struct) do
|
|||
|> put_change(:used, true)
|
||||
end
|
||||
|
||||
@spec reset_password(binary(), map()) :: {:ok, User.t()} | {:error, binary()}
|
||||
def reset_password(token, data) do
|
||||
with %{used: false} = token <- Repo.get_by(PasswordResetToken, %{token: token}),
|
||||
%User{} = user <- User.get_cached_by_id(token.user_id),
|
|
@ -9,6 +9,7 @@ defmodule Pleroma.User do
|
|||
import Ecto.Query
|
||||
|
||||
alias Comeonin.Pbkdf2
|
||||
alias Ecto.Multi
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Keys
|
||||
alias Pleroma.Notification
|
||||
|
@ -194,27 +195,24 @@ def upgrade_changeset(struct, params \\ %{}) do
|
|||
end
|
||||
|
||||
def password_update_changeset(struct, params) do
|
||||
changeset =
|
||||
struct
|
||||
|> cast(params, [:password, :password_confirmation])
|
||||
|> validate_required([:password, :password_confirmation])
|
||||
|> validate_confirmation(:password)
|
||||
|
||||
OAuth.Token.delete_user_tokens(struct)
|
||||
OAuth.Authorization.delete_user_authorizations(struct)
|
||||
|
||||
if changeset.valid? do
|
||||
hashed = Pbkdf2.hashpwsalt(changeset.changes[:password])
|
||||
|
||||
changeset
|
||||
|> put_change(:password_hash, hashed)
|
||||
else
|
||||
changeset
|
||||
end
|
||||
struct
|
||||
|> cast(params, [:password, :password_confirmation])
|
||||
|> validate_required([:password, :password_confirmation])
|
||||
|> validate_confirmation(:password)
|
||||
|> put_password_hash
|
||||
end
|
||||
|
||||
def reset_password(user, data) do
|
||||
update_and_set_cache(password_update_changeset(user, data))
|
||||
def reset_password(%User{id: user_id} = user, data) do
|
||||
multi =
|
||||
Multi.new()
|
||||
|> Multi.update(:user, password_update_changeset(user, data))
|
||||
|> Multi.delete_all(:tokens, OAuth.Token.Query.get_by_user(user_id))
|
||||
|> Multi.delete_all(:auth, OAuth.Authorization.delete_by_user_query(user))
|
||||
|
||||
case Repo.transaction(multi) do
|
||||
{:ok, %{user: user} = _} -> set_cache(user)
|
||||
{:error, _, changeset, _} -> {:error, changeset}
|
||||
end
|
||||
end
|
||||
|
||||
def register_changeset(struct, params \\ %{}, opts \\ []) do
|
||||
|
@ -250,12 +248,11 @@ def register_changeset(struct, params \\ %{}, opts \\ []) do
|
|||
end
|
||||
|
||||
if changeset.valid? do
|
||||
hashed = Pbkdf2.hashpwsalt(changeset.changes[:password])
|
||||
ap_id = User.ap_id(%User{nickname: changeset.changes[:nickname]})
|
||||
followers = User.ap_followers(%User{nickname: changeset.changes[:nickname]})
|
||||
|
||||
changeset
|
||||
|> put_change(:password_hash, hashed)
|
||||
|> put_password_hash
|
||||
|> put_change(:ap_id, ap_id)
|
||||
|> unique_constraint(:ap_id)
|
||||
|> put_change(:following, [followers])
|
||||
|
@ -1349,4 +1346,12 @@ def get_ap_ids_by_nicknames(nicknames) do
|
|||
end
|
||||
|
||||
defdelegate search(query, opts \\ []), to: User.Search
|
||||
|
||||
defp put_password_hash(
|
||||
%Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
|
||||
) do
|
||||
change(changeset, password_hash: Pbkdf2.hashpwsalt(password))
|
||||
end
|
||||
|
||||
defp put_password_hash(changeset), do: changeset
|
||||
end
|
||||
|
|
|
@ -76,14 +76,16 @@ def use_token(%Authorization{used: false, valid_until: valid_until} = auth) do
|
|||
def use_token(%Authorization{used: true}), do: {:error, "already used"}
|
||||
|
||||
@spec delete_user_authorizations(User.t()) :: {integer(), any()}
|
||||
def delete_user_authorizations(%User{id: user_id}) do
|
||||
from(
|
||||
a in Pleroma.Web.OAuth.Authorization,
|
||||
where: a.user_id == ^user_id
|
||||
)
|
||||
def delete_user_authorizations(%User{} = user) do
|
||||
user
|
||||
|> delete_by_user_query
|
||||
|> Repo.delete_all()
|
||||
end
|
||||
|
||||
def delete_by_user_query(%User{id: user_id}) do
|
||||
from(a in __MODULE__, where: a.user_id == ^user_id)
|
||||
end
|
||||
|
||||
@doc "gets auth for app by token"
|
||||
@spec get_by_token(App.t(), String.t()) :: {:ok, t()} | {:error, :not_found}
|
||||
def get_by_token(%App{id: app_id} = _app, token) do
|
||||
|
|
|
@ -133,8 +133,8 @@ defmodule Pleroma.Web.Router do
|
|||
scope "/api/pleroma", Pleroma.Web.TwitterAPI do
|
||||
pipe_through(:pleroma_api)
|
||||
|
||||
get("/password_reset/:token", UtilController, :show_password_reset)
|
||||
post("/password_reset", UtilController, :password_reset)
|
||||
get("/password_reset/:token", PasswordController, :reset, as: :reset_password)
|
||||
post("/password_reset", PasswordController, :do_reset, as: :reset_password)
|
||||
get("/emoji", UtilController, :emoji)
|
||||
get("/captcha", UtilController, :captcha)
|
||||
get("/healthcheck", UtilController, :healthcheck)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<h2>Password Reset for <%= @user.nickname %></h2>
|
||||
<%= form_for @conn, util_path(@conn, :password_reset), [as: "data"], fn f -> %>
|
||||
<%= form_for @conn, reset_password_path(@conn, :do_reset), [as: "data"], fn f -> %>
|
||||
<div class="form-row">
|
||||
<%= label f, :password, "Password" %>
|
||||
<%= password_input f, :password %>
|
|
@ -0,0 +1,37 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.TwitterAPI.PasswordController do
|
||||
@moduledoc """
|
||||
The module containts functions for reset password.
|
||||
"""
|
||||
|
||||
use Pleroma.Web, :controller
|
||||
|
||||
require Logger
|
||||
|
||||
alias Pleroma.PasswordResetToken
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
|
||||
def reset(conn, %{"token" => token}) do
|
||||
with %{used: false} = token <- Repo.get_by(PasswordResetToken, %{token: token}),
|
||||
%User{} = user <- User.get_cached_by_id(token.user_id) do
|
||||
render(conn, "reset.html", %{
|
||||
token: token,
|
||||
user: user
|
||||
})
|
||||
else
|
||||
_e -> render(conn, "invalid_token.html")
|
||||
end
|
||||
end
|
||||
|
||||
def do_reset(conn, %{"data" => data}) do
|
||||
with {:ok, _} <- PasswordResetToken.reset_password(data["token"], data) do
|
||||
render(conn, "reset_success.html")
|
||||
else
|
||||
_e -> render(conn, "reset_failed.html")
|
||||
end
|
||||
end
|
||||
end
|
|
@ -11,8 +11,6 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
|
|||
alias Pleroma.Activity
|
||||
alias Pleroma.Emoji
|
||||
alias Pleroma.Notification
|
||||
alias Pleroma.PasswordResetToken
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
|
@ -20,26 +18,6 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
|
|||
alias Pleroma.Web.OStatus
|
||||
alias Pleroma.Web.WebFinger
|
||||
|
||||
def show_password_reset(conn, %{"token" => token}) do
|
||||
with %{used: false} = token <- Repo.get_by(PasswordResetToken, %{token: token}),
|
||||
%User{} = user <- User.get_cached_by_id(token.user_id) do
|
||||
render(conn, "password_reset.html", %{
|
||||
token: token,
|
||||
user: user
|
||||
})
|
||||
else
|
||||
_e -> render(conn, "invalid_token.html")
|
||||
end
|
||||
end
|
||||
|
||||
def password_reset(conn, %{"data" => data}) do
|
||||
with {:ok, _} <- PasswordResetToken.reset_password(data["token"], data) do
|
||||
render(conn, "password_reset_success.html")
|
||||
else
|
||||
_e -> render(conn, "password_reset_failed.html")
|
||||
end
|
||||
end
|
||||
|
||||
def help_test(conn, _params) do
|
||||
json(conn, "ok")
|
||||
end
|
||||
|
|
8
lib/pleroma/web/twitter_api/views/password_view.ex
Normal file
8
lib/pleroma/web/twitter_api/views/password_view.ex
Normal file
|
@ -0,0 +1,8 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.TwitterAPI.PasswordView do
|
||||
use Pleroma.Web, :view
|
||||
import Phoenix.HTML.Form
|
||||
end
|
56
test/web/twitter_api/password_controller_test.exs
Normal file
56
test/web/twitter_api/password_controller_test.exs
Normal file
|
@ -0,0 +1,56 @@
|
|||
defmodule Pleroma.Web.TwitterAPI.PasswordControllerTest do
|
||||
use Pleroma.Web.ConnCase
|
||||
|
||||
alias Pleroma.PasswordResetToken
|
||||
alias Pleroma.Web.OAuth.Token
|
||||
import Pleroma.Factory
|
||||
|
||||
describe "GET /api/pleroma/password_reset/token" do
|
||||
test "it returns error when token invalid", %{conn: conn} do
|
||||
response =
|
||||
conn
|
||||
|> get("/api/pleroma/password_reset/token")
|
||||
|> html_response(:ok)
|
||||
|
||||
assert response =~ "<h2>Invalid Token</h2>"
|
||||
end
|
||||
|
||||
test "it shows password reset form", %{conn: conn} do
|
||||
user = insert(:user)
|
||||
{:ok, token} = PasswordResetToken.create_token(user)
|
||||
|
||||
response =
|
||||
conn
|
||||
|> get("/api/pleroma/password_reset/#{token.token}")
|
||||
|> html_response(:ok)
|
||||
|
||||
assert response =~ "<h2>Password Reset for #{user.nickname}</h2>"
|
||||
end
|
||||
end
|
||||
|
||||
describe "POST /api/pleroma/password_reset" do
|
||||
test "it returns HTTP 200", %{conn: conn} do
|
||||
user = insert(:user)
|
||||
{:ok, token} = PasswordResetToken.create_token(user)
|
||||
{:ok, _access_token} = Token.create_token(insert(:oauth_app), user, %{})
|
||||
|
||||
params = %{
|
||||
"password" => "test",
|
||||
password_confirmation: "test",
|
||||
token: token.token
|
||||
}
|
||||
|
||||
response =
|
||||
conn
|
||||
|> assign(:user, user)
|
||||
|> post("/api/pleroma/password_reset", %{data: params})
|
||||
|> html_response(:ok)
|
||||
|
||||
assert response =~ "<h2>Password changed!</h2>"
|
||||
|
||||
user = refresh_record(user)
|
||||
assert Comeonin.Pbkdf2.checkpw("test", user.password_hash)
|
||||
assert length(Token.get_user_tokens(user)) == 0
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue