Packages

phoenix_kit

1.7.144
1.7.208 1.7.207 1.7.206 1.7.205 1.7.204 1.7.203 1.7.202 1.7.201 1.7.200 1.7.199 1.7.198 1.7.197 1.7.196 1.7.194 1.7.193 1.7.192 1.7.191 1.7.190 1.7.189 1.7.187 1.7.186 1.7.185 1.7.184 1.7.183 1.7.182 1.7.181 1.7.180 1.7.179 1.7.178 1.7.177 1.7.176 1.7.175 1.7.174 1.7.173 1.7.172 1.7.171 1.7.170 1.7.169 1.7.168 1.7.167 1.7.166 1.7.165 1.7.164 1.7.162 1.7.161 1.7.160 1.7.159 1.7.157 1.7.156 1.7.155 1.7.154 1.7.153 1.7.152 1.7.151 1.7.150 1.7.149 1.7.146 1.7.145 1.7.144 1.7.143 1.7.138 1.7.133 1.7.132 1.7.131 1.7.130 1.7.128 1.7.126 1.7.125 1.7.121 1.7.120 1.7.119 1.7.118 1.7.117 1.7.116 1.7.115 1.7.114 1.7.113 1.7.112 1.7.111 1.7.110 1.7.109 1.7.108 1.7.107 1.7.106 1.7.105 1.7.104 1.7.103 1.7.102 1.7.101 1.7.100 1.7.99 1.7.98 1.7.97 1.7.96 1.7.95 1.7.94 1.7.93 1.7.92 1.7.91 1.7.90 1.7.89 1.7.88 1.7.87 1.7.86 1.7.85 1.7.84 1.7.83 1.7.82 1.7.81 1.7.80 1.7.79 1.7.78 1.7.77 1.7.76 1.7.75 1.7.74 1.7.71 1.7.70 1.7.69 1.7.66 1.7.65 1.7.64 1.7.63 1.7.62 1.7.61 1.7.59 1.7.58 1.7.57 1.7.56 1.7.55 1.7.54 1.7.53 1.7.52 1.7.51 1.7.49 1.7.44 1.7.43 1.7.42 1.7.41 1.7.39 1.7.38 1.7.37 1.7.36 1.7.34 1.7.33 1.7.31 1.7.30 1.7.29 1.7.28 1.7.27 1.7.26 1.7.25 1.7.24 1.7.23 1.7.22 1.7.21 1.7.20 1.7.19 1.7.18 1.7.17 1.7.16 1.7.15 1.7.14 1.7.13 1.7.12 1.7.11 1.7.10 1.7.9 1.7.8 1.7.7 1.7.6 1.7.5 1.7.4 1.7.3 1.7.2 1.7.1 1.7.0 1.6.20 1.6.19 1.6.18 1.6.17 1.6.16 1.6.15 1.6.14 1.6.13 1.6.12 1.6.11 1.6.10 1.6.9 1.6.8 1.6.7 1.6.6 1.6.5 1.6.4 1.6.3 1.5.2 1.5.1 1.5.0 1.4.9 1.4.8 1.4.7 1.4.6 1.4.5 1.4.4 1.4.3 1.4.2 1.4.1 1.4.0 1.3.2 1.3.1 1.3.0 1.2.10 1.2.9 1.2.8 1.2.7 1.2.5 1.2.4 1.2.2 1.2.1 1.2.0 1.1.0 1.0.0

A foundation for building Elixir Phoenix apps — SaaS, social networks, ERP systems, marketplaces, and more

Current section

Files

Jump to
phoenix_kit lib phoenix_kit_web users session.ex
Raw

lib/phoenix_kit_web/users/session.ex

defmodule PhoenixKitWeb.Users.Session do
@moduledoc """
Controller for handling user session management.
This controller manages user login and logout operations, including:
- Creating new sessions via email/password authentication
- Handling post-registration and password update flows
- Session termination (logout)
- GET-based logout for direct URL access
## Security Features
- Prevents user enumeration by not disclosing whether an email is registered
- Supports remember me functionality via UserAuth module
- Session renewal on login/logout to prevent fixation attacks
"""
use PhoenixKitWeb, :controller
alias PhoenixKit.Users.Auth
alias PhoenixKit.Utils.IpAddress
alias PhoenixKit.Utils.Routes
alias PhoenixKitWeb.Users.Auth, as: UserAuth
alias PhoenixKitWeb.Users.MultiSession
def create(conn, %{"_action" => "registered"} = params) do
create(conn, params, "Account created successfully!")
end
def create(conn, %{"_action" => "password_updated"} = params) do
conn
|> put_session(:user_return_to, Routes.path("/dashboard/settings"))
|> create(params, "Password updated successfully!")
end
def create(conn, params) do
create(conn, params, "Welcome back!")
end
defp create(conn, %{"user" => user_params}, info) do
%{"password" => password} = user_params
# Support both old "email" field and new "email_or_username" field for backwards compatibility
email_or_username = user_params["email_or_username"] || user_params["email"]
ip_address = IpAddress.extract_from_conn(conn)
case Auth.get_user_by_email_or_username_and_password(email_or_username, password, ip_address) do
{:ok, %Auth.User{is_active: false}} ->
# Valid credentials but account is inactive
conn
|> put_flash(
:error,
"Your account is currently inactive. Please contact the team if you believe this is an error."
)
|> put_flash(:email_or_username, String.slice(email_or_username, 0, 160))
|> redirect(to: Routes.path("/users/log-in"))
{:ok, user} ->
# Valid credentials and active account
conn
|> maybe_store_return_to_from_params(user_params)
|> put_flash(:info, info)
|> UserAuth.log_in_user(user, user_params)
{:error, :rate_limit_exceeded} ->
# Rate limit exceeded - show specific error message
conn
|> put_flash(:error, "Too many login attempts. Please try again later.")
|> put_flash(:email_or_username, String.slice(email_or_username, 0, 160))
|> redirect(to: Routes.path("/users/log-in"))
{:error, :invalid_credentials} ->
# Invalid credentials (wrong email/username or password)
# In order to prevent user enumeration attacks, don't disclose whether the email/username is registered.
conn
|> put_flash(:error, "Invalid email/username or password")
|> put_flash(:email_or_username, String.slice(email_or_username, 0, 160))
|> redirect(to: Routes.path("/users/log-in"))
end
end
# Logout: "all" drains the whole stack; otherwise log out the active account only
# (falling back to root) unless root is active, in which case full logout runs.
def delete(conn, %{"all" => _} = _params) do
# log_out_user/1 drains the whole multi-session stack, so this is now just a
# relabelled full logout (kept distinct for the clearer flash message).
conn
|> put_flash(:info, "Logged out of all accounts.")
|> UserAuth.log_out_user()
end
def delete(conn, _params) do
case MultiSession.log_out_active(conn) do
{:switched, conn, user} ->
conn
|> put_flash(:info, gettext("Logged out. Now signed in as %{email}.", email: user.email))
|> redirect(to: Routes.path("/"))
{:full, conn} ->
# Root account is active → full logout. log_out_user/1 drains the whole
# stack (after resolving the user for the disconnect broadcast).
conn
|> put_flash(:info, "Logged out successfully.")
|> UserAuth.log_out_user()
end
end
# --- multi-session helpers ---
defp with_gate(conn, _params, fun) do
if MultiSession.gate_allowed?(get_session(conn)) do
fun.(conn)
else
# Feature is off (or no valid root) — send the user home with a flash. A bare
# redirect (302) lets the flash render; a 403 would swallow it (the browser
# never follows the Location header on a non-3xx response).
conn
|> put_flash(:error, "Multi-account switching is not available.")
|> redirect(to: Routes.path("/"))
end
end
defp redirect_back(conn, params) do
if Routes.local_path?(params["return_to"]) do
redirect(conn, to: params["return_to"])
else
redirect(conn, to: Routes.path("/"))
end
end
# Store return_to from form params (e.g., guest checkout → login → back to checkout)
defp maybe_store_return_to_from_params(conn, %{"return_to" => return_to})
when is_binary(return_to) and return_to != "" do
if Routes.local_path?(return_to) do
put_session(conn, :user_return_to, return_to)
else
conn
end
end
defp maybe_store_return_to_from_params(conn, _params), do: conn
# Support GET logout for direct URL access. log_out_user/1 drains the whole
# multi-session stack, so secondary tokens are invalidated here too.
def get_logout(conn, _params) do
conn
|> put_flash(:info, "Logged out successfully.")
|> UserAuth.log_out_user()
end
def add_account(conn, %{"user" => %{"password" => password} = user_params} = params) do
email_or_username = user_params["email_or_username"] || user_params["email"]
with_gate(conn, params, fn conn ->
case MultiSession.add_account(conn, email_or_username, password) do
{:ok, conn} ->
conn |> put_flash(:info, "Account added.") |> redirect_back(params)
{:error, :stack_full} ->
conn
|> put_flash(:error, "Maximum number of accounts reached.")
|> redirect_back(params)
{:error, :already_in_stack} ->
conn
|> put_flash(:error, "That account is already in your session.")
|> redirect_back(params)
{:error, _reason} ->
conn
|> put_flash(:error, "Invalid email/username or password.")
|> redirect_back(params)
end
end)
end
def set_active_account(conn, %{"ref" => ref} = params) do
with_gate(conn, params, fn conn ->
case MultiSession.switch_to(conn, ref) do
{:ok, conn, user} ->
conn
|> put_flash(:info, gettext("Switched to %{email}.", email: user.email))
|> redirect_back(params)
{:error, _reason} ->
conn |> put_flash(:error, "Could not switch account.") |> redirect_back(params)
end
end)
end
def remove_account(conn, %{"ref" => ref} = params) do
with_gate(conn, params, fn conn ->
case MultiSession.remove_account(conn, ref) do
{:ok, conn} ->
conn |> put_flash(:info, "Account removed.") |> redirect_back(params)
{:error, :cannot_remove_root} ->
conn
|> put_flash(:error, "Cannot remove your primary account.")
|> redirect_back(params)
{:error, _reason} ->
conn |> put_flash(:error, "Could not remove account.") |> redirect_back(params)
end
end)
end
end