fp/apps/bright/lib/bright_web/controllers/auth_controller.ex

284 lines
8.0 KiB
Elixir
Raw Normal View History

2025-02-08 02:54:01 -08:00
defmodule BrightWeb.AuthController do
use BrightWeb, :controller
use BrightWeb, :verified_routes
alias Bright.Users
alias Bright.Users.User
2025-02-12 06:09:01 -08:00
alias Bright.Patrons.TierMapper
2025-02-08 02:54:01 -08:00
alias Bright.Repo
require Logger
2025-02-10 08:16:50 -08:00
plug(Ueberauth)
2025-02-08 02:54:01 -08:00
import Plug.Conn
import Phoenix.Controller
alias Phoenix.LiveView
def delete(conn, _params) do
conn
|> clear_session()
|> redirect(to: ~p"/")
end
def callback(conn = %{assigns: %{ueberauth_auth: auth}}, _params) do
2025-02-10 08:16:50 -08:00
Logger.debug("callback() with ueberauth_auth defined. Let's get a User!")
Logger.debug(inspect(auth))
2025-02-08 02:54:01 -08:00
if user = Users.get_by_ueberauth(auth) do
2025-02-10 08:16:50 -08:00
Logger.debug(">>>>>> user has been here before")
Logger.debug("user=#{inspect(user)}")
Logger.debug("auth=#{inspect(auth)}")
2025-02-08 02:54:01 -08:00
## @todo here we need to update the user's patron_tier
attrs = params_from_ueberauth(auth)
2025-02-10 08:16:50 -08:00
2025-02-08 02:54:01 -08:00
case Users.update_user(user, attrs) do
{:ok, user} ->
conn
|> sign_in_and_redirect(user, ~p"/profile")
2025-02-10 08:16:50 -08:00
2025-02-08 02:54:01 -08:00
{:error, reason} ->
Logger.error("error while updating the user's patron_tier. #{inspect(reason)}")
2025-02-10 08:16:50 -08:00
2025-02-08 02:54:01 -08:00
conn
|> put_flash(:error, "Patreon tier synchronization failed. Please try again.")
|> redirect(to: ~p"/")
end
else
2025-02-10 08:16:50 -08:00
Logger.debug(">>>>>> it's user's first time here.")
2025-02-08 02:54:01 -08:00
attrs = params_from_ueberauth(auth)
case Users.register_user(attrs) do
{:ok, user} ->
2025-02-10 08:16:50 -08:00
Logger.debug("user=#{inspect(user)}")
2025-02-08 02:54:01 -08:00
conn
|> put_flash(:success, "Welcome to Futureporn!")
|> sign_in_and_redirect(user, ~p"/profile")
2025-02-10 08:16:50 -08:00
2025-02-08 02:54:01 -08:00
{:error, reason} ->
Logger.error("failed Users.register_user for a first-time user. reason=#{reason}")
2025-02-10 08:16:50 -08:00
2025-02-08 02:54:01 -08:00
conn
|> put_flash(:error, "Something went wrong. Please try again.")
|> redirect(to: ~p"/")
end
end
end
2025-02-12 06:09:01 -08:00
def callback(
conn = %{assigns: %{ueberauth_failure: %Ueberauth.Failure{errors: errors}}},
_params
) do
error_messages =
errors
|> Enum.map(fn %Ueberauth.Failure.Error{message: message} -> message end)
# Join multiple errors into a single string if needed
|> Enum.join(", ")
2025-02-10 08:16:50 -08:00
2025-02-08 02:54:01 -08:00
conn
2025-02-12 06:09:01 -08:00
|> put_flash(:error, "Authentication failed: #{error_messages}")
|> redirect(to: ~p"/")
2025-02-08 02:54:01 -08:00
end
defp params_from_ueberauth(%{provider: :github, info: info, uid: uid}) do
%{name: info.name, handle: info.nickname, github_handle: info.nickname, github_id: uid}
end
2025-02-10 08:16:50 -08:00
defp params_from_ueberauth(%{
provider: :patreon,
info: info,
uid: uid,
extra: %{raw_info: %{user: user}}
}) do
Logger.debug(
"getting params_from_ueberauth provider: :patreon. info=#{inspect(info)}, uid=#{inspect(uid)}, user=#{inspect(user)}"
)
2025-02-08 02:54:01 -08:00
2025-02-12 06:09:01 -08:00
currently_entitled_tiers = Bright.Patrons.PatreonEntitlements.extract_tiers(user)
Logger.debug("currently_entitled_tiers=#{inspect(currently_entitled_tiers)}")
patron_tier =
TierMapper.largest_tier_for_platform("patreon", currently_entitled_tiers)
Logger.debug(">>>> computed patron_tier=#{inspect(patron_tier)}")
2025-02-08 02:54:01 -08:00
%{
patreon_id: uid,
patron_tier: patron_tier
}
end
defp sign_in_and_redirect(conn, user, route) do
2025-02-10 08:16:50 -08:00
Logger.debug("sign_in_and_redirect with user=#{inspect(user)} route=#{route}")
2025-02-08 02:54:01 -08:00
user |> User.sign_in_changes() |> Repo.update()
conn
# |> tap(fn c -> Logger.debug("Before: #{inspect(c.assigns)}") end)
|> assign(:current_user, user)
# |> tap(fn c -> Logger.debug("After: #{inspect(c.assigns)}") end)
|> put_flash(:success, "Authenticated! teehee.")
|> put_session("id", user.id)
|> configure_session(renew: true)
|> redirect(to: route)
end
defp maybe_store_return_to(%{method: "GET"} = conn) do
put_session(conn, :user_return_to, current_path(conn))
end
defp maybe_store_return_to(conn), do: conn
@doc """
Authenticates the user by looking into the session.
"""
def fetch_current_user(conn, _opts) do
user_id = get_session(conn, :id)
user = user_id && Users.get_user!(user_id)
2025-02-10 08:16:50 -08:00
Logger.debug(
2025-02-12 06:09:01 -08:00
"fetch_current_user attempting to get user. user_id=#{inspect(user_id)} user=#{inspect(user)}"
2025-02-10 08:16:50 -08:00
)
2025-02-08 02:54:01 -08:00
2025-02-10 08:16:50 -08:00
assign(conn, :current_user, user)
end
2025-02-08 02:54:01 -08:00
def on_mount(:current_user, _params, session, socket) do
Logger.debug("~~~~ on_mount with :current_user=#{inspect(:current_user)}")
2025-02-10 08:16:50 -08:00
2025-02-08 02:54:01 -08:00
case session do
%{"id" => id} ->
{:cont,
2025-02-10 08:16:50 -08:00
Phoenix.Component.assign_new(socket, :current_user, fn -> Users.get_user!(id) end)}
2025-02-08 02:54:01 -08:00
{:cont, assign(socket, :page_title, "DemoWeb")}
%{} ->
{:cont, Phoenix.Component.assign(socket, :current_user, nil)}
end
end
def on_mount(:ensure_authenticated, _params, session, socket) do
Logger.debug("on_mount with session=#{inspect(session)}")
2025-02-10 08:16:50 -08:00
2025-02-08 02:54:01 -08:00
case session do
%{"id" => id} ->
2025-02-10 08:16:50 -08:00
Logger.debug(
"~~~~ on_mount with :ensure_authenticated=#{inspect(:ensure_authenticated)}. user id=#{id}"
)
2025-02-08 02:54:01 -08:00
new_socket =
Phoenix.Component.assign_new(socket, :current_user, fn ->
Users.get_user!(id)
end)
Logger.debug("new_socket=#{inspect(new_socket)}")
%Users.User{} = new_socket.assigns.current_user
{:cont, new_socket}
%{} ->
Logger.debug("~~~~ on_mount with ensure_authenticated... user id was FALSY! (bad)")
{:halt, redirect_require_login(socket)}
end
rescue
Ecto.NoResultsError -> {:halt, redirect_require_login(socket)}
end
def on_mount(:ensure_admin, _params, session, socket) do
Logger.debug("~~~~ on_mount with :ensure_admin=#{inspect(:ensure_admin)}")
2025-02-10 08:16:50 -08:00
2025-02-08 02:54:01 -08:00
case session do
%{"id" => id} ->
user = Users.get_user!(id)
if Users.admin?(user) do
{:cont, socket}
else
2025-02-10 08:16:50 -08:00
{:halt, LiveView.redirect(socket, to: ~p"/")}
2025-02-08 02:54:01 -08:00
end
%{} ->
{:halt, redirect_require_login(socket)}
end
rescue
Ecto.NoResultsError -> {:halt, redirect_require_login(socket)}
end
@doc """
Used for routes that require the user to be authenticated.
"""
def require_authenticated_user(conn, _opts) do
if conn.assigns[:current_user] do
conn
else
conn
|> put_flash(:error, "You must log in to access this page.")
|> maybe_store_return_to()
|> redirect(to: ~p"/auth/patreon")
|> halt()
end
end
2025-02-12 06:09:01 -08:00
def require_patron_tier_1(conn, _), do: require_patron_tier(conn, 1)
def require_patron_tier_2(conn, _), do: require_patron_tier(conn, 2)
def require_patron_tier_3(conn, _), do: require_patron_tier(conn, 3)
@doc """
Ensures the user has a patron tier of `tier_level` or higher,
or has the role of "admin".
Redirects users who don't meet the requirement.
"""
def require_patron_tier(conn, tier_level) when is_integer(tier_level) do
user = conn.assigns[:current_user] || %{}
Logger.debug(
"require_patron_tier with conn=#{inspect(conn)} and tier_level=#{inspect(tier_level)} and user=#{inspect(user)}"
)
case user do
%{role: "admin"} ->
conn
%{patron_tier: patron_tier} when is_integer(patron_tier) and patron_tier >= tier_level ->
conn
_ ->
user_return_to = conn.assigns[:user_return_to] || "/"
conn
|> put_flash(:error, "This route is for tier #{tier_level} or higher.")
|> redirect(to: user_return_to)
|> halt()
end
end
2025-02-08 02:54:01 -08:00
@doc """
Used for routes that require the user to have admin privs.
"""
def require_admin_user(conn, _opts) do
case conn.assigns[:current_user] do
2025-02-10 08:16:50 -08:00
%{role: "admin"} ->
conn
2025-02-12 06:09:01 -08:00
user ->
Logger.debug(
"A USER IS DOING A THING THAT REQUIRES ADMIN USER. BUT WE THINK THEY ARE NOT AN ADMIN. user=#{inspect(user)}"
)
2025-02-08 02:54:01 -08:00
conn
|> put_flash(:error, "Only admins can do that.")
|> maybe_store_return_to()
|> redirect(to: ~p"/")
|> halt()
end
end
defp redirect_require_login(socket) do
socket
|> LiveView.put_flash(:error, "Please sign in")
|> LiveView.redirect(to: ~p"/auth/patreon")
end
end