[#184] small refactoring reset password
authorMaksim <parallel588@gmail.com>
Mon, 24 Jun 2019 19:01:56 +0000 (19:01 +0000)
committerkaniini <nenolod@gmail.com>
Mon, 24 Jun 2019 19:01:56 +0000 (19:01 +0000)
14 files changed:
lib/mix/tasks/pleroma/user.ex
lib/pleroma/emails/user_email.ex
lib/pleroma/password_reset_token.ex [moved from lib/pleroma/PasswordResetToken.ex with 93% similarity]
lib/pleroma/user.ex
lib/pleroma/web/oauth/authorization.ex
lib/pleroma/web/router.ex
lib/pleroma/web/templates/twitter_api/password/invalid_token.html.eex [moved from lib/pleroma/web/templates/twitter_api/util/invalid_token.html.eex with 100% similarity]
lib/pleroma/web/templates/twitter_api/password/reset.html.eex [moved from lib/pleroma/web/templates/twitter_api/util/password_reset.html.eex with 82% similarity]
lib/pleroma/web/templates/twitter_api/password/reset_failed.html.eex [moved from lib/pleroma/web/templates/twitter_api/util/password_reset_failed.html.eex with 100% similarity]
lib/pleroma/web/templates/twitter_api/password/reset_success.html.eex [moved from lib/pleroma/web/templates/twitter_api/util/password_reset_success.html.eex with 100% similarity]
lib/pleroma/web/twitter_api/controllers/password_controller.ex [new file with mode: 0644]
lib/pleroma/web/twitter_api/controllers/util_controller.ex
lib/pleroma/web/twitter_api/views/password_view.ex [new file with mode: 0644]
test/web/twitter_api/password_controller_test.exs [new file with mode: 0644]

index ab158f57ee66b118d9623d0f480a7cf6aadf750b..8a78b4fe663b7a8e792f773716f9b2fb6b4a6caa 100644 (file)
@@ -204,9 +204,9 @@ defmodule Mix.Tasks.Pleroma.User 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
           )
         }"
index 8502a0d0c6d5ab189582fdbc2982f9a7c64d89a2..9346207658f2bc48491b06dfafe81e439004765a 100644 (file)
@@ -23,13 +23,8 @@ defmodule Pleroma.Emails.UserEmail do
   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>
similarity index 93%
rename from lib/pleroma/PasswordResetToken.ex
rename to lib/pleroma/password_reset_token.ex
index f31ea5bc57d6fb7df3c2e3caba579f1a7c796f1c..4a833f6a5ba4909a58a264f20cfffb731bc4cb03 100644 (file)
@@ -37,6 +37,7 @@ defmodule Pleroma.PasswordResetToken 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),
index 1e59a4121bbcb9f84d84066044f0270f1f53752d..f7191762fa97b7a3a2e3183377a44550cbc9b1ad 100644 (file)
@@ -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,29 +195,26 @@ defmodule Pleroma.User 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
+    struct
+    |> cast(params, [:password, :password_confirmation])
+    |> validate_required([:password, :password_confirmation])
+    |> validate_confirmation(:password)
+    |> put_password_hash
+  end
+
+  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 reset_password(user, data) do
-    update_and_set_cache(password_update_changeset(user, data))
-  end
-
   def register_changeset(struct, params \\ %{}, opts \\ []) do
     need_confirmation? =
       if is_nil(opts[:need_confirmation]) do
@@ -250,12 +248,11 @@ defmodule Pleroma.User 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 @@ defmodule Pleroma.User 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
index 18973413ea16b607fee74c8ae88ab76f3ef75831..d53e20d12ea1462dcc05b385da2004bbb857d49d 100644 (file)
@@ -76,14 +76,16 @@ defmodule Pleroma.Web.OAuth.Authorization 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
index 837153ed452547690dfccdf6ba04fa6ec7bb12e3..c504116b6ede5b7a5d7b41e75490cd548c2fdb5a 100644 (file)
@@ -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)
similarity index 82%
rename from lib/pleroma/web/templates/twitter_api/util/password_reset.html.eex
rename to lib/pleroma/web/templates/twitter_api/password/reset.html.eex
index a3facf0172138ac0fc0f88276af77bf64e2fcf96..7d3ef6b0d41791d80b0e827ee1f06321d48c0987 100644 (file)
@@ -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 %>
diff --git a/lib/pleroma/web/twitter_api/controllers/password_controller.ex b/lib/pleroma/web/twitter_api/controllers/password_controller.ex
new file mode 100644 (file)
index 0000000..1941e61
--- /dev/null
@@ -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
index 489170d80b98e48c4f6934c95106d99c5aeb1c97..b1863528f36f3b1df42d5b4972905d3e017e9224 100644 (file)
@@ -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
diff --git a/lib/pleroma/web/twitter_api/views/password_view.ex b/lib/pleroma/web/twitter_api/views/password_view.ex
new file mode 100644 (file)
index 0000000..b166b92
--- /dev/null
@@ -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
diff --git a/test/web/twitter_api/password_controller_test.exs b/test/web/twitter_api/password_controller_test.exs
new file mode 100644 (file)
index 0000000..6b9da82
--- /dev/null
@@ -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