some not working changes trying to fix user login to include a username
This commit is contained in:
@@ -434,6 +434,10 @@ defmodule Mixer.Accounts.User do
|
||||
|
||||
identities do
|
||||
identity :unique_email, [:email]
|
||||
identity :unique_username, [:username], nils_distinct?: true
|
||||
identity :unique_username, [:username] do
|
||||
eager_check_with Mixer.Accounts
|
||||
message "is already taken"
|
||||
nils_distinct? true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -18,6 +18,6 @@ defmodule MixerWeb.AuthOverrides do
|
||||
|
||||
# Inject the username field into the password registration form
|
||||
override AshAuthentication.Phoenix.Components.Password do
|
||||
set :register_extra_component, &MixerWeb.AuthComponents.username_register_field/1
|
||||
set :register_extra_component, &MixerWeb.AuthComponents.username_field/1
|
||||
end
|
||||
end
|
||||
|
||||
@@ -11,7 +11,7 @@ defmodule MixerWeb.AuthComponents do
|
||||
Receives `form` (an `AshPhoenix.Form`) as an assign via the
|
||||
`register_extra_component` override.
|
||||
"""
|
||||
def username_register_field(assigns) do
|
||||
def username_field(assigns) do
|
||||
field = assigns.form[:username]
|
||||
|
||||
assigns =
|
||||
@@ -27,7 +27,7 @@ defmodule MixerWeb.AuthComponents do
|
||||
Username
|
||||
</label>
|
||||
<div class="flex">
|
||||
<span class="input rounded-r-none border-r-0 text-base-content/50 select-none">@</span>
|
||||
<span class="flex items-center justify-center px-4 bg-base-200 border border-base-300 border-r-0 rounded-l-lg text-base-content/50 select-none">@</span>
|
||||
<input
|
||||
type="text"
|
||||
id={@field_id}
|
||||
@@ -35,17 +35,21 @@ defmodule MixerWeb.AuthComponents do
|
||||
value={@field_value}
|
||||
class={"input w-full rounded-l-none #{if @field_errors != [], do: "input-error", else: ""}"}
|
||||
placeholder="your_handle"
|
||||
autocomplete="username"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<p :if={@field_errors != []} class="mt-1 text-xs text-error">
|
||||
{@field_errors |> List.first() |> elem(0)}
|
||||
</p>
|
||||
<p :if={@field_errors == []} class="mt-1 text-xs text-base-content/50">
|
||||
3–30 characters · letters, numbers, underscores
|
||||
<p :for={error <- @field_errors} class="mt-1 text-xs text-error">
|
||||
{translate_error(error)}
|
||||
</p>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
def translate_error({msg, opts}) do
|
||||
if count = opts[:count] do
|
||||
Gettext.dngettext(MixerWeb.Gettext, "errors", msg, msg, count, opts)
|
||||
else
|
||||
Gettext.dgettext(MixerWeb.Gettext, "errors", msg, opts)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -37,7 +37,7 @@ defmodule MixerWeb.MagicSignInLive do
|
||||
domain = Info.authentication_domain!(resource)
|
||||
|
||||
# Determine whether this user needs to pick a username
|
||||
needs_username? = needs_username?(token, resource)
|
||||
needs_username? = needs_username?(token, resource, domain)
|
||||
|
||||
form =
|
||||
resource
|
||||
@@ -75,70 +75,26 @@ defmodule MixerWeb.MagicSignInLive do
|
||||
@spec render(Socket.assigns()) :: Rendered.t()
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
<div class={override_for(@overrides, :root_class)}>
|
||||
<.live_component
|
||||
module={AshAuthentication.Phoenix.Components.Banner}
|
||||
id="magic-sign-in-banner"
|
||||
overrides={@overrides}
|
||||
/>
|
||||
<div class="min-h-screen flex flex-col items-center justify-center bg-base-100 p-4">
|
||||
<div class="w-full max-w-sm mb-8 text-center">
|
||||
<.live_component
|
||||
module={AshAuthentication.Phoenix.Components.Banner}
|
||||
id="magic-sign-in-banner"
|
||||
overrides={@overrides}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="w-full max-w-sm p-6 bg-base-100 border border-base-200 rounded-xl shadow-sm">
|
||||
<.form :let={form} for={@form} phx-change="validate" phx-submit="submit" phx-trigger-action={@trigger_action}
|
||||
action={auth_path(@socket, @subject_name, @auth_routes_prefix, @strategy, :sign_in)}
|
||||
method="POST">
|
||||
|
||||
<div style="max-width: 400px; margin: 0 auto; padding: 1rem;">
|
||||
<.form
|
||||
:let={form}
|
||||
for={@form}
|
||||
phx-submit="submit"
|
||||
phx-trigger-action={@trigger_action}
|
||||
action={
|
||||
auth_path(
|
||||
@socket,
|
||||
@subject_name,
|
||||
@auth_routes_prefix,
|
||||
@strategy,
|
||||
:sign_in
|
||||
)
|
||||
}
|
||||
method="POST"
|
||||
>
|
||||
{hidden_input(form, :token, [])}
|
||||
|
||||
<%!-- Username field — only shown for new or username-less users --%>
|
||||
<div :if={@needs_username?} class="mt-2 mb-4">
|
||||
<label
|
||||
for={form[:username].id}
|
||||
class="block text-sm font-medium text-base-content mb-1"
|
||||
>
|
||||
Choose a username
|
||||
</label>
|
||||
<div class="flex">
|
||||
<span class="input rounded-r-none border-r-0 text-base-content/50 select-none">
|
||||
@
|
||||
</span>
|
||||
<input
|
||||
type="text"
|
||||
id={form[:username].id}
|
||||
name={form[:username].name}
|
||||
value={form[:username].value || ""}
|
||||
class={"input w-full rounded-l-none #{if form[:username].errors != [], do: "input-error", else: ""}"}
|
||||
placeholder="your_handle"
|
||||
autocomplete="username"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<p
|
||||
:if={form[:username].errors != []}
|
||||
class="mt-1 text-xs text-error"
|
||||
>
|
||||
{form[:username].errors |> List.first() |> elem(0)}
|
||||
</p>
|
||||
<p :if={form[:username].errors == []} class="mt-1 text-xs text-base-content/50">
|
||||
3–30 characters · letters, numbers, underscores
|
||||
</p>
|
||||
</div>
|
||||
<%!-- Using the unified component --%>
|
||||
<MixerWeb.AuthComponents.username_field :if={@needs_username?} form={form} />
|
||||
|
||||
{submit("Sign in",
|
||||
class: "btn btn-primary w-full mt-2",
|
||||
phx_disable_with: "Signing in…"
|
||||
)}
|
||||
{submit("Sign in", class: "btn btn-primary w-full", phx_disable_with: "Signing in...")}
|
||||
</.form>
|
||||
</div>
|
||||
</div>
|
||||
@@ -156,28 +112,43 @@ defmodule MixerWeb.MagicSignInLive do
|
||||
|
||||
form_params = Map.get(params, subject_name, %{})
|
||||
|
||||
form = Form.validate(socket.assigns.form, form_params)
|
||||
# Use Form.validate with :all_errors to surface uniqueness constraints
|
||||
form =
|
||||
socket.assigns.form
|
||||
|> Form.validate(form_params, errors: true)
|
||||
|
||||
socket =
|
||||
socket
|
||||
|> assign(:form, form)
|
||||
|> assign(:trigger_action, form.valid?)
|
||||
if form.valid? do
|
||||
# Only trigger the POST redirect if the data is truly valid
|
||||
{:noreply, assign(socket, form: form, trigger_action: true)}
|
||||
else
|
||||
socket =
|
||||
socket
|
||||
|> assign(form: form, trigger_action: false)
|
||||
{:noreply, socket}
|
||||
end
|
||||
end
|
||||
|
||||
{:noreply, socket}
|
||||
@impl true
|
||||
def handle_event("validate", params, socket) do
|
||||
subject_name = socket.assigns.subject_name |> to_string() |> slugify()
|
||||
form_params = Map.get(params, subject_name, %{})
|
||||
|
||||
form = Form.validate(socket.assigns.form, form_params, errors: true)
|
||||
{:noreply, assign(socket, form: form)}
|
||||
end
|
||||
|
||||
# ── Helpers ──────────────────────────────────────────────────────────────────
|
||||
|
||||
# Returns true if the user is new or has no username set yet.
|
||||
defp needs_username?(nil, _resource), do: true
|
||||
defp needs_username?(nil, _resource, _domain), do: true
|
||||
|
||||
defp needs_username?(token, resource) do
|
||||
defp needs_username?(token, resource, domain) do
|
||||
with {:ok, claims} <- AshAuthentication.Jwt.peek(token),
|
||||
# 1. Try to find an existing user from the claims
|
||||
user <- find_user(claims, resource),
|
||||
user <- find_user(claims, resource, domain),
|
||||
# 2. If a user exists, check if they already have a username
|
||||
false <- is_nil(user) do
|
||||
is_nil(user.username)
|
||||
is_nil(user.username) or user.username == ""
|
||||
else
|
||||
_ ->
|
||||
# Unknown / new user — ask for username to be safe
|
||||
@@ -185,7 +156,7 @@ defmodule MixerWeb.MagicSignInLive do
|
||||
end
|
||||
end
|
||||
|
||||
defp find_user(claims, resource) do
|
||||
defp find_user(claims, resource, domain) do
|
||||
# Try 'sub' first if it looks like a user subject (e.g. "User:123")
|
||||
sub = Map.get(claims, "sub")
|
||||
|
||||
@@ -201,7 +172,11 @@ defmodule MixerWeb.MagicSignInLive do
|
||||
user ||
|
||||
case Map.get(claims, "identity") || Map.get(claims, "email") do
|
||||
email when is_binary(email) ->
|
||||
case Ash.get(resource, [email: email], action: :get_by_email, authorize?: false) do
|
||||
# Use for_read with the explicit action and arguments
|
||||
resource
|
||||
|> Ash.Query.for_read(:get_by_email, %{email: email})
|
||||
|> Ash.read_one(domain: domain, authorize?: false)
|
||||
|> case do
|
||||
{:ok, user} -> user
|
||||
_ -> nil
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user