Packages
phoenix_kit
1.2.2
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/magic_link_live.ex
defmodule PhoenixKitWeb.Users.MagicLinkLive do
@moduledoc """
LiveView for magic link authentication.
This LiveView handles the magic link authentication flow:
1. User enters their email address
2. System sends magic link to their email
3. User clicks link to authenticate
The magic link verification is handled by the controller, this LiveView
handles the email input and confirmation flow.
"""
use PhoenixKitWeb, :live_view
alias PhoenixKit.Admin.Presence
alias PhoenixKit.Mailer
alias PhoenixKit.Users.MagicLink
@impl true
def mount(_params, session, socket) 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: DateTime.utc_now(),
ip_address: get_connect_info(socket, :peer_data) |> extract_ip_address(),
user_agent: get_connect_info(socket, :user_agent),
current_page: "/phoenix_kit/users/magic-link"
})
end
form = to_form(%{"email" => ""}, as: "magic_link")
{:ok,
socket
|> assign(:page_title, "Magic Link Login")
|> assign(:form, form)
|> assign(:sent, false)
|> assign(:loading, false)
|> assign(:error, nil)}
end
@impl true
def handle_event("validate", %{"magic_link" => magic_link_params}, socket) do
form = to_form(magic_link_params, as: "magic_link")
{:noreply, assign(socket, form: form)}
end
@impl true
def handle_event("send_magic_link", %{"magic_link" => %{"email" => email}}, socket) do
if valid_email?(email) do
form = to_form(%{"email" => email}, as: "magic_link")
{:noreply,
socket
|> assign(:form, form)
|> assign(:loading, true)
|> assign(:error, nil)
|> send_magic_link_async(email)}
else
form = to_form(%{"email" => email}, as: "magic_link")
{:noreply,
socket
|> assign(:form, form)
|> assign(:error, "Please enter a valid email address")}
end
end
@impl true
def handle_async(:send_magic_link, {:ok, result}, socket) do
case result do
{:ok, _user} ->
{:noreply,
socket
|> assign(:sent, true)
|> assign(:loading, false)
|> put_flash(:info, "Magic link sent! Check your email.")}
{:error, _} ->
# For security, we don't reveal whether the email exists or not
{:noreply,
socket
|> assign(:sent, true)
|> assign(:loading, false)
|> put_flash(:info, "If that email address exists, a magic link has been sent.")}
end
end
@impl true
def handle_async(:send_magic_link, {:exit, _reason}, socket) do
{:noreply,
socket
|> assign(:loading, false)
|> assign(:error, "Failed to send magic link. Please try again.")}
end
# Send magic link email to user and handle response
defp send_magic_link_email_to_user(user, token) do
magic_link_url = MagicLink.magic_link_url(token)
case Mailer.send_magic_link_email(user, magic_link_url) do
{:ok, _} -> {:ok, user}
{:error, reason} -> {:error, reason}
end
end
# Process the magic link sending in the background
defp send_magic_link_async(socket, email) do
Phoenix.LiveView.start_async(socket, :send_magic_link, fn ->
case MagicLink.generate_magic_link(email) do
{:ok, user, token} ->
send_magic_link_email_to_user(user, token)
{:error, :user_not_found} ->
# For security, we simulate the same delay as successful case
Process.sleep(100)
{:error, :user_not_found}
{:error, reason} ->
{:error, reason}
end
end)
end
# Simple email validation
defp valid_email?(email) when is_binary(email) do
String.match?(email, ~r/^[^\s]+@[^\s]+\.[^\s]+$/)
end
@impl true
def render(assigns) do
~H"""
<PhoenixKitWeb.Components.LayoutWrapper.app_layout
flash={@flash}
phoenix_kit_current_scope={assigns[:phoenix_kit_current_scope]}
page_title={@page_title}
>
<div class="mx-auto max-w-sm">
<.header class="text-center">
Magic Link Login
<:subtitle>Enter your email to receive a secure login link</:subtitle>
</.header>
<.form
for={@form}
id="magic_link_form"
phx-submit="send_magic_link"
phx-change="validate"
>
<fieldset class="fieldset">
<legend class="fieldset-legend sr-only">Magic Link Authentication</legend>
<div :if={@error} class="alert alert-error text-sm mb-4">
<svg
xmlns="http://www.w3.org/2000/svg"
class="stroke-current shrink-0 h-6 w-6"
fill="none"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<span>{@error}</span>
</div>
<label class="label" for="magic_link_email">Email</label>
<input
id="magic_link_email"
name="magic_link[email]"
type="email"
class="input input-bordered validator w-full"
placeholder="you@example.com"
value={@form.params["email"] || ""}
pattern="^[\w\.-]+@[\w\.-]+\.[a-zA-Z]{2,}$"
title="Please enter a valid email address"
required
/>
<p class="validator-hint">Please enter a valid email address</p>
<button
type="submit"
phx-disable-with="Sending magic link..."
class="btn btn-primary w-full mt-4"
disabled={@loading || @sent}
>
<%= if @loading do %>
<span class="loading loading-spinner loading-sm mr-2"></span> Sending magic link...
<% else %>
Send Magic Link <span aria-hidden="true">→</span>
<% end %>
</button>
</fieldset>
</.form>
<div :if={@sent} class="mt-6 p-4 bg-green-50 border border-green-200 rounded-md">
<div class="flex">
<.icon name="hero-check-circle" class="h-5 w-5 text-green-400" />
<div class="ml-3">
<h3 class="text-sm font-medium text-green-800">
Magic link sent!
</h3>
<p class="mt-1 text-sm text-green-700">
Check your email for a secure login link. The link will expire in 15 minutes.
</p>
</div>
</div>
</div>
<!-- Development Mode Notice -->
<div :if={show_dev_notice?()} class="alert alert-info text-sm mt-6">
<svg
xmlns="http://www.w3.org/2000/svg"
class="stroke-current shrink-0 h-6 w-6"
fill="none"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
>
</path>
</svg>
<span>
Development mode: Check
<.link href="/dev/mailbox" class="font-semibold underline">mailbox</.link>
for confirmation emails
</span>
</div>
<div class="mt-6">
<div class="relative">
<div class="absolute inset-0 flex items-center">
<div class="w-full border-t border-gray-300" />
</div>
<div class="relative flex justify-center text-sm">
<span class="px-2 bg-white text-gray-500">Or continue with</span>
</div>
</div>
<div class="mt-6 text-center">
<.link navigate="/phoenix_kit/users/log-in" class="text-sm text-brand hover:underline">
Sign in with password
</.link>
</div>
<div class="mt-3 text-center">
<.link
navigate="/phoenix_kit/users/register"
class="text-sm text-gray-600 hover:text-gray-500"
>
Don't have an account? Sign up
</.link>
</div>
</div>
</div>
</PhoenixKitWeb.Components.LayoutWrapper.app_layout>
"""
end
defp show_dev_notice? do
case Application.get_env(:phoenix_kit, PhoenixKit.Mailer)[:adapter] do
Swoosh.Adapters.Local -> true
_ -> false
end
end
defp generate_session_id do
:crypto.strong_rand_bytes(16) |> Base.encode64()
end
defp extract_ip_address(nil), do: "unknown"
defp extract_ip_address(%{address: {a, b, c, d}}), do: "#{a}.#{b}.#{c}.#{d}"
defp extract_ip_address(%{address: address}), do: to_string(address)
defp extract_ip_address(_), do: "unknown"
end