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
|