[#114] Added email confirmation resend action. Added tests

for registration, authentication, email confirmation, confirmation resending.
Made admin methods create confirmed users.
This commit is contained in:
Ivan Tashkinov 2018-12-18 17:13:52 +03:00
parent aed0f90287
commit b096e30cff
9 changed files with 230 additions and 16 deletions

View file

@ -103,8 +103,8 @@ def run(["new", nickname, email | rest]) do
bio: bio bio: bio
} }
user = User.register_changeset(%User{}, params) changeset = User.register_changeset(%User{}, params, confirmed: true)
Repo.insert!(user) {:ok, _user} = User.register(changeset)
Mix.shell().info("User #{nickname} created") Mix.shell().info("User #{nickname} created")

View file

@ -74,13 +74,15 @@ def follow_changeset(struct, params \\ %{}) do
def user_info(%User{} = user) do def user_info(%User{} = user) do
oneself = if user.local, do: 1, else: 0 oneself = if user.local, do: 1, else: 0
user_info = user.info
%{ %{
following_count: length(user.following) - oneself, following_count: length(user.following) - oneself,
note_count: user.info.note_count, note_count: user_info.note_count,
follower_count: user.info.follower_count, follower_count: user_info.follower_count,
locked: user.info.locked, locked: user_info.locked,
default_scope: user.info.default_scope confirmation_pending: user_info.confirmation_pending,
default_scope: user_info.default_scope
} }
end end
@ -209,14 +211,18 @@ def register_changeset(struct, params \\ %{}, opts \\ []) do
@doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)" @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
def register(%Ecto.Changeset{} = changeset) do def register(%Ecto.Changeset{} = changeset) do
with {:ok, user} <- Repo.insert(changeset) do with {:ok, user} <- Repo.insert(changeset) do
{:ok, _} = try_send_confirmation_email(user)
{:ok, user}
end
end
def try_send_confirmation_email(%User{} = user) do
if user.info.confirmation_pending do if user.info.confirmation_pending do
{:ok, _} =
user user
|> Pleroma.UserEmail.account_confirmation_email() |> Pleroma.UserEmail.account_confirmation_email()
|> Pleroma.Mailer.deliver() |> Pleroma.Mailer.deliver()
end else
{:ok, :noop}
{:ok, user}
end end
end end

View file

@ -31,7 +31,7 @@ def create_authorization(conn, %{
}) do }) do
with %User{} = user <- User.get_by_nickname_or_email(name), with %User{} = user <- User.get_by_nickname_or_email(name),
true <- Pbkdf2.checkpw(password, user.password_hash), true <- Pbkdf2.checkpw(password, user.password_hash),
true <- User.auth_active?(user), {:auth_active, true} <- {:auth_active, User.auth_active?(user)},
%App{} = app <- Repo.get_by(App, client_id: client_id), %App{} = app <- Repo.get_by(App, client_id: client_id),
{:ok, auth} <- Authorization.create_authorization(app, user) do {:ok, auth} <- Authorization.create_authorization(app, user) do
# Special case: Local MastodonFE. # Special case: Local MastodonFE.
@ -64,6 +64,15 @@ def create_authorization(conn, %{
redirect(conn, external: url) redirect(conn, external: url)
end end
else
{:auth_active, false} ->
conn
|> put_flash(:error, "Account confirmation pending")
|> put_status(:forbidden)
|> authorize(params)
error ->
error
end end
end end
@ -102,7 +111,7 @@ def token_exchange(
with %App{} = app <- get_app_from_request(conn, params), with %App{} = app <- get_app_from_request(conn, params),
%User{} = user <- User.get_by_nickname_or_email(name), %User{} = user <- User.get_by_nickname_or_email(name),
true <- Pbkdf2.checkpw(password, user.password_hash), true <- Pbkdf2.checkpw(password, user.password_hash),
true <- User.auth_active?(user), {:auth_active, true} <- {:auth_active, User.auth_active?(user)},
{:ok, auth} <- Authorization.create_authorization(app, user), {:ok, auth} <- Authorization.create_authorization(app, user),
{:ok, token} <- Token.exchange_token(app, auth) do {:ok, token} <- Token.exchange_token(app, auth) do
response = %{ response = %{
@ -115,6 +124,11 @@ def token_exchange(
json(conn, response) json(conn, response)
else else
{:auth_active, false} ->
conn
|> put_status(:forbidden)
|> json(%{error: "Account confirmation pending"})
_error -> _error ->
put_status(conn, 400) put_status(conn, 400)
|> json(%{error: "Invalid credentials"}) |> json(%{error: "Invalid credentials"})

View file

@ -284,6 +284,8 @@ defmodule Pleroma.Web.Router do
get("/account/confirm_email/:token", TwitterAPI.Controller, :confirm_email, as: :confirm_email) get("/account/confirm_email/:token", TwitterAPI.Controller, :confirm_email, as: :confirm_email)
post("/account/resend_confirmation_email", TwitterAPI.Controller, :resend_confirmation_email)
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)
end end

View file

@ -13,7 +13,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
require Logger require Logger
plug(:only_if_public_instance when action in [:public_timeline, :public_and_external_timeline]) plug(:only_if_public_instance when action in [:public_timeline, :public_and_external_timeline])
plug(:fetch_flash when action in [:confirm_email]) plug(:fetch_flash when action in [:confirm_email, :resend_confirmation_email])
action_fallback(:errors) action_fallback(:errors)
def verify_credentials(%{assigns: %{user: user}} = conn, _params) do def verify_credentials(%{assigns: %{user: user}} = conn, _params) do
@ -385,6 +385,17 @@ def confirm_email(conn, %{"token" => token}) do
end end
end end
def resend_confirmation_email(conn, params) do
nickname_or_email = params["email"] || params["nickname"]
with %User{} = user <- User.get_by_nickname_or_email(nickname_or_email),
{:ok, _} <- User.try_send_confirmation_email(user) do
conn
|> put_flash(:info, "Email confirmation has been sent.")
|> json_response(: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

@ -177,6 +177,48 @@ test "it ensures info is not nil" do
end end
end end
describe "user registration, with :account_activation_required" do
@full_user_data %{
bio: "A guy",
name: "my name",
nickname: "nick",
password: "test",
password_confirmation: "test",
email: "email@example.com"
}
setup do
setting = Pleroma.Config.get([:instance, :account_activation_required])
unless setting do
Pleroma.Config.put([:instance, :account_activation_required], true)
on_exit(fn -> Pleroma.Config.put([:instance, :account_activation_required], setting) end)
end
:ok
end
test "it creates unconfirmed user" do
changeset = User.register_changeset(%User{}, @full_user_data)
assert changeset.valid?
{:ok, user} = Repo.insert(changeset)
assert user.info.confirmation_pending
assert user.info.confirmation_token
end
test "it creates confirmed user if :confirmed option is given" do
changeset = User.register_changeset(%User{}, @full_user_data, confirmed: true)
assert changeset.valid?
{:ok, user} = Repo.insert(changeset)
refute user.info.confirmation_pending
refute user.info.confirmation_token
end
end
describe "get_or_fetch/1" do describe "get_or_fetch/1" do
test "gets an existing user by nickname" do test "gets an existing user by nickname" do
user = insert(:user) user = insert(:user)

View file

@ -50,6 +50,26 @@ test "issues a token for an all-body request" do
assert Repo.get_by(Token, token: token) assert Repo.get_by(Token, token: token)
end end
test "issues a token for `password` grant_type with valid credentials" do
password = "testpassword"
user = insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt(password))
app = insert(:oauth_app)
conn =
build_conn()
|> post("/oauth/token", %{
"grant_type" => "password",
"username" => user.nickname,
"password" => password,
"client_id" => app.client_id,
"client_secret" => app.client_secret
})
assert %{"access_token" => token} = json_response(conn, 200)
assert Repo.get_by(Token, token: token)
end
test "issues a token for request with HTTP basic auth client credentials" do test "issues a token for request with HTTP basic auth client credentials" do
user = insert(:user) user = insert(:user)
app = insert(:oauth_app) app = insert(:oauth_app)
@ -93,6 +113,36 @@ test "rejects token exchange with invalid client credentials" do
refute Map.has_key?(resp, "access_token") refute Map.has_key?(resp, "access_token")
end end
test "rejects token exchange for valid credentials belonging to unconfirmed user" do
password = "testpassword"
user = insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt(password))
info_change = Pleroma.User.Info.confirmation_update(user.info, :unconfirmed)
{:ok, user} =
user
|> Ecto.Changeset.change()
|> Ecto.Changeset.put_embed(:info, info_change)
|> Repo.update()
refute Pleroma.User.auth_active?(user)
app = insert(:oauth_app)
conn =
build_conn()
|> post("/oauth/token", %{
"grant_type" => "password",
"username" => user.nickname,
"password" => password,
"client_id" => app.client_id,
"client_secret" => app.client_secret
})
assert resp = json_response(conn, 403)
assert %{"error" => _} = resp
refute Map.has_key?(resp, "access_token")
end
test "rejects an invalid authorization code" do test "rejects an invalid authorization code" do
app = insert(:oauth_app) app = insert(:oauth_app)

View file

@ -873,6 +873,70 @@ test "it returns 500 when user is not local", %{conn: conn, user: user} do
end end
end end
describe "GET /api/account/confirm_email/:token" do
setup do
user = insert(:user)
info_change = User.Info.confirmation_update(user.info, :unconfirmed)
{:ok, user} =
user
|> Changeset.change()
|> Changeset.put_embed(:info, info_change)
|> Repo.update()
assert user.info.confirmation_pending
[user: user]
end
test "it redirects to root url", %{conn: conn, user: user} do
conn = get(conn, "/api/account/confirm_email/#{user.info.confirmation_token}")
assert 302 == conn.status
end
test "it confirms the user account", %{conn: conn, user: user} do
get(conn, "/api/account/confirm_email/#{user.info.confirmation_token}")
user = Repo.get(User, user.id)
refute user.info.confirmation_pending
refute user.info.confirmation_token
end
end
describe "POST /api/account/resend_confirmation_email" do
setup do
user = insert(:user)
info_change = User.Info.confirmation_update(user.info, :unconfirmed)
{:ok, user} =
user
|> Changeset.change()
|> Changeset.put_embed(:info, info_change)
|> Repo.update()
assert user.info.confirmation_pending
[user: user]
end
test "it returns 204 No Content", %{conn: conn, user: user} do
conn
|> assign(:user, user)
|> post("/api/account/resend_confirmation_email?email=#{user.email}")
|> json_response(:no_content)
end
test "it sends confirmation email", %{conn: conn, user: user} do
conn
|> assign(:user, user)
|> post("/api/account/resend_confirmation_email?email=#{user.email}")
Swoosh.TestAssertions.assert_email_sent(Pleroma.UserEmail.account_confirmation_email(user))
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)

View file

@ -275,6 +275,31 @@ test "it registers a new user with empty string in bio and returns the user." do
UserView.render("show.json", %{user: fetched_user}) UserView.render("show.json", %{user: fetched_user})
end end
@moduletag skip: "needs 'account_activation_required: true' in config"
test "it sends confirmation email if :account_activation_required is specified in instance config" do
setting = Pleroma.Config.get([:instance, :account_activation_required])
unless setting do
Pleroma.Config.put([:instance, :account_activation_required], true)
on_exit(fn -> Pleroma.Config.put([:instance, :account_activation_required], setting) end)
end
data = %{
"nickname" => "lain",
"email" => "lain@wired.jp",
"fullname" => "lain iwakura",
"bio" => "",
"password" => "bear",
"confirm" => "bear"
}
{:ok, user} = TwitterAPI.register_user(data)
assert user.info.confirmation_pending
Swoosh.TestAssertions.assert_email_sent(Pleroma.UserEmail.account_confirmation_email(user))
end
test "it registers a new user and parses mentions in the bio" do test "it registers a new user and parses mentions in the bio" do
data1 = %{ data1 = %{
"nickname" => "john", "nickname" => "john",