ratemyclient/lib/my_first_elixir_vibe_code_web/user_auth.ex
Kevin Sivic 9ece312442 Initial commit: RateMyClient™ Phoenix application
Features:
- User registration and authentication with email/password
- Admin login with username-based authentication (separate from regular users)
- Review system for contractors to rate clients
- Star rating system with review forms
- Client identification with private data protection
- Contractor registration with document verification
- Admin dashboard for review management
- Contact form (demo, non-functional)
- Responsive navigation with DaisyUI components
- Docker Compose setup for production deployment
- PostgreSQL database with Ecto migrations
- High Vis color scheme (dark background with safety orange/green)

Admin credentials: username: admin, password: admin123

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-28 21:30:50 -05:00

173 lines
4.3 KiB
Elixir

defmodule MyFirstElixirVibeCodeWeb.UserAuth do
@moduledoc """
User authentication module for managing user sessions.
"""
use MyFirstElixirVibeCodeWeb, :verified_routes
import Plug.Conn
import Phoenix.Controller
alias MyFirstElixirVibeCode.Accounts
@max_age 60 * 60 * 24 * 60 # 60 days
@remember_me_cookie "_my_first_elixir_vibe_code_web_user_remember_me"
@remember_me_options [sign: true, max_age: @max_age, same_site: "Lax"]
# Plug behaviour
def init(opts), do: opts
def call(conn, opts) when is_atom(opts) do
apply(__MODULE__, opts, [conn, opts])
end
@doc """
Logs the user in.
It renews the session ID to avoid fixation attacks.
"""
def log_in_user(conn, user) do
token = :crypto.strong_rand_bytes(32) |> Base.url_encode64(padding: false)
user_return_to = get_session(conn, :user_return_to)
conn
|> renew_session()
|> put_token_in_session(token)
|> put_session(:user_id, user.id)
|> put_session(:is_admin, user.is_admin)
|> put_session(:live_socket_id, "users_sessions:#{Base.url_encode64(token)}")
|> redirect(to: user_return_to || signed_in_path(user))
end
defp signed_in_path(user) do
if user.is_admin do
~p"/admin/dashboard"
else
~p"/"
end
end
defp renew_session(conn) do
conn
|> configure_session(renew: true)
|> clear_session()
end
@doc """
Logs the user out.
It clears all session data for safety.
"""
def log_out_user(conn) do
if live_socket_id = get_session(conn, :live_socket_id) do
MyFirstElixirVibeCodeWeb.Endpoint.broadcast(live_socket_id, "disconnect", %{})
end
conn
|> renew_session()
|> delete_resp_cookie(@remember_me_cookie)
|> redirect(to: ~p"/")
end
@doc """
Authenticates the user by looking into the session.
"""
def fetch_current_user(conn, _opts) do
user_id = get_session(conn, :user_id)
user = user_id && Accounts.get_user!(user_id)
assign(conn, :current_user, user)
end
@doc """
Handles mounting and authenticating the current_user in LiveViews.
"""
def on_mount(:mount_current_user, _params, session, socket) do
{:cont, mount_current_user(socket, session)}
end
def on_mount(:ensure_authenticated, _params, session, socket) do
socket = mount_current_user(socket, session)
if socket.assigns.current_user do
{:cont, socket}
else
socket =
socket
|> Phoenix.LiveView.put_flash(:error, "You must log in to access this page.")
|> Phoenix.LiveView.redirect(to: ~p"/login")
{:halt, socket}
end
end
def on_mount(:redirect_if_user_is_authenticated, _params, session, socket) do
socket = mount_current_user(socket, session)
if socket.assigns.current_user do
{:halt, Phoenix.LiveView.redirect(socket, to: signed_in_path(socket.assigns.current_user))}
else
{:cont, socket}
end
end
defp mount_current_user(socket, session) do
Phoenix.Component.assign_new(socket, :current_user, fn ->
if user_id = session["user_id"] do
Accounts.get_user!(user_id)
end
end)
end
@doc """
Used for routes that require the user to not be authenticated.
"""
def redirect_if_user_is_authenticated(conn, _opts) do
if conn.assigns[:current_user] do
conn
|> redirect(to: signed_in_path(conn.assigns.current_user))
|> halt()
else
conn
end
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"/login")
|> halt()
end
end
@doc """
Used for routes that require the user to be an admin.
"""
def require_admin_user(conn, _opts) do
if conn.assigns[:current_user] && conn.assigns.current_user.is_admin do
conn
else
conn
|> put_flash(:error, "You must be an admin to access this page.")
|> redirect(to: ~p"/")
|> halt()
end
end
defp put_token_in_session(conn, token) do
conn
|> put_session(:user_token, token)
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
end