Introduce support for 3rd-party auth (OAuth2 & LDAP)

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel
2020-06-27 19:12:45 +02:00
parent 59a538feba
commit 9a080c1f10
48 changed files with 1380 additions and 240 deletions

View File

@@ -124,7 +124,11 @@ defmodule Mobilizon.GraphQL.Resolvers.Config do
},
rules: Config.instance_rules(),
version: Config.instance_version(),
federating: Config.instance_federating()
federating: Config.instance_federating(),
auth: %{
ldap: Config.ldap_enabled?(),
oauth_providers: Config.oauth_consumer_strategies()
}
}
end
end

View File

@@ -202,10 +202,12 @@ defmodule Mobilizon.GraphQL.Resolvers.Person do
"""
def register_person(_parent, args, _resolution) do
with {:ok, %User{} = user} <- Users.get_user_by_email(args.email),
{:no_actor, nil} <- {:no_actor, Users.get_actor_for_user(user)},
user_actor <- Users.get_actor_for_user(user),
no_actor <- is_nil(user_actor),
{:no_actor, true} <- {:no_actor, no_actor},
args <- Map.put(args, :user_id, user.id),
args <- save_attached_pictures(args),
{:ok, %Actor{} = new_person} <- Actors.new_person(args) do
{:ok, %Actor{} = new_person} <- Actors.new_person(args, true) do
{:ok, new_person}
else
{:error, :user_not_found} ->

View File

@@ -9,6 +9,7 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
alias Mobilizon.Actors.Actor
alias Mobilizon.Crypto
alias Mobilizon.Federation.ActivityPub
alias Mobilizon.Service.Auth.Authenticator
alias Mobilizon.Storage.{Page, Repo}
alias Mobilizon.Users.{Setting, User}
@@ -59,18 +60,16 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
Login an user. Returns a token and the user
"""
def login_user(_parent, %{email: email, password: password}, _resolution) do
with {:ok, %User{confirmed_at: %DateTime{}} = user} <- Users.get_user_by_email(email),
{:ok, %{access_token: access_token, refresh_token: refresh_token}} <-
Users.authenticate(%{user: user, password: password}) do
{:ok, %{access_token: access_token, refresh_token: refresh_token, user: user}}
else
{:ok, %User{confirmed_at: nil} = _user} ->
{:error, "User account not confirmed"}
case Authenticator.authenticate(email, password) do
{:ok,
%{access_token: _access_token, refresh_token: _refresh_token, user: _user} =
user_and_tokens} ->
{:ok, user_and_tokens}
{:error, :user_not_found} ->
{:error, "No user with this email was found"}
{:error, :unauthorized} ->
{:error, _error} ->
{:error, "Impossible to authenticate, either your email or password are invalid."}
end
end
@@ -82,7 +81,7 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
with {:ok, user, _claims} <- Auth.Guardian.resource_from_token(refresh_token),
{:ok, _old, {exchanged_token, _claims}} <-
Auth.Guardian.exchange(refresh_token, ["access", "refresh"], "access"),
{:ok, refresh_token} <- Users.generate_refresh_token(user) do
{:ok, refresh_token} <- Authenticator.generate_refresh_token(user) do
{:ok, %{access_token: exchanged_token, refresh_token: refresh_token}}
else
{:error, message} ->
@@ -151,7 +150,7 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
{:check_confirmation_token, Email.User.check_confirmation_token(token)},
{:get_actor, actor} <- {:get_actor, Users.get_actor_for_user(user)},
{:ok, %{access_token: access_token, refresh_token: refresh_token}} <-
Users.generate_tokens(user) do
Authenticator.generate_tokens(user) do
{:ok,
%{
access_token: access_token,
@@ -192,10 +191,15 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
def send_reset_password(_parent, args, _resolution) do
with email <- Map.get(args, :email),
{:ok, %User{locale: locale} = user} <- Users.get_user_by_email(email, true),
{:can_reset_password, true} <-
{:can_reset_password, Authenticator.can_reset_password?(user)},
{:ok, %Bamboo.Email{} = _email_html} <-
Email.User.send_password_reset_email(user, Map.get(args, :locale, locale)) do
{:ok, email}
else
{:can_reset_password, false} ->
{:error, "This user can't reset their password"}
{:error, :user_not_found} ->
# TODO : implement rate limits for this endpoint
{:error, "No user with this email was found"}
@@ -209,10 +213,10 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
Reset the password from an user
"""
def reset_password(_parent, %{password: password, token: token}, _resolution) do
with {:ok, %User{} = user} <-
with {:ok, %User{email: email} = user} <-
Email.User.check_reset_password_token(password, token),
{:ok, %{access_token: access_token, refresh_token: refresh_token}} <-
Users.authenticate(%{user: user, password: password}) do
Authenticator.authenticate(email, password) do
{:ok, %{access_token: access_token, refresh_token: refresh_token, user: user}}
end
end
@@ -295,10 +299,12 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
def change_password(
_parent,
%{old_password: old_password, new_password: new_password},
%{context: %{current_user: %User{password_hash: old_password_hash} = user}}
%{context: %{current_user: %User{} = user}}
) do
with {:current_password, true} <-
{:current_password, Argon2.verify_pass(old_password, old_password_hash)},
with {:can_change_password, true} <-
{:can_change_password, Authenticator.can_change_password?(user)},
{:current_password, {:ok, %User{}}} <-
{:current_password, Authenticator.login(user.email, old_password)},
{:same_password, false} <- {:same_password, old_password == new_password},
{:ok, %User{} = user} <-
user
@@ -306,7 +312,7 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
|> Repo.update() do
{:ok, user}
else
{:current_password, false} ->
{:current_password, _} ->
{:error, "The current password is invalid"}
{:same_password, true} ->
@@ -323,10 +329,12 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
end
def change_email(_parent, %{email: new_email, password: password}, %{
context: %{current_user: %User{email: old_email, password_hash: password_hash} = user}
context: %{current_user: %User{email: old_email} = user}
}) do
with {:current_password, true} <-
{:current_password, Argon2.verify_pass(password, password_hash)},
with {:can_change_password, true} <-
{:can_change_password, Authenticator.can_change_email?(user)},
{:current_password, {:ok, %User{}}} <-
{:current_password, Authenticator.login(user.email, password)},
{:same_email, false} <- {:same_email, new_email == old_email},
{:email_valid, true} <- {:email_valid, Email.Checker.valid?(new_email)},
{:ok, %User{} = user} <-
@@ -347,7 +355,7 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
{:ok, user}
else
{:current_password, false} ->
{:current_password, _} ->
{:error, "The password provided is invalid"}
{:same_email, true} ->
@@ -377,14 +385,24 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
end
end
def delete_account(_parent, %{password: password}, %{
context: %{current_user: %User{password_hash: password_hash} = user}
def delete_account(_parent, args, %{
context: %{current_user: %User{email: email} = user}
}) do
case {:current_password, Argon2.verify_pass(password, password_hash)} do
{:current_password, true} ->
with {:user_has_password, true} <- {:user_has_password, Authenticator.has_password?(user)},
{:confirmation_password, password} when not is_nil(password) <-
{:confirmation_password, Map.get(args, :password)},
{:current_password, {:ok, _}} <-
{:current_password, Authenticator.authenticate(email, password)} do
do_delete_account(user)
else
# If the user hasn't got any password (3rd-party auth)
{:user_has_password, false} ->
do_delete_account(user)
{:current_password, false} ->
{:confirmation_password, nil} ->
{:error, "The password provided is invalid"}
{:current_password, _} ->
{:error, "The password provided is invalid"}
end
end

View File

@@ -39,6 +39,7 @@ defmodule Mobilizon.GraphQL.Schema.ConfigType do
end
field(:rules, :string, description: "The instance's rules")
field(:auth, :auth, description: "The instance auth methods")
end
object :terms do
@@ -132,6 +133,16 @@ defmodule Mobilizon.GraphQL.Schema.ConfigType do
field(:groups, :boolean)
end
object :auth do
field(:ldap, :boolean, description: "Whether or not LDAP auth is enabled")
field(:oauth_providers, list_of(:oauth_provider), description: "List of oauth providers")
end
object :oauth_provider do
field(:id, :string, description: "The provider ID")
field(:label, :string, description: "The label for the auth provider")
end
object :config_queries do
@desc "Get the instance config"
field :config, :config do

View File

@@ -52,6 +52,8 @@ defmodule Mobilizon.GraphQL.Schema.UserType do
field(:locale, :string, description: "The user's locale")
field(:provider, :string, description: "The user's login provider")
field(:disabled, :boolean, description: "Whether the user is disabled")
field(:participations, :paginated_participant_list,