Packages

phoenix_kit

1.7.218
1.7.218 1.7.217 1.7.216 1.7.215 1.7.214 1.7.213 1.7.212 1.7.211 1.7.210 1.7.209 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 confirmation_instructions.ex
Raw

lib/phoenix_kit_web/users/confirmation_instructions.ex

defmodule PhoenixKitWeb.Users.ConfirmationInstructions do
@moduledoc """
LiveView for resending email confirmation instructions.
Allows users to request a new confirmation email if they didn't receive
the original or if the link expired. Only sends if the account exists
and is not already confirmed.
This page is also where the authentication gates park logged-in users
whose email is not yet confirmed (when `require_email_confirmation` is
on), so it moves them along as soon as confirmation happens:
- On mount, an already-confirmed user is redirected onward immediately —
covers a confirmation done elsewhere (or directly in the DB) followed
by a refresh, since the user is reloaded from the DB on every mount.
- While parked, the LiveView listens on this user's own confirmation topic
for `{:user_confirmed, user}` (broadcast by both the email-link flow and
the admin confirm action) and redirects onward live, no refresh needed —
e.g. when the user clicks the emailed link in another tab. It is a
per-user topic, not the site-wide admin users feed, so a parked
non-admin never receives other users' structs.
"Onward" is `?return_to=` (stashed by the gate that parked them), then
the session's `user_return_to`, then the `after_login_path` setting.
"""
use PhoenixKitWeb, :live_view
alias PhoenixKit.Admin.Events
alias PhoenixKit.Users.Auth
alias PhoenixKit.Users.RateLimiter
alias PhoenixKit.Utils.Routes
def mount(params, session, socket) do
user = socket.assigns[:phoenix_kit_current_user]
destination = resolve_destination(params, session)
cond do
user && user.confirmed_at ->
{:ok, redirect(socket, to: destination)}
is_nil(user) ->
{:ok,
socket
|> assign(form: to_form(%{"email" => ""}, as: "user"))
|> assign(awaiting_confirmation?: false)
|> assign(destination: destination)}
true ->
# Subscribe FIRST, then re-read: a confirmation committed between the
# on_mount user load and the subscribe would otherwise be missed, and
# the page would sit there forever despite promising to continue
# automatically. Re-reading after subscribing closes that window —
# either the re-read sees it, or the broadcast reaches us.
if connected?(socket), do: Events.subscribe_to_user_confirmation(user.uuid)
if connected?(socket) and confirmed_since_mount?(user) do
{:ok, redirect(socket, to: destination)}
else
{:ok,
socket
|> assign(form: to_form(%{"email" => user.email}, as: "user"))
|> assign(awaiting_confirmation?: true)
|> assign(destination: destination)}
end
end
end
defp confirmed_since_mount?(user) do
case Auth.get_user(user.uuid) do
%{confirmed_at: confirmed_at} -> not is_nil(confirmed_at)
_ -> false
end
end
def handle_event("send_instructions", %{"user" => %{"email" => email}}, socket) do
# Throttle BEFORE the lookup and answer identically either way. This form
# is public: only an existing unconfirmed account does work (insert a token,
# send mail), so an unthrottled endpoint is both a targeted mail-flood
# vector and a timing oracle for which addresses are registered.
with :ok <- RateLimiter.check_confirmation_resend_rate_limit(email),
%{} = user <- Auth.get_user_by_email(email) do
Auth.deliver_user_confirmation_instructions(
user,
&Routes.url("/users/confirm/#{&1}")
)
end
info =
"If your email is in our system and it has not been confirmed yet, you will receive an email with instructions shortly."
socket = put_flash(socket, :info, info)
# A parked (logged-in, unconfirmed) user stays here so the live
# auto-advance can fire once they click the emailed link; anonymous
# visitors are sent on to the resolved destination.
if socket.assigns.awaiting_confirmation? do
{:noreply, socket}
else
{:noreply, redirect(socket, to: socket.assigns.destination)}
end
end
def handle_info({:user_confirmed, %{uuid: uuid}}, socket) do
current = socket.assigns[:phoenix_kit_current_user]
if current && current.uuid == uuid do
{:noreply,
socket
|> put_flash(:info, gettext("Email confirmed. Welcome!"))
|> redirect(to: socket.assigns.destination)}
else
{:noreply, socket}
end
end
# Defensive: the topic carries only this user's confirmation today.
def handle_info(_msg, socket), do: {:noreply, socket}
defp resolve_destination(params, session) do
Routes.post_auth_path([params["return_to"], session["user_return_to"]])
end
end