forked from AkkomaGang/akkoma
[#184] small refactoring reset password
This commit is contained in:
parent
1ab8a9e4fe
commit
a0c4ebb4d7
14 changed files with 142 additions and 60 deletions
|
@ -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
|
||||||
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
|
||||||
|> 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
|
||||||
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 @@ 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
|
||||||
|
|
||||||
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 @@ 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
|
||||||
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
|
||||||
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
|
||||||
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)
|
||||||
|
|
|
@ -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 %>
|
|
@ -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.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
|
||||||
|
|
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