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/live/settings/authorization.ex
defmodule PhoenixKitWeb.Live.Settings.Authorization do
@moduledoc """
Authorization settings management LiveView for PhoenixKit.
Manages login page branding and authentication methods including
magic links and OAuth provider configuration.
"""
use PhoenixKitWeb, :live_view
use Gettext, backend: PhoenixKitWeb.Gettext
alias PhoenixKit.Modules.Storage.URLSigner
alias PhoenixKit.Settings
alias PhoenixKit.Users.OAuthConfig
require Logger
def mount(_params, _session, socket) do
current_settings = Settings.list_all_settings()
defaults = Settings.get_defaults()
setting_options = Settings.get_setting_options()
merged_settings = Map.merge(defaults, current_settings)
changeset = Settings.change_settings(merged_settings)
socket =
socket
|> assign(:page_title, "Authorization Settings")
|> assign(:settings, merged_settings)
|> assign(:saved_settings, merged_settings)
|> assign(:setting_options, setting_options)
|> assign(:changeset, changeset)
|> assign(:saving, false)
|> assign(
:project_title,
merged_settings["project_title"] || PhoenixKit.Config.get(:project_title, "PhoenixKit")
)
|> assign(:show_media_selector, false)
|> assign(:media_selection_target, nil)
{:ok, socket}
end
def handle_params(_params, _url, socket) do
{:noreply, socket}
end
def handle_event("validate_settings", %{"settings" => settings_params}, socket) do
changeset = Settings.validate_settings(settings_params)
socket =
socket
|> assign(:settings, settings_params)
|> assign(:changeset, changeset)
{:noreply, socket}
end
def handle_event("save_settings", %{"settings" => settings_params}, socket) do
socket = assign(socket, :saving, true)
case Settings.update_settings(settings_params) do
{:ok, updated_settings} ->
OAuthConfig.configure_providers()
changeset = Settings.change_settings(updated_settings)
socket =
socket
|> assign(:settings, updated_settings)
|> assign(:saved_settings, updated_settings)
|> assign(:changeset, changeset)
|> assign(:saving, false)
|> put_flash(:info, "Authorization settings updated successfully")
{:noreply, socket}
{:error, errors} ->
error_msg = format_error_message(errors)
socket =
socket
|> assign(:saving, false)
|> put_flash(:error, error_msg)
{:noreply, socket}
end
end
def handle_event("test_oauth", %{"provider" => provider}, socket) do
provider_atom = String.to_existing_atom(provider)
case OAuthConfig.test_connection(provider_atom) do
{:ok, message} ->
{:noreply, put_flash(socket, :info, message)}
{:error, message} ->
{:noreply, put_flash(socket, :error, message)}
end
end
def handle_event("reload_oauth_config", _params, socket) do
OAuthConfig.configure_providers()
{:noreply, put_flash(socket, :info, "OAuth configuration reloaded from database")}
end
def handle_event("open_media_selector", %{"target" => target}, socket) do
{:noreply,
socket
|> assign(:show_media_selector, true)
|> assign(:media_selection_target, String.to_existing_atom(target))}
end
def handle_event("clear_branding_image", %{"target" => target}, socket) do
key =
case target do
"logo" -> "auth_logo_file_uuid"
"background" -> "auth_background_image_file_uuid"
"background_mobile" -> "auth_background_image_mobile_file_uuid"
end
settings = Map.put(socket.assigns.settings, key, "")
{:noreply, assign(socket, :settings, settings)}
end
## Media selector callbacks
def handle_info({:media_selected, file_uuids}, socket) do
file_uuid = List.first(file_uuids) || ""
key =
case socket.assigns.media_selection_target do
:logo -> "auth_logo_file_uuid"
:background -> "auth_background_image_file_uuid"
:background_mobile -> "auth_background_image_mobile_file_uuid"
end
settings = Map.put(socket.assigns.settings, key, file_uuid)
{:noreply,
socket
|> assign(:settings, settings)
|> assign(:show_media_selector, false)
|> assign(:media_selection_target, nil)}
end
def handle_info({:media_selector_closed}, socket) do
{:noreply,
socket
|> assign(:show_media_selector, false)
|> assign(:media_selection_target, nil)}
end
# Helper functions
defp format_error_message(%Ecto.Changeset{} = changeset) do
changeset
|> Ecto.Changeset.traverse_errors(fn {msg, opts} ->
Enum.reduce(opts, msg, fn {key, value}, acc ->
String.replace(acc, "%{#{key}}", to_string(value))
end)
end)
|> Map.values()
|> List.flatten()
|> Enum.join(", ")
end
def signed_preview_url(file_uuid, variant) do
URLSigner.signed_url(file_uuid, variant)
end
def get_oauth_callback_url(settings, provider) do
site_url = settings["site_url"] || "https://example.com"
url_prefix = PhoenixKit.Config.get_url_prefix()
"#{site_url}#{url_prefix}/users/auth/#{provider}/callback"
end
@doc """
Collapsible per-provider OAuth setup guide: the callback-URL box with a copy
button, the provider-specific console steps (slot), and the reverse-proxy
notice. One component instead of four hand-copied ~95-line blocks.
"""
attr :callback_url, :string, required: true
attr :copy_hint, :string, required: true, doc: "e.g. \"Copy this URL to Google Cloud Console\""
slot :steps, required: true, doc: "provider-specific <li> instruction items"
def oauth_setup_instructions(assigns) do
~H"""
<div class="collapse collapse-arrow bg-base-200 mt-4">
<input type="checkbox" class="peer" />
<div class="collapse-title text-sm font-medium">
{gettext("Setup Instructions")}
</div>
<div class="collapse-content text-sm space-y-4">
<%!-- Callback URL --%>
<div class="bg-base-100 border border-base-300 rounded-lg p-4">
<div class="flex items-start gap-3">
<.icon
name="hero-information-circle"
class="stroke-current shrink-0 h-5 w-5 text-info mt-0.5"
/>
<div class="flex-1 min-w-0">
<div class="text-sm font-semibold text-base-content mb-2">
{gettext("Callback URL")}
</div>
<div class="flex items-center gap-2">
<code class="flex-1 px-3 py-2 bg-base-200 text-base-content border border-base-300 rounded font-mono text-xs break-all">
{@callback_url}
</code>
<button
type="button"
class="btn btn-sm btn-square btn-outline"
onclick={"navigator.clipboard.writeText('#{@callback_url}')"}
title={gettext("Copy to clipboard")}
>
<.icon name="hero-clipboard" class="h-4 w-4" />
</button>
</div>
<div class="text-xs text-base-content/60 mt-2">{@copy_hint}</div>
</div>
</div>
</div>
<%!-- Provider-specific steps --%>
<div class="space-y-3">
<ol class="list-decimal list-inside space-y-2 text-base-content/80">
{render_slot(@steps)}
</ol>
</div>
<%!-- Reverse Proxy Notice --%>
<div class="bg-base-200 border border-base-300 rounded-lg p-3 text-xs">
<div class="flex items-start gap-2">
<.icon name="hero-information-circle" class="h-4 w-4 text-info shrink-0 mt-0.5" />
<div class="text-base-content/80">
<strong class="text-base-content">
{gettext("Reverse Proxy Users:")}
</strong>
{gettext("If behind nginx/apache, ensure")}
<code class="px-1 py-0.5 bg-base-300 text-base-content rounded text-xs">
X-Forwarded-Proto
</code>
{gettext("header is set to")}
<code class="px-1 py-0.5 bg-base-300 text-base-content rounded text-xs">
https
</code>
</div>
</div>
</div>
</div>
</div>
"""
end
end