Merge branch '114_email_confirmation' into 'develop'
[#114] Email confirmation See merge request pleroma/pleroma!546
This commit is contained in:
commit
2e2030ada8
|
@ -69,6 +69,7 @@ config :pleroma, Pleroma.Mailer,
|
||||||
* `banner_upload_limit`: File size limit of user’s profile banners
|
* `banner_upload_limit`: File size limit of user’s profile banners
|
||||||
* `registrations_open`: Enable registrations for anyone, invitations can be enabled when false.
|
* `registrations_open`: Enable registrations for anyone, invitations can be enabled when false.
|
||||||
* `invites_enabled`: Enable user invitations for admins (depends on `registrations_open: false`).
|
* `invites_enabled`: Enable user invitations for admins (depends on `registrations_open: false`).
|
||||||
|
* `account_activation_required`: Require users to confirm their emails before signing in.
|
||||||
* `federating`: Enable federation with other instances
|
* `federating`: Enable federation with other instances
|
||||||
* `allow_relay`: Enable Pleroma’s Relay, which makes it possible to follow a whole instance
|
* `allow_relay`: Enable Pleroma’s Relay, which makes it possible to follow a whole instance
|
||||||
* `rewrite_policy`: Message Rewrite Policy, either one or a list. Here are the ones available by default:
|
* `rewrite_policy`: Message Rewrite Policy, either one or a list. Here are the ones available by default:
|
||||||
|
|
|
@ -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")
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@ defp sender do
|
||||||
|
|
||||||
defp recipient(email, nil), do: email
|
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)
|
||||||
|
|
||||||
def password_reset_email(user, password_reset_token) when is_binary(password_reset_token) do
|
def password_reset_email(user, password_reset_token) when is_binary(password_reset_token) do
|
||||||
password_reset_url =
|
password_reset_url =
|
||||||
|
@ -32,7 +33,7 @@ def password_reset_email(user, password_reset_token) when is_binary(password_res
|
||||||
"""
|
"""
|
||||||
|
|
||||||
new()
|
new()
|
||||||
|> to(recipient(user.email, user.name))
|
|> to(recipient(user))
|
||||||
|> from(sender())
|
|> from(sender())
|
||||||
|> subject("Password reset")
|
|> subject("Password reset")
|
||||||
|> html_body(html_body)
|
|> html_body(html_body)
|
||||||
|
@ -63,4 +64,26 @@ def user_invitation_email(
|
||||||
|> subject("Invitation to #{instance_name()}")
|
|> subject("Invitation to #{instance_name()}")
|
||||||
|> html_body(html_body)
|
|> html_body(html_body)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def account_confirmation_email(user) do
|
||||||
|
confirmation_url =
|
||||||
|
Router.Helpers.confirm_email_url(
|
||||||
|
Endpoint,
|
||||||
|
:confirm_email,
|
||||||
|
user.id,
|
||||||
|
to_string(user.info.confirmation_token)
|
||||||
|
)
|
||||||
|
|
||||||
|
html_body = """
|
||||||
|
<h3>Welcome to #{instance_name()}!</h3>
|
||||||
|
<p>Email confirmation is required to activate the account.</p>
|
||||||
|
<p>Click the following link to proceed: <a href="#{confirmation_url}">activate your account</a>.</p>
|
||||||
|
"""
|
||||||
|
|
||||||
|
new()
|
||||||
|
|> to(recipient(user))
|
||||||
|
|> from(sender())
|
||||||
|
|> subject("#{instance_name()} account confirmation")
|
||||||
|
|> html_body(html_body)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -38,6 +38,13 @@ defmodule Pleroma.User do
|
||||||
timestamps()
|
timestamps()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def auth_active?(%User{} = user) do
|
||||||
|
(user.info && !user.info.confirmation_pending) ||
|
||||||
|
!Pleroma.Config.get([:instance, :account_activation_required])
|
||||||
|
end
|
||||||
|
|
||||||
|
def superuser?(%User{} = user), do: user.info && User.Info.superuser?(user.info)
|
||||||
|
|
||||||
def avatar_url(user) do
|
def avatar_url(user) do
|
||||||
case user.avatar do
|
case user.avatar do
|
||||||
%{"url" => [%{"href" => href} | _]} -> href
|
%{"url" => [%{"href" => href} | _]} -> href
|
||||||
|
@ -78,6 +85,7 @@ def user_info(%User{} = user) do
|
||||||
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,
|
||||||
|
confirmation_pending: user.info.confirmation_pending,
|
||||||
default_scope: user.info.default_scope
|
default_scope: user.info.default_scope
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
@ -168,7 +176,16 @@ def reset_password(user, data) do
|
||||||
update_and_set_cache(password_update_changeset(user, data))
|
update_and_set_cache(password_update_changeset(user, data))
|
||||||
end
|
end
|
||||||
|
|
||||||
def register_changeset(struct, params \\ %{}) do
|
def register_changeset(struct, params \\ %{}, opts \\ []) do
|
||||||
|
confirmation_status =
|
||||||
|
if opts[:confirmed] || !Pleroma.Config.get([:instance, :account_activation_required]) do
|
||||||
|
:confirmed
|
||||||
|
else
|
||||||
|
:unconfirmed
|
||||||
|
end
|
||||||
|
|
||||||
|
info_change = User.Info.confirmation_changeset(%User.Info{}, confirmation_status)
|
||||||
|
|
||||||
changeset =
|
changeset =
|
||||||
struct
|
struct
|
||||||
|> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation])
|
|> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation])
|
||||||
|
@ -180,7 +197,7 @@ def register_changeset(struct, params \\ %{}) do
|
||||||
|> validate_format(:email, @email_regex)
|
|> validate_format(:email, @email_regex)
|
||||||
|> validate_length(:bio, max: 1000)
|
|> validate_length(:bio, max: 1000)
|
||||||
|> validate_length(:name, min: 1, max: 100)
|
|> validate_length(:name, min: 1, max: 100)
|
||||||
|> put_change(:info, %Pleroma.User.Info{})
|
|> put_change(:info, info_change)
|
||||||
|
|
||||||
if changeset.valid? do
|
if changeset.valid? do
|
||||||
hashed = Pbkdf2.hashpwsalt(changeset.changes[:password])
|
hashed = Pbkdf2.hashpwsalt(changeset.changes[:password])
|
||||||
|
@ -197,6 +214,25 @@ def register_changeset(struct, params \\ %{}) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
|
||||||
|
def register(%Ecto.Changeset{} = changeset) do
|
||||||
|
with {:ok, user} <- Repo.insert(changeset),
|
||||||
|
{:ok, _} = try_send_confirmation_email(user) do
|
||||||
|
{:ok, user}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def try_send_confirmation_email(%User{} = user) do
|
||||||
|
if user.info.confirmation_pending &&
|
||||||
|
Pleroma.Config.get([:instance, :account_activation_required]) do
|
||||||
|
user
|
||||||
|
|> Pleroma.UserEmail.account_confirmation_email()
|
||||||
|
|> Pleroma.Mailer.deliver()
|
||||||
|
else
|
||||||
|
{:ok, :noop}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def needs_update?(%User{local: true}), do: false
|
def needs_update?(%User{local: true}), do: false
|
||||||
|
|
||||||
def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
|
def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
|
||||||
|
|
|
@ -9,6 +9,8 @@ defmodule Pleroma.User.Info do
|
||||||
field(:note_count, :integer, default: 0)
|
field(:note_count, :integer, default: 0)
|
||||||
field(:follower_count, :integer, default: 0)
|
field(:follower_count, :integer, default: 0)
|
||||||
field(:locked, :boolean, default: false)
|
field(:locked, :boolean, default: false)
|
||||||
|
field(:confirmation_pending, :boolean, default: false)
|
||||||
|
field(:confirmation_token, :string, default: nil)
|
||||||
field(:default_scope, :string, default: "public")
|
field(:default_scope, :string, default: "public")
|
||||||
field(:blocks, {:array, :string}, default: [])
|
field(:blocks, {:array, :string}, default: [])
|
||||||
field(:domain_blocks, {:array, :string}, default: [])
|
field(:domain_blocks, {:array, :string}, default: [])
|
||||||
|
@ -35,6 +37,8 @@ defmodule Pleroma.User.Info do
|
||||||
# subject _> Where is this used?
|
# subject _> Where is this used?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def superuser?(info), do: info.is_admin || info.is_moderator
|
||||||
|
|
||||||
def set_activation_status(info, deactivated) do
|
def set_activation_status(info, deactivated) do
|
||||||
params = %{deactivated: deactivated}
|
params = %{deactivated: deactivated}
|
||||||
|
|
||||||
|
@ -141,6 +145,24 @@ def profile_update(info, params) do
|
||||||
])
|
])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def confirmation_changeset(info, :confirmed) do
|
||||||
|
confirmation_changeset(info, %{
|
||||||
|
confirmation_pending: false,
|
||||||
|
confirmation_token: nil
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
def confirmation_changeset(info, :unconfirmed) do
|
||||||
|
confirmation_changeset(info, %{
|
||||||
|
confirmation_pending: true,
|
||||||
|
confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
def confirmation_changeset(info, params) do
|
||||||
|
cast(info, params, [:confirmation_pending, :confirmation_token])
|
||||||
|
end
|
||||||
|
|
||||||
def mastodon_profile_update(info, params) do
|
def mastodon_profile_update(info, params) do
|
||||||
info
|
info
|
||||||
|> cast(params, [
|
|> cast(params, [
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
||||||
use Pleroma.Web, :controller
|
use Pleroma.Web, :controller
|
||||||
alias Pleroma.{User, Repo}
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.ActivityPub.Relay
|
alias Pleroma.Web.ActivityPub.Relay
|
||||||
|
|
||||||
import Pleroma.Web.ControllerHelper, only: [json_response: 3]
|
import Pleroma.Web.ControllerHelper, only: [json_response: 3]
|
||||||
|
@ -26,7 +26,7 @@ def user_create(
|
||||||
conn,
|
conn,
|
||||||
%{"nickname" => nickname, "email" => email, "password" => password}
|
%{"nickname" => nickname, "email" => email, "password" => password}
|
||||||
) do
|
) do
|
||||||
new_user = %{
|
user_data = %{
|
||||||
nickname: nickname,
|
nickname: nickname,
|
||||||
name: nickname,
|
name: nickname,
|
||||||
email: email,
|
email: email,
|
||||||
|
@ -35,11 +35,11 @@ def user_create(
|
||||||
bio: "."
|
bio: "."
|
||||||
}
|
}
|
||||||
|
|
||||||
User.register_changeset(%User{}, new_user)
|
changeset = User.register_changeset(%User{}, user_data, confirmed: true)
|
||||||
|> Repo.insert!()
|
{:ok, user} = User.register(changeset)
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> json(new_user.nickname)
|
|> json(user.nickname)
|
||||||
end
|
end
|
||||||
|
|
||||||
def tag_users(conn, %{"nicknames" => nicknames, "tags" => tags}) do
|
def tag_users(conn, %{"nicknames" => nicknames, "tags" => tags}) do
|
||||||
|
|
|
@ -110,7 +110,8 @@ def verify_credentials(%{assigns: %{user: user}} = conn, _) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def user(%{assigns: %{user: for_user}} = conn, %{"id" => id}) do
|
def user(%{assigns: %{user: for_user}} = conn, %{"id" => id}) do
|
||||||
with %User{} = user <- Repo.get(User, id) do
|
with %User{} = user <- Repo.get(User, id),
|
||||||
|
true <- User.auth_active?(user) || user.id == for_user.id || User.superuser?(for_user) do
|
||||||
account = AccountView.render("account.json", %{user: user, for: for_user})
|
account = AccountView.render("account.json", %{user: user, for: for_user})
|
||||||
json(conn, account)
|
json(conn, account)
|
||||||
else
|
else
|
||||||
|
|
|
@ -62,6 +62,7 @@ def render("account.json", %{user: user} = opts) do
|
||||||
|
|
||||||
# Pleroma extension
|
# Pleroma extension
|
||||||
pleroma: %{
|
pleroma: %{
|
||||||
|
confirmation_pending: user_info.confirmation_pending,
|
||||||
tags: user.tags
|
tags: user.tags
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -132,6 +132,7 @@ def nodeinfo(conn, %{"version" => "2.0"}) do
|
||||||
banner: Keyword.get(instance, :banner_upload_limit),
|
banner: Keyword.get(instance, :banner_upload_limit),
|
||||||
background: Keyword.get(instance, :background_upload_limit)
|
background: Keyword.get(instance, :background_upload_limit)
|
||||||
},
|
},
|
||||||
|
accountActivationRequired: Keyword.get(instance, :account_activation_required, false),
|
||||||
invitesEnabled: Keyword.get(instance, :invites_enabled, false),
|
invitesEnabled: Keyword.get(instance, :invites_enabled, false),
|
||||||
features: features
|
features: features
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +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),
|
||||||
|
{: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.
|
||||||
|
@ -63,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
|
||||||
|
|
||||||
|
@ -101,6 +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),
|
||||||
|
{: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 = %{
|
||||||
|
@ -113,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"})
|
||||||
|
|
|
@ -283,6 +283,15 @@ defmodule Pleroma.Web.Router do
|
||||||
post("/account/register", TwitterAPI.Controller, :register)
|
post("/account/register", TwitterAPI.Controller, :register)
|
||||||
post("/account/password_reset", TwitterAPI.Controller, :password_reset)
|
post("/account/password_reset", TwitterAPI.Controller, :password_reset)
|
||||||
|
|
||||||
|
get(
|
||||||
|
"/account/confirm_email/:user_id/: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
|
||||||
|
|
|
@ -174,6 +174,8 @@ def config(conn, _params) do
|
||||||
closed: if(Keyword.get(instance, :registrations_open), do: "0", else: "1"),
|
closed: if(Keyword.get(instance, :registrations_open), do: "0", else: "1"),
|
||||||
private: if(Keyword.get(instance, :public, true), do: "0", else: "1"),
|
private: if(Keyword.get(instance, :public, true), do: "0", else: "1"),
|
||||||
vapidPublicKey: vapid_public_key,
|
vapidPublicKey: vapid_public_key,
|
||||||
|
accountActivationRequired:
|
||||||
|
if(Keyword.get(instance, :account_activation_required, false), do: "1", else: "0"),
|
||||||
invitesEnabled: if(Keyword.get(instance, :invites_enabled, false), do: "1", else: "0")
|
invitesEnabled: if(Keyword.get(instance, :invites_enabled, false), do: "1", else: "0")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
|
defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
|
||||||
alias Pleroma.{UserInviteToken, User, Activity, Repo, Object}
|
alias Pleroma.{UserInviteToken, User, Activity, Repo, Object}
|
||||||
|
alias Pleroma.{UserEmail, Mailer}
|
||||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
alias Pleroma.Web.TwitterAPI.UserView
|
alias Pleroma.Web.TwitterAPI.UserView
|
||||||
alias Pleroma.Web.CommonAPI
|
alias Pleroma.Web.CommonAPI
|
||||||
|
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
|
|
||||||
def create_status(%User{} = user, %{"status" => _} = data) do
|
def create_status(%User{} = user, %{"status" => _} = data) do
|
||||||
|
@ -161,10 +163,11 @@ def register_user(params) do
|
||||||
|
|
||||||
cond do
|
cond do
|
||||||
registrations_open || (!is_nil(token) && !token.used) ->
|
registrations_open || (!is_nil(token) && !token.used) ->
|
||||||
changeset = User.register_changeset(%User{info: %{}}, params)
|
changeset = User.register_changeset(%User{}, params)
|
||||||
|
|
||||||
with {:ok, user} <- Repo.insert(changeset) do
|
with {:ok, user} <- User.register(changeset) do
|
||||||
!registrations_open && UserInviteToken.mark_as_used(token.token)
|
!registrations_open && UserInviteToken.mark_as_used(token.token)
|
||||||
|
|
||||||
{:ok, user}
|
{:ok, user}
|
||||||
else
|
else
|
||||||
{:error, changeset} ->
|
{:error, changeset} ->
|
||||||
|
@ -189,8 +192,8 @@ def password_reset(nickname_or_email) do
|
||||||
%User{local: true} = user <- User.get_by_nickname_or_email(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
|
{:ok, token_record} <- Pleroma.PasswordResetToken.create_token(user) do
|
||||||
user
|
user
|
||||||
|> Pleroma.UserEmail.password_reset_email(token_record.token)
|
|> UserEmail.password_reset_email(token_record.token)
|
||||||
|> Pleroma.Mailer.deliver()
|
|> Mailer.deliver()
|
||||||
else
|
else
|
||||||
false ->
|
false ->
|
||||||
{:error, "bad user identifier"}
|
{:error, "bad user identifier"}
|
||||||
|
|
|
@ -96,10 +96,15 @@ def friends_timeline(%{assigns: %{user: user}} = conn, params) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def show_user(conn, params) do
|
def show_user(conn, params) do
|
||||||
with {:ok, shown} <- TwitterAPI.get_user(params) do
|
for_user = conn.assigns.user
|
||||||
|
|
||||||
|
with {:ok, shown} <- TwitterAPI.get_user(params),
|
||||||
|
true <-
|
||||||
|
User.auth_active?(shown) ||
|
||||||
|
(for_user && (for_user.id == shown.id || User.superuser?(for_user))) do
|
||||||
params =
|
params =
|
||||||
if user = conn.assigns.user do
|
if for_user do
|
||||||
%{user: shown, for: user}
|
%{user: shown, for: for_user}
|
||||||
else
|
else
|
||||||
%{user: shown}
|
%{user: shown}
|
||||||
end
|
end
|
||||||
|
@ -110,6 +115,11 @@ def show_user(conn, params) do
|
||||||
else
|
else
|
||||||
{:error, msg} ->
|
{:error, msg} ->
|
||||||
bad_request_reply(conn, msg)
|
bad_request_reply(conn, msg)
|
||||||
|
|
||||||
|
false ->
|
||||||
|
conn
|
||||||
|
|> put_status(404)
|
||||||
|
|> json(%{error: "Unconfirmed user"})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -372,6 +382,29 @@ def password_reset(conn, params) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def confirm_email(conn, %{"user_id" => uid, "token" => token}) do
|
||||||
|
with %User{} = user <- Repo.get(User, uid),
|
||||||
|
true <- user.local,
|
||||||
|
true <- user.info.confirmation_pending,
|
||||||
|
true <- user.info.confirmation_token == token,
|
||||||
|
info_change <- User.Info.confirmation_changeset(user.info, :confirmed),
|
||||||
|
changeset <- Changeset.change(user) |> Changeset.put_embed(:info, info_change),
|
||||||
|
{:ok, _} <- User.update_and_set_cache(changeset) do
|
||||||
|
conn
|
||||||
|
|> redirect(to: "/")
|
||||||
|
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
|
||||||
|
|> 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})
|
||||||
|
|
|
@ -81,6 +81,7 @@ def render("user.json", %{user: user = %User{}} = assigns) do
|
||||||
|
|
||||||
# Pleroma extension
|
# Pleroma extension
|
||||||
"pleroma" => %{
|
"pleroma" => %{
|
||||||
|
"confirmation_pending" => user_info.confirmation_pending,
|
||||||
"tags" => user.tags
|
"tags" => user.tags
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -55,7 +55,10 @@ test "Represent a user account" do
|
||||||
privacy: "public",
|
privacy: "public",
|
||||||
sensitive: false
|
sensitive: false
|
||||||
},
|
},
|
||||||
pleroma: %{tags: []}
|
pleroma: %{
|
||||||
|
confirmation_pending: false,
|
||||||
|
tags: []
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
assert expected == AccountView.render("account.json", %{user: user})
|
assert expected == AccountView.render("account.json", %{user: user})
|
||||||
|
@ -93,7 +96,10 @@ test "Represent a Service(bot) account" do
|
||||||
privacy: "public",
|
privacy: "public",
|
||||||
sensitive: false
|
sensitive: false
|
||||||
},
|
},
|
||||||
pleroma: %{tags: []}
|
pleroma: %{
|
||||||
|
confirmation_pending: false,
|
||||||
|
tags: []
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
assert expected == AccountView.render("account.json", %{user: user})
|
assert expected == AccountView.render("account.json", %{user: 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,43 @@ 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 and confirmation is required" 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
|
||||||
|
|
||||||
|
password = "testpassword"
|
||||||
|
user = insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt(password))
|
||||||
|
info_change = Pleroma.User.Info.confirmation_changeset(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,89 @@ test "it returns 500 when user is not local", %{conn: conn, user: user} do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "GET /api/account/confirm_email/:id/:token" do
|
||||||
|
setup do
|
||||||
|
user = insert(:user)
|
||||||
|
info_change = User.Info.confirmation_changeset(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.id}/#{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.id}/#{user.info.confirmation_token}")
|
||||||
|
|
||||||
|
user = Repo.get(User, user.id)
|
||||||
|
|
||||||
|
refute user.info.confirmation_pending
|
||||||
|
refute user.info.confirmation_token
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it returns 500 if user cannot be found by id", %{conn: conn, user: user} do
|
||||||
|
conn = get(conn, "/api/account/confirm_email/0/#{user.info.confirmation_token}")
|
||||||
|
|
||||||
|
assert 500 == conn.status
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it returns 500 if token is invalid", %{conn: conn, user: user} do
|
||||||
|
conn = get(conn, "/api/account/confirm_email/#{user.id}/wrong_token")
|
||||||
|
|
||||||
|
assert 500 == conn.status
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "POST /api/account/resend_confirmation_email" do
|
||||||
|
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
|
||||||
|
|
||||||
|
user = insert(:user)
|
||||||
|
info_change = User.Info.confirmation_changeset(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",
|
||||||
|
|
|
@ -96,7 +96,10 @@ test "A user" do
|
||||||
"default_scope" => "public",
|
"default_scope" => "public",
|
||||||
"no_rich_text" => false,
|
"no_rich_text" => false,
|
||||||
"fields" => [],
|
"fields" => [],
|
||||||
"pleroma" => %{"tags" => []}
|
"pleroma" => %{
|
||||||
|
"confirmation_pending" => false,
|
||||||
|
"tags" => []
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
assert represented == UserView.render("show.json", %{user: user})
|
assert represented == UserView.render("show.json", %{user: user})
|
||||||
|
@ -138,7 +141,10 @@ test "A user for a given other follower", %{user: user} do
|
||||||
"default_scope" => "public",
|
"default_scope" => "public",
|
||||||
"no_rich_text" => false,
|
"no_rich_text" => false,
|
||||||
"fields" => [],
|
"fields" => [],
|
||||||
"pleroma" => %{"tags" => []}
|
"pleroma" => %{
|
||||||
|
"confirmation_pending" => false,
|
||||||
|
"tags" => []
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
assert represented == UserView.render("show.json", %{user: user, for: follower})
|
assert represented == UserView.render("show.json", %{user: user, for: follower})
|
||||||
|
@ -181,7 +187,10 @@ test "A user that follows you", %{user: user} do
|
||||||
"default_scope" => "public",
|
"default_scope" => "public",
|
||||||
"no_rich_text" => false,
|
"no_rich_text" => false,
|
||||||
"fields" => [],
|
"fields" => [],
|
||||||
"pleroma" => %{"tags" => []}
|
"pleroma" => %{
|
||||||
|
"confirmation_pending" => false,
|
||||||
|
"tags" => []
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
assert represented == UserView.render("show.json", %{user: follower, for: user})
|
assert represented == UserView.render("show.json", %{user: follower, for: user})
|
||||||
|
@ -231,7 +240,10 @@ test "A blocked user for the blocker" do
|
||||||
"default_scope" => "public",
|
"default_scope" => "public",
|
||||||
"no_rich_text" => false,
|
"no_rich_text" => false,
|
||||||
"fields" => [],
|
"fields" => [],
|
||||||
"pleroma" => %{"tags" => []}
|
"pleroma" => %{
|
||||||
|
"confirmation_pending" => false,
|
||||||
|
"tags" => []
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
blocker = Repo.get(User, blocker.id)
|
blocker = Repo.get(User, blocker.id)
|
||||||
|
|
Loading…
Reference in a new issue