mobilizon/lib/web/controllers/auth_controller.ex

164 lines
5.3 KiB
Elixir
Raw Permalink Normal View History

defmodule Mobilizon.Web.AuthController do
use Mobilizon.Web, :controller
alias Mobilizon.Service.Auth.Authenticator
alias Mobilizon.Users
alias Mobilizon.Users.User
import Mobilizon.Service.Guards, only: [is_valid_string: 1]
require Logger
plug(:put_layout, false)
plug(Plug.Session,
store: :cookie,
key: "_auth_callback",
signing_salt: {Mobilizon.Web.AuthController, :secret_key_base, []}
)
plug(Ueberauth)
@spec request(Plug.Conn.t(), map()) :: Plug.Conn.t()
def request(conn, %{"provider" => provider_name} = _params) do
case provider_config(provider_name) do
{:ok, provider_config} ->
conn
|> Ueberauth.run_request(provider_name, provider_config)
{:error, error} ->
redirect_to_error(conn, error, provider_name)
end
end
@spec callback(Plug.Conn.t(), map()) :: Plug.Conn.t() | {:error, any()}
def callback(
%{assigns: %{ueberauth_failure: fails}} = conn,
%{"provider" => provider} = _params
) do
Logger.warning("Unable to login user with #{provider} #{inspect(fails)}")
redirect_to_error(conn, :unknown_error, provider)
end
def callback(
%{assigns: %{ueberauth_auth: %Ueberauth.Auth{strategy: strategy} = auth, locale: locale}} =
conn,
_params
) do
email = email_from_ueberauth(auth)
[_, _, _, strategy] = strategy |> to_string() |> String.split(".")
strategy = String.downcase(strategy)
user =
with {:valid_email, false} <- {:valid_email, is_nil(email) or email == ""},
{:error, :user_not_found} <- Users.get_user_by_email(email),
{:ok, %User{} = user} <- Users.create_external(email, strategy, %{locale: locale}) do
user
else
{:ok, %User{} = user} ->
user
{:error, error} ->
{:error, error}
error ->
{:error, error}
end
with %User{} = user <- user,
{:ok, %{access_token: access_token, refresh_token: refresh_token}} <-
Authenticator.generate_tokens(user) do
Logger.info("Logged-in user \"#{email}\" through #{strategy}")
render(conn, "callback.html", %{
access_token: access_token,
refresh_token: refresh_token,
user: user,
username: username_from_ueberauth(auth),
name: display_name_from_ueberauth(auth)
})
else
err ->
Logger.warning("Unable to login user \"#{email}\" #{inspect(err)}")
redirect_to_error(conn, :unknown_error, strategy)
end
end
def callback(conn, %{"provider" => provider_name} = params) do
case provider_config(provider_name) do
{:ok, provider_config} ->
conn
|> Ueberauth.run_callback(provider_name, provider_config)
|> callback(params)
{:error, error} ->
redirect_to_error(conn, error, provider_name)
end
end
# Github only give public emails as part of the user profile,
# so we explicitely request all user emails and filter on the primary one
@spec email_from_ueberauth(Ueberauth.Auth.t()) :: String.t() | nil
defp email_from_ueberauth(%Ueberauth.Auth{
strategy: Ueberauth.Strategy.Github,
extra: %Ueberauth.Auth.Extra{raw_info: %{user: %{"emails" => emails}}}
})
when length(emails) > 0,
do: emails |> Enum.find(& &1["primary"]) |> (& &1["email"]).()
defp email_from_ueberauth(%Ueberauth.Auth{
extra: %Ueberauth.Auth.Extra{raw_info: %{user: %{"email" => email}}}
})
when is_valid_string(email),
do: email
defp email_from_ueberauth(%Ueberauth.Auth{info: %Ueberauth.Auth.Info{email: email}})
when is_valid_string(email),
do: email
defp email_from_ueberauth(_), do: nil
defp username_from_ueberauth(%Ueberauth.Auth{info: %Ueberauth.Auth.Info{nickname: nickname}})
when is_valid_string(nickname),
do: nickname
defp username_from_ueberauth(_), do: nil
defp display_name_from_ueberauth(%Ueberauth.Auth{info: %Ueberauth.Auth.Info{name: name}})
when is_valid_string(name),
do: name
defp display_name_from_ueberauth(_), do: nil
@spec provider_config(String.t()) :: {:ok, any()} | {:error, :not_supported | :unknown_error}
defp provider_config(provider_name) do
with ueberauth when is_list(ueberauth) <- Application.get_env(:ueberauth, Ueberauth),
providers when is_list(providers) <- Keyword.get(ueberauth, :providers),
providers_keys <- providers |> Keyword.keys() |> Enum.map(&Atom.to_string/1),
{:supported, true} <- {:supported, provider_name in providers_keys},
provider_name <- String.to_existing_atom(provider_name),
provider_config <- Keyword.get(providers, provider_name) do
{:ok, provider_config}
else
{:supported, false} ->
{:error, :not_supported}
_ ->
{:error, :unknown_error}
end
end
@spec redirect_to_error(Plug.Conn.t(), atom(), String.t()) :: Plug.Conn.t()
defp redirect_to_error(conn, :not_supported, provider_name) do
redirect(conn, to: "/login?code=Login Provider not found&provider=#{provider_name}")
end
defp redirect_to_error(conn, :unknown_error, provider_name) do
redirect(conn, to: "/login?code=Error with Login Provider&provider=#{provider_name}")
end
def secret_key_base do
:mobilizon
|> Application.get_env(Mobilizon.Web.Endpoint, [])
|> Keyword.get(:secret_key_base)
end
end