updated error messages for authentication process
This commit is contained in:
parent
52a0bf62f5
commit
108a39c876
5 changed files with 149 additions and 73 deletions
|
@ -11,11 +11,9 @@ def init(options) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def call(%{assigns: %{user: %User{} = user}} = conn, _) do
|
def call(%{assigns: %{user: %User{} = user}} = conn, _) do
|
||||||
if User.auth_active?(user) do
|
case User.account_status(user) do
|
||||||
conn
|
:active -> conn
|
||||||
else
|
_ -> assign(conn, :user, nil)
|
||||||
conn
|
|
||||||
|> assign(:user, nil)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ defmodule Pleroma.User do
|
||||||
alias Comeonin.Pbkdf2
|
alias Comeonin.Pbkdf2
|
||||||
alias Ecto.Multi
|
alias Ecto.Multi
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
|
alias Pleroma.Config
|
||||||
alias Pleroma.Conversation.Participation
|
alias Pleroma.Conversation.Participation
|
||||||
alias Pleroma.Delivery
|
alias Pleroma.Delivery
|
||||||
alias Pleroma.FollowingRelationship
|
alias Pleroma.FollowingRelationship
|
||||||
|
@ -35,7 +36,7 @@ defmodule Pleroma.User do
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
@type t :: %__MODULE__{}
|
@type t :: %__MODULE__{}
|
||||||
|
@type account_status :: :active | :deactivated | :password_reset_pending | :confirmation_pending
|
||||||
@primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
|
@primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
|
||||||
|
|
||||||
# credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
|
# credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
|
||||||
|
@ -216,14 +217,21 @@ def unquote(:"#{outgoing_relation_target}_ap_ids")(user, restrict_deactivated? \
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc "Returns if the user should be allowed to authenticate"
|
@doc "Returns status account"
|
||||||
def auth_active?(%User{deactivated: true}), do: false
|
@spec account_status(User.t()) :: account_status()
|
||||||
|
def account_status(%User{deactivated: true}), do: :deactivated
|
||||||
|
def account_status(%User{password_reset_pending: true}), do: :password_reset_pending
|
||||||
|
|
||||||
def auth_active?(%User{confirmation_pending: true}),
|
def account_status(%User{confirmation_pending: true}) do
|
||||||
do: !Pleroma.Config.get([:instance, :account_activation_required])
|
case Config.get([:instance, :account_activation_required]) do
|
||||||
|
true -> :confirmation_pending
|
||||||
|
_ -> :active
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def auth_active?(%User{}), do: true
|
def account_status(%User{}), do: :active
|
||||||
|
|
||||||
|
@spec visible_for?(User.t(), User.t() | nil) :: boolean()
|
||||||
def visible_for?(user, for_user \\ nil)
|
def visible_for?(user, for_user \\ nil)
|
||||||
|
|
||||||
def visible_for?(%User{invisible: true}, _), do: false
|
def visible_for?(%User{invisible: true}, _), do: false
|
||||||
|
@ -231,15 +239,17 @@ def visible_for?(%User{invisible: true}, _), do: false
|
||||||
def visible_for?(%User{id: user_id}, %User{id: for_id}) when user_id == for_id, do: true
|
def visible_for?(%User{id: user_id}, %User{id: for_id}) when user_id == for_id, do: true
|
||||||
|
|
||||||
def visible_for?(%User{} = user, for_user) do
|
def visible_for?(%User{} = user, for_user) do
|
||||||
auth_active?(user) || superuser?(for_user)
|
account_status(user) == :active || superuser?(for_user)
|
||||||
end
|
end
|
||||||
|
|
||||||
def visible_for?(_, _), do: false
|
def visible_for?(_, _), do: false
|
||||||
|
|
||||||
|
@spec superuser?(User.t()) :: boolean()
|
||||||
def superuser?(%User{local: true, is_admin: true}), do: true
|
def superuser?(%User{local: true, is_admin: true}), do: true
|
||||||
def superuser?(%User{local: true, is_moderator: true}), do: true
|
def superuser?(%User{local: true, is_moderator: true}), do: true
|
||||||
def superuser?(_), do: false
|
def superuser?(_), do: false
|
||||||
|
|
||||||
|
@spec invisible?(User.t()) :: boolean()
|
||||||
def invisible?(%User{invisible: true}), do: true
|
def invisible?(%User{invisible: true}), do: true
|
||||||
def invisible?(_), do: false
|
def invisible?(_), do: false
|
||||||
|
|
||||||
|
|
|
@ -167,17 +167,37 @@ defp handle_create_authorization_error(
|
||||||
|
|
||||||
defp handle_create_authorization_error(
|
defp handle_create_authorization_error(
|
||||||
%Plug.Conn{} = conn,
|
%Plug.Conn{} = conn,
|
||||||
{:auth_active, false},
|
{:account_status, :confirmation_pending},
|
||||||
%{"authorization" => _} = params
|
%{"authorization" => _} = params
|
||||||
) do
|
) do
|
||||||
# Per https://github.com/tootsuite/mastodon/blob/
|
|
||||||
# 51e154f5e87968d6bb115e053689767ab33e80cd/app/controllers/api/base_controller.rb#L76
|
|
||||||
conn
|
conn
|
||||||
|> put_flash(:error, dgettext("errors", "Your login is missing a confirmed e-mail address"))
|
|> put_flash(:error, dgettext("errors", "Your login is missing a confirmed e-mail address"))
|
||||||
|> put_status(:forbidden)
|
|> put_status(:forbidden)
|
||||||
|> authorize(params)
|
|> authorize(params)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp handle_create_authorization_error(
|
||||||
|
%Plug.Conn{} = conn,
|
||||||
|
{:account_status, :password_reset_pending},
|
||||||
|
%{"authorization" => _} = params
|
||||||
|
) do
|
||||||
|
conn
|
||||||
|
|> put_flash(:error, dgettext("errors", "Password reset is required"))
|
||||||
|
|> put_status(:forbidden)
|
||||||
|
|> authorize(params)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp handle_create_authorization_error(
|
||||||
|
%Plug.Conn{} = conn,
|
||||||
|
{:account_status, :deactivated},
|
||||||
|
%{"authorization" => _} = params
|
||||||
|
) do
|
||||||
|
conn
|
||||||
|
|> put_flash(:error, dgettext("errors", "Your account is currently disabled"))
|
||||||
|
|> put_status(:forbidden)
|
||||||
|
|> authorize(params)
|
||||||
|
end
|
||||||
|
|
||||||
defp handle_create_authorization_error(%Plug.Conn{} = conn, error, %{"authorization" => _}) do
|
defp handle_create_authorization_error(%Plug.Conn{} = conn, error, %{"authorization" => _}) do
|
||||||
Authenticator.handle_error(conn, error)
|
Authenticator.handle_error(conn, error)
|
||||||
end
|
end
|
||||||
|
@ -218,46 +238,14 @@ def token_exchange(
|
||||||
) do
|
) do
|
||||||
with {:ok, %User{} = user} <- Authenticator.get_user(conn),
|
with {:ok, %User{} = user} <- Authenticator.get_user(conn),
|
||||||
{:ok, app} <- Token.Utils.fetch_app(conn),
|
{:ok, app} <- Token.Utils.fetch_app(conn),
|
||||||
{:auth_active, true} <- {:auth_active, User.auth_active?(user)},
|
{:account_status, :active} <- {:account_status, User.account_status(user)},
|
||||||
{:user_active, true} <- {:user_active, !user.deactivated},
|
|
||||||
{:password_reset_pending, false} <-
|
|
||||||
{:password_reset_pending, user.password_reset_pending},
|
|
||||||
{:ok, scopes} <- validate_scopes(app, params),
|
{:ok, scopes} <- validate_scopes(app, params),
|
||||||
{:ok, auth} <- Authorization.create_authorization(app, user, scopes),
|
{:ok, auth} <- Authorization.create_authorization(app, user, scopes),
|
||||||
{:ok, token} <- Token.exchange_token(app, auth) do
|
{:ok, token} <- Token.exchange_token(app, auth) do
|
||||||
json(conn, Token.Response.build(user, token))
|
json(conn, Token.Response.build(user, token))
|
||||||
else
|
else
|
||||||
{:auth_active, false} ->
|
error ->
|
||||||
# Per https://github.com/tootsuite/mastodon/blob/
|
handle_token_exchange_error(conn, error)
|
||||||
# 51e154f5e87968d6bb115e053689767ab33e80cd/app/controllers/api/base_controller.rb#L76
|
|
||||||
render_error(
|
|
||||||
conn,
|
|
||||||
:forbidden,
|
|
||||||
"Your login is missing a confirmed e-mail address",
|
|
||||||
%{},
|
|
||||||
"missing_confirmed_email"
|
|
||||||
)
|
|
||||||
|
|
||||||
{:user_active, false} ->
|
|
||||||
render_error(
|
|
||||||
conn,
|
|
||||||
:forbidden,
|
|
||||||
"Your account is currently disabled",
|
|
||||||
%{},
|
|
||||||
"account_is_disabled"
|
|
||||||
)
|
|
||||||
|
|
||||||
{:password_reset_pending, true} ->
|
|
||||||
render_error(
|
|
||||||
conn,
|
|
||||||
:forbidden,
|
|
||||||
"Password reset is required",
|
|
||||||
%{},
|
|
||||||
"password_reset_required"
|
|
||||||
)
|
|
||||||
|
|
||||||
_error ->
|
|
||||||
render_invalid_credentials_error(conn)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -286,6 +274,43 @@ def token_exchange(%Plug.Conn{} = conn, %{"grant_type" => "client_credentials"}
|
||||||
# Bad request
|
# Bad request
|
||||||
def token_exchange(%Plug.Conn{} = conn, params), do: bad_request(conn, params)
|
def token_exchange(%Plug.Conn{} = conn, params), do: bad_request(conn, params)
|
||||||
|
|
||||||
|
defp handle_token_exchange_error(%Plug.Conn{} = conn, {:account_status, :deactivated}) do
|
||||||
|
render_error(
|
||||||
|
conn,
|
||||||
|
:forbidden,
|
||||||
|
"Your account is currently disabled",
|
||||||
|
%{},
|
||||||
|
"account_is_disabled"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp handle_token_exchange_error(
|
||||||
|
%Plug.Conn{} = conn,
|
||||||
|
{:account_status, :password_reset_pending}
|
||||||
|
) do
|
||||||
|
render_error(
|
||||||
|
conn,
|
||||||
|
:forbidden,
|
||||||
|
"Password reset is required",
|
||||||
|
%{},
|
||||||
|
"password_reset_required"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp handle_token_exchange_error(%Plug.Conn{} = conn, {:account_status, :confirmation_pending}) do
|
||||||
|
render_error(
|
||||||
|
conn,
|
||||||
|
:forbidden,
|
||||||
|
"Your login is missing a confirmed e-mail address",
|
||||||
|
%{},
|
||||||
|
"missing_confirmed_email"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp handle_token_exchange_error(%Plug.Conn{} = conn, _error) do
|
||||||
|
render_invalid_credentials_error(conn)
|
||||||
|
end
|
||||||
|
|
||||||
def token_revoke(%Plug.Conn{} = conn, %{"token" => _token} = params) do
|
def token_revoke(%Plug.Conn{} = conn, %{"token" => _token} = params) do
|
||||||
with {:ok, app} <- Token.Utils.fetch_app(conn),
|
with {:ok, app} <- Token.Utils.fetch_app(conn),
|
||||||
{:ok, _token} <- RevokeToken.revoke(app, params) do
|
{:ok, _token} <- RevokeToken.revoke(app, params) do
|
||||||
|
@ -472,7 +497,7 @@ defp do_create_authorization(
|
||||||
%App{} = app <- Repo.get_by(App, client_id: client_id),
|
%App{} = app <- Repo.get_by(App, client_id: client_id),
|
||||||
true <- redirect_uri in String.split(app.redirect_uris),
|
true <- redirect_uri in String.split(app.redirect_uris),
|
||||||
{:ok, scopes} <- validate_scopes(app, auth_attrs),
|
{:ok, scopes} <- validate_scopes(app, auth_attrs),
|
||||||
{:auth_active, true} <- {:auth_active, User.auth_active?(user)} do
|
{:account_status, :active} <- {:account_status, User.account_status(user)} do
|
||||||
Authorization.create_authorization(app, user, scopes)
|
Authorization.create_authorization(app, user, scopes)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1286,23 +1286,35 @@ test "User.delete() plugs any possible zombie objects" do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "auth_active?/1 works correctly" do
|
describe "account_status/1" do
|
||||||
|
clear_config([:instance, :account_activation_required])
|
||||||
|
|
||||||
|
test "return confirmation_pending for unconfirm user" do
|
||||||
Pleroma.Config.put([:instance, :account_activation_required], true)
|
Pleroma.Config.put([:instance, :account_activation_required], true)
|
||||||
|
user = insert(:user, confirmation_pending: true)
|
||||||
|
assert User.account_status(user) == :confirmation_pending
|
||||||
|
end
|
||||||
|
|
||||||
local_user = insert(:user, local: true, confirmation_pending: true)
|
test "return active for confirmed user" do
|
||||||
confirmed_user = insert(:user, local: true, confirmation_pending: false)
|
Pleroma.Config.put([:instance, :account_activation_required], true)
|
||||||
remote_user = insert(:user, local: false)
|
user = insert(:user, confirmation_pending: false)
|
||||||
|
assert User.account_status(user) == :active
|
||||||
|
end
|
||||||
|
|
||||||
refute User.auth_active?(local_user)
|
test "return active for remote user" do
|
||||||
assert User.auth_active?(confirmed_user)
|
user = insert(:user, local: false)
|
||||||
assert User.auth_active?(remote_user)
|
assert User.account_status(user) == :active
|
||||||
|
end
|
||||||
|
|
||||||
# also shows unactive for deactivated users
|
test "returns :password_reset_pending for user with reset password" do
|
||||||
|
user = insert(:user, password_reset_pending: true)
|
||||||
|
assert User.account_status(user) == :password_reset_pending
|
||||||
|
end
|
||||||
|
|
||||||
deactivated_but_confirmed =
|
test "returns :deactivated for deactivated user" do
|
||||||
insert(:user, local: true, confirmation_pending: false, deactivated: true)
|
user = insert(:user, local: true, confirmation_pending: false, deactivated: true)
|
||||||
|
assert User.account_status(user) == :deactivated
|
||||||
refute User.auth_active?(deactivated_but_confirmed)
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "superuser?/1" do
|
describe "superuser?/1" do
|
||||||
|
|
|
@ -819,7 +819,7 @@ test "rejects token exchange for valid credentials belonging to unconfirmed user
|
||||||
|> User.confirmation_changeset(need_confirmation: true)
|
|> User.confirmation_changeset(need_confirmation: true)
|
||||||
|> User.update_and_set_cache()
|
|> User.update_and_set_cache()
|
||||||
|
|
||||||
refute Pleroma.User.auth_active?(user)
|
refute Pleroma.User.account_status(user) == :active
|
||||||
|
|
||||||
app = insert(:oauth_app)
|
app = insert(:oauth_app)
|
||||||
|
|
||||||
|
@ -849,7 +849,7 @@ test "rejects token exchange for valid credentials belonging to deactivated user
|
||||||
|
|
||||||
app = insert(:oauth_app)
|
app = insert(:oauth_app)
|
||||||
|
|
||||||
conn =
|
resp =
|
||||||
build_conn()
|
build_conn()
|
||||||
|> post("/oauth/token", %{
|
|> post("/oauth/token", %{
|
||||||
"grant_type" => "password",
|
"grant_type" => "password",
|
||||||
|
@ -858,10 +858,12 @@ test "rejects token exchange for valid credentials belonging to deactivated user
|
||||||
"client_id" => app.client_id,
|
"client_id" => app.client_id,
|
||||||
"client_secret" => app.client_secret
|
"client_secret" => app.client_secret
|
||||||
})
|
})
|
||||||
|
|> json_response(403)
|
||||||
|
|
||||||
assert resp = json_response(conn, 403)
|
assert resp == %{
|
||||||
assert %{"error" => _} = resp
|
"error" => "Your account is currently disabled",
|
||||||
refute Map.has_key?(resp, "access_token")
|
"identifier" => "account_is_disabled"
|
||||||
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
test "rejects token exchange for user with password_reset_pending set to true" do
|
test "rejects token exchange for user with password_reset_pending set to true" do
|
||||||
|
@ -875,7 +877,7 @@ test "rejects token exchange for user with password_reset_pending set to true" d
|
||||||
|
|
||||||
app = insert(:oauth_app, scopes: ["read", "write"])
|
app = insert(:oauth_app, scopes: ["read", "write"])
|
||||||
|
|
||||||
conn =
|
resp =
|
||||||
build_conn()
|
build_conn()
|
||||||
|> post("/oauth/token", %{
|
|> post("/oauth/token", %{
|
||||||
"grant_type" => "password",
|
"grant_type" => "password",
|
||||||
|
@ -884,12 +886,41 @@ test "rejects token exchange for user with password_reset_pending set to true" d
|
||||||
"client_id" => app.client_id,
|
"client_id" => app.client_id,
|
||||||
"client_secret" => app.client_secret
|
"client_secret" => app.client_secret
|
||||||
})
|
})
|
||||||
|
|> json_response(403)
|
||||||
|
|
||||||
assert resp = json_response(conn, 403)
|
assert resp == %{
|
||||||
|
"error" => "Password reset is required",
|
||||||
|
"identifier" => "password_reset_required"
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
assert resp["error"] == "Password reset is required"
|
test "rejects token exchange for user with confirmation_pending set to true" do
|
||||||
assert resp["identifier"] == "password_reset_required"
|
Pleroma.Config.put([:instance, :account_activation_required], true)
|
||||||
refute Map.has_key?(resp, "access_token")
|
password = "testpassword"
|
||||||
|
|
||||||
|
user =
|
||||||
|
insert(:user,
|
||||||
|
password_hash: Comeonin.Pbkdf2.hashpwsalt(password),
|
||||||
|
confirmation_pending: true
|
||||||
|
)
|
||||||
|
|
||||||
|
app = insert(:oauth_app, scopes: ["read", "write"])
|
||||||
|
|
||||||
|
resp =
|
||||||
|
build_conn()
|
||||||
|
|> post("/oauth/token", %{
|
||||||
|
"grant_type" => "password",
|
||||||
|
"username" => user.nickname,
|
||||||
|
"password" => password,
|
||||||
|
"client_id" => app.client_id,
|
||||||
|
"client_secret" => app.client_secret
|
||||||
|
})
|
||||||
|
|> json_response(403)
|
||||||
|
|
||||||
|
assert resp == %{
|
||||||
|
"error" => "Your login is missing a confirmed e-mail address",
|
||||||
|
"identifier" => "missing_confirmed_email"
|
||||||
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
test "rejects an invalid authorization code" do
|
test "rejects an invalid authorization code" do
|
||||||
|
|
Loading…
Reference in a new issue