Packages

phoenix_kit

1.7.153
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 users magic_link_registration.ex
Raw

lib/phoenix_kit/users/magic_link_registration.ex

defmodule PhoenixKit.Users.MagicLinkRegistration do
@moduledoc """
Two-step user registration via Magic Link email.
Flow:
1. User enters email
2. Receives magic link via email
3. Clicks link to complete registration with profile details
"""
import Ecto.Query, warn: false
alias PhoenixKit.RepoHelper, as: Repo
alias PhoenixKit.Config
alias PhoenixKit.Modules.Referrals
alias PhoenixKit.Settings
alias PhoenixKit.Users.Auth
alias PhoenixKit.Users.Auth.{User, UserToken}
alias PhoenixKit.Utils.Date, as: UtilsDate
alias PhoenixKit.Utils.Routes
@magic_link_registration_context "magic_link_registration"
@doc """
Sends a registration magic link to the specified email address.
"""
def send_registration_link(email) when is_binary(email) do
email = String.trim(email) |> String.downcase()
if valid_email?(email) do
case Auth.get_user_by_email(email) do
%User{} ->
{:error, :email_already_exists}
nil ->
generate_and_send_token(email)
end
else
{:error, :invalid_email}
end
end
@doc """
Verifies a magic link registration token.
"""
def verify_registration_token(token) when is_binary(token) do
case Base.url_decode64(token, padding: false) do
{:ok, decoded_token} ->
hashed_token = :crypto.hash(:sha256, decoded_token)
expiry_minutes = get_expiry_minutes()
query =
from t in UserToken,
where:
t.token == ^hashed_token and
t.context == ^@magic_link_registration_context and
t.inserted_at > ago(^expiry_minutes, "minute"),
select: t
case Repo.one(query) do
%UserToken{sent_to: email} = _token ->
{:ok, email}
nil ->
{:error, :invalid_token}
end
:error ->
{:error, :invalid_token}
end
end
@doc """
Completes user registration using a magic link token.
"""
def complete_registration(token, attrs, ip_address \\ nil)
when is_binary(token) and is_map(attrs) do
case verify_registration_token(token) do
{:ok, email} ->
case Auth.get_user_by_email(email) do
%User{} ->
delete_registration_token(token)
{:error, :email_already_exists}
nil ->
do_complete_registration(email, attrs, ip_address, token)
end
{:error, reason} ->
{:error, reason}
end
end
@doc """
Deletes a registration token.
"""
def delete_registration_token(token) when is_binary(token) do
case Base.url_decode64(token, padding: false) do
{:ok, decoded_token} ->
hashed_token = :crypto.hash(:sha256, decoded_token)
query =
from t in UserToken,
where: t.token == ^hashed_token and t.context == ^@magic_link_registration_context
Repo.delete_all(query)
:ok
:error ->
:ok
end
end
@doc """
Generates a registration magic link URL.
"""
def registration_link_url(token) when is_binary(token) do
Routes.url("/users/register/verify/#{token}")
end
@doc """
Cleans up expired registration tokens.
"""
def cleanup_expired_tokens do
expiry_minutes = get_expiry_minutes()
query =
from t in UserToken,
where:
t.context == ^@magic_link_registration_context and
t.inserted_at <= ago(^expiry_minutes, "minute")
{deleted_count, _} = Repo.delete_all(query)
deleted_count
end
# Private functions
defp generate_and_send_token(email) do
{token, user_token} =
UserToken.build_email_token_for_context(email, @magic_link_registration_context)
case Repo.insert(user_token) do
{:ok, _} ->
registration_url = registration_link_url(token)
case send_registration_email(email, registration_url) do
{:ok, _} ->
{:ok, email, token}
{:error, reason} ->
delete_registration_token(token)
{:error, reason}
end
{:error, changeset} ->
{:error, changeset}
end
end
defp do_complete_registration(email, attrs, ip_address, token) do
{referral_code, attrs} = Map.pop(attrs, "referral_code")
attrs =
attrs
|> Map.put("email", email)
|> Map.put("confirmed_at", UtilsDate.utc_now())
track_geolocation = Settings.get_boolean_setting("track_registration_geolocation", false)
result =
if track_geolocation && ip_address do
Auth.register_user_with_geolocation(attrs, ip_address)
else
Auth.register_user(attrs, ip_address)
end
case result do
{:ok, user} ->
if referral_code do
process_referral_code(user, referral_code)
end
delete_registration_token(token)
{:ok, user}
{:error, changeset} ->
{:error, changeset}
end
end
defp send_registration_email(email, registration_url) do
temp_user = %{email: email}
Auth.UserNotifier.deliver_magic_link_registration(temp_user, registration_url)
end
defp process_referral_code(user, referral_code) when is_binary(referral_code) do
if Code.ensure_loaded?(Referrals) do
case Referrals.get_code_by_string(referral_code) do
nil -> :ok
code -> Referrals.use_code(code.code, user.uuid)
end
end
:ok
end
defp valid_email?(email) when is_binary(email) do
String.match?(email, ~r/^[^\s]+@[^\s]+\.[^\s]+$/)
end
def get_expiry_minutes do
Config.get(:magic_link_for_registration_expiry_minutes, 30)
end
end