[#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:
parent
aed0f90287
commit
b096e30cff
|
@ -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")
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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"})
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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})
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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",
|
||||||
|
|
Loading…
Reference in a new issue