Commit a0c4ebb4 authored by Maksim's avatar Maksim Committed by kaniini

[#184] small refactoring reset password

parent 1ab8a9e4
...@@ -204,9 +204,9 @@ def run(["reset_password", nickname]) do ...@@ -204,9 +204,9 @@ def run(["reset_password", nickname]) 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
) )
}" }"
......
...@@ -23,13 +23,8 @@ defp recipient(email, nil), do: email ...@@ -23,13 +23,8 @@ defp recipient(email, nil), do: email
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>
......
...@@ -37,6 +37,7 @@ def used_changeset(struct) do ...@@ -37,6 +37,7 @@ def used_changeset(struct) 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),
......
...@@ -9,6 +9,7 @@ defmodule Pleroma.User do ...@@ -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,29 +195,26 @@ def upgrade_changeset(struct, params \\ %{}) do ...@@ -194,29 +195,26 @@ def upgrade_changeset(struct, params \\ %{}) 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
end
OAuth.Token.delete_user_tokens(struct)
OAuth.Authorization.delete_user_authorizations(struct) def reset_password(%User{id: user_id} = user, data) do
multi =
if changeset.valid? do Multi.new()
hashed = Pbkdf2.hashpwsalt(changeset.changes[:password]) |> Multi.update(:user, password_update_changeset(user, data))
|> Multi.delete_all(:tokens, OAuth.Token.Query.get_by_user(user_id))
changeset |> Multi.delete_all(:auth, OAuth.Authorization.delete_by_user_query(user))
|> put_change(:password_hash, hashed)
else case Repo.transaction(multi) do
changeset {:ok, %{user: user} = _} -> set_cache(user)
{:error, _, changeset, _} -> {:error, changeset}
end end
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 def register_changeset(struct, params \\ %{}, opts \\ []) do
need_confirmation? = need_confirmation? =
if is_nil(opts[:need_confirmation]) do if is_nil(opts[:need_confirmation]) do
...@@ -250,12 +248,11 @@ def register_changeset(struct, params \\ %{}, opts \\ []) do ...@@ -250,12 +248,11 @@ def register_changeset(struct, params \\ %{}, opts \\ []) 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 @@ def get_ap_ids_by_nicknames(nicknames) do ...@@ -1349,4 +1346,12 @@ def get_ap_ids_by_nicknames(nicknames) 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
...@@ -76,14 +76,16 @@ def use_token(%Authorization{used: false, valid_until: valid_until} = auth) do ...@@ -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"} 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
......
...@@ -133,8 +133,8 @@ defmodule Pleroma.Web.Router do ...@@ -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)
......
<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 %>
......
# 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 ...@@ -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 ...@@ -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
......
# 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
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
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment