Packages
phoenix_kit
1.7.187
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
Current section
Files
lib/phoenix_kit_web/users/registration.ex
defmodule PhoenixKitWeb.Users.Registration do
@moduledoc """
LiveView for user registration.
Provides a registration form for new users to create an account.
Supports referral codes and respects the allow_registration setting.
Tracks anonymous visitor sessions during registration.
"""
use PhoenixKitWeb, :live_view
alias PhoenixKit.Admin.Presence
alias PhoenixKit.Settings
alias PhoenixKit.Users.Auth
alias PhoenixKit.Users.Auth.User
alias PhoenixKit.Users.Invitations
alias PhoenixKit.Users.Referrals
alias PhoenixKit.Utils.Date, as: UtilsDate
alias PhoenixKit.Utils.IpAddress
alias PhoenixKit.Utils.Routes
def mount(params, session, socket) do
case PhoenixKitWeb.Users.Auth.maybe_redirect_authenticated(socket) do
{:redirect, socket} ->
{:ok, socket}
:cont ->
do_mount(params, session, socket)
end
end
defp do_mount(params, session, socket) do
# Check if registration is allowed
allow_registration = Settings.get_boolean_setting("allow_registration", true)
if allow_registration do
# Track anonymous visitor session
if connected?(socket) do
session_id = session["live_socket_id"] || generate_session_id()
Presence.track_anonymous(session_id, %{
connected_at: UtilsDate.utc_now(),
ip_address: IpAddress.extract_from_socket(socket),
user_agent: get_connect_info(socket, :user_agent),
current_page: Routes.path("/users/register")
})
end
# Get project title from settings (with Config fallback)
project_title = PhoenixKit.Settings.get_project_title()
# Get referral codes configuration
referral_codes_config = Referrals.get_config()
# Get Magic Link registration setting
magic_link_registration_enabled =
Settings.get_boolean_setting("magic_link_registration_enabled", true)
# Get username field visibility setting
show_username = Settings.get_setting("registration_show_username", "true") != "false"
# Get organization accounts setting
org_accounts_enabled =
Settings.get_boolean_setting("enable_organization_accounts", false)
changeset = Auth.change_user_registration(%User{})
# Extract and store IP address during mount for later use
ip_address = IpAddress.extract_from_socket(socket)
# Parse invitation token from URL params
invitation_token = Map.get(params, "invitation")
pending_invitation = load_pending_invitation(invitation_token)
socket =
socket
|> assign(trigger_submit: false, check_errors: false)
|> assign(username_edited: false)
|> assign(project_title: project_title)
|> assign(referral_codes_enabled: referral_codes_config.enabled)
|> assign(referral_codes_required: referral_codes_config.required)
|> assign(referral_code: nil)
|> assign(referral_code_error: nil)
|> assign(user_ip_address: ip_address)
|> assign(magic_link_registration_enabled: magic_link_registration_enabled)
|> assign(show_username: show_username)
|> assign(org_accounts_enabled: org_accounts_enabled)
|> assign(pending_invitation: pending_invitation)
|> assign(pending_invitation_token: invitation_token)
|> assign_form(changeset)
{:ok, socket, temporary_assigns: [form: nil]}
else
socket =
socket
|> put_flash(
:error,
"User registration is currently disabled. Please contact an administrator."
)
|> redirect(to: Routes.path("/users/log-in"))
{:ok, socket}
end
end
def handle_event("save", %{"user" => user_params} = params, socket) do
referral_code = params["referral_code"]
# If the username was never manually edited, let the schema (re)generate it
# from the final email rather than persisting a possibly-stale preview value.
user_params = maybe_sync_username(user_params, socket.assigns.username_edited)
# Validate referral code if system is enabled
case validate_referral_code(referral_code, socket) do
{:ok, validated_code} ->
# Check if geolocation tracking is enabled
track_geolocation = Settings.get_boolean_setting("track_registration_geolocation", false)
ip_address = socket.assigns.user_ip_address
# Use appropriate registration function based on geolocation setting
registration_result =
if track_geolocation do
Auth.register_user_with_geolocation(user_params, ip_address)
else
Auth.register_user(user_params, ip_address)
end
case registration_result do
{:ok, user} ->
# Record referral code usage if provided and valid
if validated_code do
Referrals.use_code(validated_code.code, user.uuid)
end
# Store invitation UUID in custom_fields for auto-accept after email confirmation
if socket.assigns[:pending_invitation] do
Auth.update_user_fields(user, %{
"pending_invitation_uuid" => socket.assigns.pending_invitation.uuid
})
end
case Auth.deliver_user_confirmation_instructions(
user,
&Routes.url("/users/confirm/#{&1}")
) do
{:ok, _} ->
# Email sent successfully
:ok
{:error, error} ->
# Log error but don't fail registration
require Logger
Logger.error("Failed to send confirmation email: #{inspect(error)}")
:ok
end
PhoenixKit.Activity.log(%{
action: "user.registered",
module: "users",
mode: "manual",
actor_uuid: user.uuid,
resource_type: "user",
resource_uuid: user.uuid,
metadata: %{"method" => "self_registration", "actor_role" => "user"}
})
changeset = Auth.change_user_registration(user)
{:noreply, socket |> assign(trigger_submit: true) |> assign_form(changeset)}
{:error, %Ecto.Changeset{} = changeset} ->
{:noreply, socket |> assign(check_errors: true) |> assign_form(changeset)}
end
{:error, error_message} ->
socket =
socket
|> assign(referral_code: referral_code)
|> assign(referral_code_error: error_message)
|> assign(check_errors: true)
{:noreply, socket}
end
end
def handle_event("validate", %{"user" => user_params} = params, socket) do
referral_code = params["referral_code"]
# Track whether the user owns the username field, then keep it in sync with
# the email while it's still auto-managed.
username_edited = username_manually_edited?(socket, params, user_params)
user_params = maybe_sync_username(user_params, username_edited)
socket = assign(socket, username_edited: username_edited)
# Validate referral code and update error state
case validate_referral_code(referral_code, socket) do
{:ok, _} ->
socket =
socket
|> assign(referral_code: referral_code)
|> assign(referral_code_error: nil)
changeset = Auth.change_user_registration(%User{}, user_params)
{:noreply, assign_form(socket, Map.put(changeset, :action, :validate))}
{:error, error_message} ->
socket =
socket
|> assign(referral_code: referral_code)
|> assign(referral_code_error: error_message)
changeset = Auth.change_user_registration(%User{}, user_params)
{:noreply, assign_form(socket, Map.put(changeset, :action, :validate))}
end
end
# Determines whether the user has taken manual ownership of the username field.
#
# `phx-change` reports the touched field in `_target`. Typing a non-empty value
# into the username field claims it (auto-sync stops); clearing it releases
# ownership so auto-sync from email resumes. Any other field leaves the current
# ownership state untouched.
defp username_manually_edited?(socket, params, user_params) do
case params["_target"] do
["user", "username"] -> String.trim(user_params["username"] || "") != ""
_ -> socket.assigns.username_edited
end
end
# While the username is still auto-managed, blank it so the registration
# changeset regenerates it from the (possibly just-corrected) email. Once the
# user owns the field, their value is passed through unchanged.
defp maybe_sync_username(user_params, true = _username_edited), do: user_params
defp maybe_sync_username(user_params, false = _username_edited),
do: Map.put(user_params, "username", "")
defp assign_form(socket, %Ecto.Changeset{} = changeset) do
form = to_form(changeset, as: "user")
if changeset.valid? do
assign(socket, form: form, check_errors: false)
else
assign(socket, form: form)
end
end
defp validate_referral_code(referral_code, socket) do
cond do
# If referral codes are disabled, always allow registration
not socket.assigns.referral_codes_enabled ->
{:ok, nil}
# If referral codes are required but none provided
socket.assigns.referral_codes_required and
(is_nil(referral_code) or String.trim(referral_code) == "") ->
{:error, "Referral code is required"}
# If referral code is provided, validate it
referral_code && String.trim(referral_code) != "" ->
validate_referral_code_value(String.trim(referral_code))
# If referral codes are optional and none provided
true ->
{:ok, nil}
end
end
defp validate_referral_code_value(code_string) do
case Referrals.get_code_by_string(code_string) do
nil ->
{:error, "Invalid referral code"}
code ->
cond do
not code.status ->
{:error, "This referral code is no longer active"}
Referrals.expired?(code) ->
{:error, "This referral code has expired"}
Referrals.usage_limit_reached?(code) ->
{:error, "This referral code has reached its usage limit"}
true ->
{:ok, code}
end
end
end
defp generate_session_id do
:crypto.strong_rand_bytes(16) |> Base.encode64()
end
defp load_pending_invitation(nil), do: nil
defp load_pending_invitation(token) when is_binary(token) do
case Invitations.get_by_token(token) do
{:ok, invitation} -> invitation
{:error, _} -> nil
end
end
end