Merge branch '114_user_registration_emails' into 'develop'

[#114] User registration emails

Closes #114

See merge request pleroma/pleroma!531
This commit is contained in:
href 2018-12-14 09:16:03 +00:00
commit 30dc81667c
13 changed files with 169 additions and 2 deletions

View file

@ -30,6 +30,31 @@ This filter replaces the filename (not the path) of an upload. For complete obfu
* `text`: Text to replace filenames in links. If empty, `{random}.extension` will be used. * `text`: Text to replace filenames in links. If empty, `{random}.extension` will be used.
## Pleroma.Mailer
* `adapter`: one of the mail adapters listed in [Swoosh readme](https://github.com/swoosh/swoosh#adapters), or `Swoosh.Adapters.Local` for in-memory mailbox.
* `api_key` / `password` and / or other adapter-specific settings, per the above documentation.
An example for Sendgrid adapter:
```
config :pleroma, Pleroma.Mailer,
adapter: Swoosh.Adapters.Sendgrid,
api_key: "YOUR_API_KEY"
```
An example for SMTP adapter:
```
config :pleroma, Pleroma.Mailer,
adapter: Swoosh.Adapters.SMTP,
relay: "smtp.gmail.com",
username: "YOUR_USERNAME@gmail.com",
password: "YOUR_SMTP_PASSWORD",
port: 465,
ssl: true,
tls: :always,
auth: :always
```
## :uri_schemes ## :uri_schemes
* `valid_schemes`: List of the scheme part that is considered valid to be an URL * `valid_schemes`: List of the scheme part that is considered valid to be an URL

View file

@ -17,6 +17,8 @@
check_origin: false, check_origin: false,
watchers: [] watchers: []
config :pleroma, Pleroma.Mailer, adapter: Swoosh.Adapters.Local
# ## SSL Support # ## SSL Support
# #
# In order to use HTTPS in development, a self-signed # In order to use HTTPS in development, a self-signed

View file

@ -11,6 +11,8 @@
config :pleroma, Pleroma.Uploaders.Local, uploads: "test/uploads" config :pleroma, Pleroma.Uploaders.Local, uploads: "test/uploads"
config :pleroma, Pleroma.Mailer, adapter: Swoosh.Adapters.Test
# Configure your database # Configure your database
config :pleroma, Pleroma.Repo, config :pleroma, Pleroma.Repo,
adapter: Ecto.Adapters.Postgres, adapter: Ecto.Adapters.Postgres,

View file

@ -0,0 +1,3 @@
defmodule Pleroma.Mailer do
use Swoosh.Mailer, otp_app: :pleroma
end

View file

@ -0,0 +1,40 @@
defmodule Pleroma.UserEmail do
@moduledoc "User emails"
import Swoosh.Email
alias Pleroma.Web.{Endpoint, Router}
defp instance_config, do: Pleroma.Config.get(:instance)
defp instance_name, do: instance_config()[:name]
defp sender do
{instance_name(), instance_config()[:email]}
end
defp recipient(email, nil), do: email
defp recipient(email, name), do: {name, email}
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
)
html_body = """
<h3>Reset your password at #{instance_name()}</h3>
<p>Someone has requested password change for your account at #{instance_name()}.</p>
<p>If it was you, visit the following link to proceed: <a href="#{password_reset_url}">reset password</a>.</p>
<p>If it was someone else, nothing to worry about: your data is secure and your password has not been changed.</p>
"""
new()
|> to(recipient(user.email, user.name))
|> from(sender())
|> subject("Password reset")
|> html_body(html_body)
end
end

View file

@ -85,6 +85,15 @@ defmodule Pleroma.Web.Router do
plug(:accepts, ["html", "json"]) plug(:accepts, ["html", "json"])
end end
pipeline :mailbox_preview do
plug(:accepts, ["html"])
plug(:put_secure_browser_headers, %{
"content-security-policy" =>
"default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline' 'unsafe-eval'"
})
end
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", UtilController, :show_password_reset)
@ -268,6 +277,7 @@ defmodule Pleroma.Web.Router do
get("/statusnet/conversation/:id", TwitterAPI.Controller, :fetch_conversation) get("/statusnet/conversation/:id", TwitterAPI.Controller, :fetch_conversation)
post("/account/register", TwitterAPI.Controller, :register) post("/account/register", TwitterAPI.Controller, :register)
post("/account/password_reset", TwitterAPI.Controller, :password_reset)
get("/search", TwitterAPI.Controller, :search) get("/search", TwitterAPI.Controller, :search)
get("/statusnet/tags/timeline/:tag", TwitterAPI.Controller, :public_and_external_timeline) get("/statusnet/tags/timeline/:tag", TwitterAPI.Controller, :public_and_external_timeline)
@ -424,6 +434,14 @@ defmodule Pleroma.Web.Router do
get("/:sig/:url/:filename", MediaProxyController, :remote) get("/:sig/:url/:filename", MediaProxyController, :remote)
end end
if Mix.env() == :dev do
scope "/dev" do
pipe_through([:mailbox_preview])
forward("/mailbox", Plug.Swoosh.MailboxPreview, base_path: "/dev/mailbox")
end
end
scope "/", Fallback do scope "/", Fallback do
get("/registration/:token", RedirectController, :registration_page) get("/registration/:token", RedirectController, :registration_page)
get("/*path", RedirectController, :redirector) get("/*path", RedirectController, :redirector)

View file

@ -1 +1,2 @@
<h2>Password reset failed</h2> <h2>Password reset failed</h2>
<h3><a href="<%= Pleroma.Web.base_url() %>">Homepage</a></h3>

View file

@ -1 +1,2 @@
<h2>Password changed!</h2> <h2>Password changed!</h2>
<h3><a href="<%= Pleroma.Web.base_url() %>">Homepage</a></h3>

View file

@ -167,6 +167,25 @@ def register_user(params) do
end end
end end
def password_reset(nickname_or_email) do
with true <- is_binary(nickname_or_email),
%User{local: true} = user <- User.get_by_nickname_or_email(nickname_or_email),
{:ok, token_record} <- Pleroma.PasswordResetToken.create_token(user) do
user
|> Pleroma.UserEmail.password_reset_email(token_record.token)
|> Pleroma.Mailer.deliver()
else
false ->
{:error, "bad user identifier"}
%User{local: false} ->
{:error, "remote user"}
nil ->
{:error, "unknown user"}
end
end
def get_by_id_or_nickname(id_or_nickname) do def get_by_id_or_nickname(id_or_nickname) do
if !is_integer(id_or_nickname) && :error == Integer.parse(id_or_nickname) do if !is_integer(id_or_nickname) && :error == Integer.parse(id_or_nickname) do
Repo.get_by(User, nickname: id_or_nickname) Repo.get_by(User, nickname: id_or_nickname)

View file

@ -1,5 +1,8 @@
defmodule Pleroma.Web.TwitterAPI.Controller do defmodule Pleroma.Web.TwitterAPI.Controller do
use Pleroma.Web, :controller use Pleroma.Web, :controller
import Pleroma.Web.ControllerHelper, only: [json_response: 3]
alias Pleroma.Web.TwitterAPI.{TwitterAPI, UserView, ActivityView, NotificationView} alias Pleroma.Web.TwitterAPI.{TwitterAPI, UserView, ActivityView, NotificationView}
alias Pleroma.Web.CommonAPI alias Pleroma.Web.CommonAPI
alias Pleroma.{Repo, Activity, Object, User, Notification} alias Pleroma.{Repo, Activity, Object, User, Notification}
@ -322,6 +325,14 @@ def register(conn, params) do
end end
end end
def password_reset(conn, params) do
nickname_or_email = params["email"] || params["nickname"]
with {:ok, _} <- TwitterAPI.password_reset(nickname_or_email) do
json_response(conn, :no_content, "")
end
end
def update_avatar(%{assigns: %{user: user}} = conn, params) do def update_avatar(%{assigns: %{user: user}} = conn, params) do
{:ok, object} = ActivityPub.upload(params, type: :avatar) {:ok, object} = ActivityPub.upload(params, type: :avatar)
change = Changeset.change(user, %{avatar: object.data}) change = Changeset.change(user, %{avatar: object.data})

View file

@ -75,7 +75,9 @@ defp deps do
git: "https://github.com/msantos/crypt", ref: "1f2b58927ab57e72910191a7ebaeff984382a1d3"}, git: "https://github.com/msantos/crypt", ref: "1f2b58927ab57e72910191a7ebaeff984382a1d3"},
{:cors_plug, "~> 1.5"}, {:cors_plug, "~> 1.5"},
{:ex_doc, "> 0.18.3 and < 0.20.0", only: :dev, runtime: false}, {:ex_doc, "> 0.18.3 and < 0.20.0", only: :dev, runtime: false},
{:web_push_encryption, "~> 0.2.1"} {:web_push_encryption, "~> 0.2.1"},
{:swoosh, "~> 0.20"},
{:gen_smtp, "~> 0.13"}
] ]
end end

View file

@ -20,6 +20,7 @@
"ex_aws_s3": {:hex, :ex_aws_s3, "2.0.1", "9e09366e77f25d3d88c5393824e613344631be8db0d1839faca49686e99b6704", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm"}, "ex_aws_s3": {:hex, :ex_aws_s3, "2.0.1", "9e09366e77f25d3d88c5393824e613344631be8db0d1839faca49686e99b6704", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm"},
"ex_doc": {:hex, :ex_doc, "0.19.1", "519bb9c19526ca51d326c060cb1778d4a9056b190086a8c6c115828eaccea6cf", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.7", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"}, "ex_doc": {:hex, :ex_doc, "0.19.1", "519bb9c19526ca51d326c060cb1778d4a9056b190086a8c6c115828eaccea6cf", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.7", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"},
"ex_machina": {:hex, :ex_machina, "2.2.0", "fec496331e04fc2db2a1a24fe317c12c0c4a50d2beb8ebb3531ed1f0d84be0ed", [:mix], [{:ecto, "~> 2.1", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm"}, "ex_machina": {:hex, :ex_machina, "2.2.0", "fec496331e04fc2db2a1a24fe317c12c0c4a50d2beb8ebb3531ed1f0d84be0ed", [:mix], [{:ecto, "~> 2.1", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm"},
"gen_smtp": {:hex, :gen_smtp, "0.13.0", "11f08504c4bdd831dc520b8f84a1dce5ce624474a797394e7aafd3c29f5dcd25", [:rebar3], [], "hexpm"},
"gettext": {:hex, :gettext, "0.15.0", "40a2b8ce33a80ced7727e36768499fc9286881c43ebafccae6bab731e2b2b8ce", [:mix], [], "hexpm"}, "gettext": {:hex, :gettext, "0.15.0", "40a2b8ce33a80ced7727e36768499fc9286881c43ebafccae6bab731e2b2b8ce", [:mix], [], "hexpm"},
"hackney": {:hex, :hackney, "1.13.0", "24edc8cd2b28e1c652593833862435c80661834f6c9344e84b6a2255e7aeef03", [:rebar3], [{:certifi, "2.3.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "5.1.2", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"}, "hackney": {:hex, :hackney, "1.13.0", "24edc8cd2b28e1c652593833862435c80661834f6c9344e84b6a2255e7aeef03", [:rebar3], [{:certifi, "2.3.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "5.1.2", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
"html_sanitize_ex": {:hex, :html_sanitize_ex, "1.3.0", "f005ad692b717691203f940c686208aa3d8ffd9dd4bb3699240096a51fa9564e", [:mix], [{:mochiweb, "~> 2.15", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm"}, "html_sanitize_ex": {:hex, :html_sanitize_ex, "1.3.0", "f005ad692b717691203f940c686208aa3d8ffd9dd4bb3699240096a51fa9564e", [:mix], [{:mochiweb, "~> 2.15", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm"},
@ -49,6 +50,7 @@
"postgrex": {:hex, :postgrex, "0.13.5", "3d931aba29363e1443da167a4b12f06dcd171103c424de15e5f3fc2ba3e6d9c5", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 1.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm"}, "postgrex": {:hex, :postgrex, "0.13.5", "3d931aba29363e1443da167a4b12f06dcd171103c424de15e5f3fc2ba3e6d9c5", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 1.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm"},
"ranch": {:hex, :ranch, "1.3.2", "e4965a144dc9fbe70e5c077c65e73c57165416a901bd02ea899cfd95aa890986", [:rebar3], [], "hexpm"}, "ranch": {:hex, :ranch, "1.3.2", "e4965a144dc9fbe70e5c077c65e73c57165416a901bd02ea899cfd95aa890986", [:rebar3], [], "hexpm"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [:make, :rebar], [], "hexpm"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [:make, :rebar], [], "hexpm"},
"swoosh": {:hex, :swoosh, "0.20.0", "9a6c13822c9815993c03b6f8fccc370fcffb3c158d9754f67b1fdee6b3a5d928", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.12", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 1.1", [hex: :mime, repo: "hexpm", optional: false]}, {:plug, "~> 1.4", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm"},
"tesla": {:hex, :tesla, "1.2.1", "864783cc27f71dd8c8969163704752476cec0f3a51eb3b06393b3971dc9733ff", [:mix], [{:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "~> 4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm"}, "tesla": {:hex, :tesla, "1.2.1", "864783cc27f71dd8c8969163704752476cec0f3a51eb3b06393b3971dc9733ff", [:mix], [{:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "~> 4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm"},
"trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, "trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
"tzdata": {:hex, :tzdata, "0.5.17", "50793e3d85af49736701da1a040c415c97dc1caf6464112fd9bd18f425d3053b", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"}, "tzdata": {:hex, :tzdata, "0.5.17", "50793e3d85af49736701da1a040c415c97dc1caf6464112fd9bd18f425d3053b", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},

View file

@ -9,6 +9,7 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
alias Pleroma.Web.CommonAPI alias Pleroma.Web.CommonAPI
alias Pleroma.Web.TwitterAPI.TwitterAPI alias Pleroma.Web.TwitterAPI.TwitterAPI
alias Comeonin.Pbkdf2 alias Comeonin.Pbkdf2
alias Ecto.Changeset
import Pleroma.Factory import Pleroma.Factory
@ -270,7 +271,7 @@ test "with credentials", %{conn: conn, user: current_user} do
since_id = List.last(activities).id since_id = List.last(activities).id
current_user = current_user =
Ecto.Changeset.change(current_user, following: [User.ap_followers(user)]) Changeset.change(current_user, following: [User.ap_followers(user)])
|> Repo.update!() |> Repo.update!()
conn = conn =
@ -832,6 +833,46 @@ test "it returns errors on a problem", %{conn: conn} do
end end
end end
describe "POST /api/account/password_reset, with valid parameters" do
setup %{conn: conn} do
user = insert(:user)
conn = post(conn, "/api/account/password_reset?email=#{user.email}")
%{conn: conn, user: user}
end
test "it returns 204", %{conn: conn} do
assert json_response(conn, :no_content)
end
test "it creates a PasswordResetToken record for user", %{user: user} do
token_record = Repo.get_by(Pleroma.PasswordResetToken, user_id: user.id)
assert token_record
end
test "it sends an email to user", %{user: user} do
token_record = Repo.get_by(Pleroma.PasswordResetToken, user_id: user.id)
Swoosh.TestAssertions.assert_email_sent(
Pleroma.UserEmail.password_reset_email(user, token_record.token)
)
end
end
describe "POST /api/account/password_reset, with invalid parameters" do
setup [:valid_user]
test "it returns 500 when user is not found", %{conn: conn, user: user} do
conn = post(conn, "/api/account/password_reset?email=nonexisting_#{user.email}")
assert json_response(conn, :internal_server_error)
end
test "it returns 500 when user is not local", %{conn: conn, user: user} do
{:ok, user} = Repo.update(Changeset.change(user, local: false))
conn = post(conn, "/api/account/password_reset?email=#{user.email}")
assert json_response(conn, :internal_server_error)
end
end
describe "GET /api/externalprofile/show" do describe "GET /api/externalprofile/show" do
test "it returns the user", %{conn: conn} do test "it returns the user", %{conn: conn} do
user = insert(:user) user = insert(:user)