Merge branch 'issues/184' into 'develop'

[#184] small refactoring  reset password

See merge request pleroma/pleroma!1301
This commit is contained in:
kaniini 2019-06-24 19:01:56 +00:00
commit d80859731e
14 changed files with 142 additions and 60 deletions

View file

@ -204,9 +204,9 @@ defmodule Mix.Tasks.Pleroma.User do
IO.puts( IO.puts(
"URL: #{ "URL: #{
Pleroma.Web.Router.Helpers.util_url( Pleroma.Web.Router.Helpers.reset_password_url(
Pleroma.Web.Endpoint, Pleroma.Web.Endpoint,
:show_password_reset, :reset,
token.token token.token
) )
}" }"

View file

@ -23,13 +23,8 @@ defmodule Pleroma.Emails.UserEmail do
defp recipient(email, name), do: {name, email} defp recipient(email, name), do: {name, email}
defp recipient(%Pleroma.User{} = user), do: recipient(user.email, user.name) 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 def password_reset_email(user, token) when is_binary(token) do
password_reset_url = password_reset_url = Router.Helpers.reset_password_url(Endpoint, :reset, token)
Router.Helpers.util_url(
Endpoint,
:show_password_reset,
password_reset_token
)
html_body = """ html_body = """
<h3>Reset your password at #{instance_name()}</h3> <h3>Reset your password at #{instance_name()}</h3>

View file

@ -37,6 +37,7 @@ defmodule Pleroma.PasswordResetToken do
|> put_change(:used, true) |> put_change(:used, true)
end end
@spec reset_password(binary(), map()) :: {:ok, User.t()} | {:error, binary()}
def reset_password(token, data) do def reset_password(token, data) do
with %{used: false} = token <- Repo.get_by(PasswordResetToken, %{token: token}), with %{used: false} = token <- Repo.get_by(PasswordResetToken, %{token: token}),
%User{} = user <- User.get_cached_by_id(token.user_id), %User{} = user <- User.get_cached_by_id(token.user_id),

View file

@ -9,6 +9,7 @@ defmodule Pleroma.User do
import Ecto.Query import Ecto.Query
alias Comeonin.Pbkdf2 alias Comeonin.Pbkdf2
alias Ecto.Multi
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.Keys alias Pleroma.Keys
alias Pleroma.Notification alias Pleroma.Notification
@ -194,27 +195,24 @@ defmodule Pleroma.User do
end end
def password_update_changeset(struct, params) do def password_update_changeset(struct, params) do
changeset =
struct struct
|> cast(params, [:password, :password_confirmation]) |> cast(params, [:password, :password_confirmation])
|> validate_required([:password, :password_confirmation]) |> validate_required([:password, :password_confirmation])
|> validate_confirmation(:password) |> validate_confirmation(:password)
|> put_password_hash
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
end end
def reset_password(user, data) do def reset_password(%User{id: user_id} = user, data) do
update_and_set_cache(password_update_changeset(user, data)) 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 end
def register_changeset(struct, params \\ %{}, opts \\ []) do def register_changeset(struct, params \\ %{}, opts \\ []) do
@ -250,12 +248,11 @@ defmodule Pleroma.User do
end end
if changeset.valid? do if changeset.valid? do
hashed = Pbkdf2.hashpwsalt(changeset.changes[:password])
ap_id = User.ap_id(%User{nickname: changeset.changes[:nickname]}) ap_id = User.ap_id(%User{nickname: changeset.changes[:nickname]})
followers = User.ap_followers(%User{nickname: changeset.changes[:nickname]}) followers = User.ap_followers(%User{nickname: changeset.changes[:nickname]})
changeset changeset
|> put_change(:password_hash, hashed) |> put_password_hash
|> put_change(:ap_id, ap_id) |> put_change(:ap_id, ap_id)
|> unique_constraint(:ap_id) |> unique_constraint(:ap_id)
|> put_change(:following, [followers]) |> put_change(:following, [followers])
@ -1349,4 +1346,12 @@ defmodule Pleroma.User do
end end
defdelegate search(query, opts \\ []), to: User.Search 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 end

View file

@ -76,14 +76,16 @@ defmodule Pleroma.Web.OAuth.Authorization do
def use_token(%Authorization{used: true}), do: {:error, "already used"} def use_token(%Authorization{used: true}), do: {:error, "already used"}
@spec delete_user_authorizations(User.t()) :: {integer(), any()} @spec delete_user_authorizations(User.t()) :: {integer(), any()}
def delete_user_authorizations(%User{id: user_id}) do def delete_user_authorizations(%User{} = user) do
from( user
a in Pleroma.Web.OAuth.Authorization, |> delete_by_user_query
where: a.user_id == ^user_id
)
|> Repo.delete_all() |> Repo.delete_all()
end 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" @doc "gets auth for app by token"
@spec get_by_token(App.t(), String.t()) :: {:ok, t()} | {:error, :not_found} @spec get_by_token(App.t(), String.t()) :: {:ok, t()} | {:error, :not_found}
def get_by_token(%App{id: app_id} = _app, token) do def get_by_token(%App{id: app_id} = _app, token) do

View file

@ -133,8 +133,8 @@ defmodule Pleroma.Web.Router do
scope "/api/pleroma", Pleroma.Web.TwitterAPI do scope "/api/pleroma", Pleroma.Web.TwitterAPI do
pipe_through(:pleroma_api) pipe_through(:pleroma_api)
get("/password_reset/:token", UtilController, :show_password_reset) get("/password_reset/:token", PasswordController, :reset, as: :reset_password)
post("/password_reset", UtilController, :password_reset) post("/password_reset", PasswordController, :do_reset, as: :reset_password)
get("/emoji", UtilController, :emoji) get("/emoji", UtilController, :emoji)
get("/captcha", UtilController, :captcha) get("/captcha", UtilController, :captcha)
get("/healthcheck", UtilController, :healthcheck) get("/healthcheck", UtilController, :healthcheck)

View file

@ -1,5 +1,5 @@
<h2>Password Reset for <%= @user.nickname %></h2> <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"> <div class="form-row">
<%= label f, :password, "Password" %> <%= label f, :password, "Password" %>
<%= password_input f, :password %> <%= password_input f, :password %>

View file

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

View file

@ -11,8 +11,6 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.Emoji alias Pleroma.Emoji
alias Pleroma.Notification alias Pleroma.Notification
alias Pleroma.PasswordResetToken
alias Pleroma.Repo
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web alias Pleroma.Web
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
@ -20,26 +18,6 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
alias Pleroma.Web.OStatus alias Pleroma.Web.OStatus
alias Pleroma.Web.WebFinger 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 def help_test(conn, _params) do
json(conn, "ok") json(conn, "ok")
end end

View 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

View 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