diff --git a/lib/accounts/user.ex b/lib/accounts/user.ex index 53156b4..daf29ba 100644 --- a/lib/accounts/user.ex +++ b/lib/accounts/user.ex @@ -71,6 +71,19 @@ defmodule Mv.Accounts.User do accept [:email] end + # Admin action for direct password changes in admin panel + # Uses the official Ash Authentication HashPasswordChange with correct context + update :admin_set_password do + accept [:email] + argument :password, :string, allow_nil?: false, sensitive?: true + + # Set the strategy context that HashPasswordChange expects + change set_context(%{strategy_name: :password}) + + # Use the official Ash Authentication password change + change AshAuthentication.Strategy.Password.HashPasswordChange + end + read :get_by_subject do description "Get a user by the subject claim in a JWT" argument :subject, :string, allow_nil?: false @@ -121,6 +134,14 @@ defmodule Mv.Accounts.User do identity :unique_oidc_id, [:oidc_id] end + # Global validations - applied to all relevant actions + validations do + # Password strength policy: minimum 8 characters for all password-related actions + validate string_length(:password, min: 8) do + where action_is([:register_with_password, :admin_set_password]) + end + end + # You can customize this if you wish, but this is a safe default that # only allows user data to be interacted with via AshAuthentication. # policies do diff --git a/lib/mv_web/live/user_live/form.ex b/lib/mv_web/live/user_live/form.ex index dd78c0c..b599bc1 100644 --- a/lib/mv_web/live/user_live/form.ex +++ b/lib/mv_web/live/user_live/form.ex @@ -13,23 +13,81 @@ defmodule MvWeb.UserLive.Form do <.form for={@form} id="user-form" phx-change="validate" phx-submit="save"> <.input field={@form[:email]} label={gettext("Email")} required type="email" /> - <%= if @user do %> -
-

- {gettext("Note")}: {gettext( - "Password can only be changed through authentication functions." - )} -

-
- <% else %> -
-

- {gettext("Note")}: {gettext( - "Users created here will need to set their password through the authentication system." - )} -

-
- <% end %> + +
+ + + <%= if @show_password_fields do %> +
+ <.input + field={@form[:password]} + label={gettext("Password")} + type="password" + required + autocomplete="new-password" + /> + + + <%= if !@user do %> + <.input + field={@form[:password_confirmation]} + label={gettext("Confirm Password")} + type="password" + required + autocomplete="new-password" + /> + <% end %> + +
+

{gettext("Password requirements")}:

+
    +
  • {gettext("At least 8 characters")}
  • +
  • {gettext("Include both letters and numbers")}
  • +
  • {gettext("Consider using special characters")}
  • +
+
+ + <%= if @user do %> +
+

+ {gettext("Admin Note")}: {gettext( + "As an administrator, you can directly set a new password for this user using the same secure Ash Authentication system." + )} +

+
+ <% end %> +
+ <% else %> + <%= if @user do %> +
+

+ {gettext("Note")}: {gettext( + "Check 'Change Password' above to set a new password for this user." + )} +

+
+ <% else %> +
+

+ {gettext("Note")}: {gettext( + "User will be created without a password. Check 'Set Password' to add one." + )} +

+
+ <% end %> + <% end %> +
<.button phx-disable-with={gettext("Saving...")} variant="primary"> {gettext("Save User")} @@ -56,6 +114,7 @@ defmodule MvWeb.UserLive.Form do |> assign(:return_to, return_to(params["return_to"])) |> assign(user: user) |> assign(:page_title, page_title) + |> assign(:show_password_fields, false) |> assign_form()} end @@ -63,6 +122,19 @@ defmodule MvWeb.UserLive.Form do defp return_to(_), do: "index" @impl true + def handle_event("toggle_password_section", _params, socket) do + show_password_fields = !socket.assigns.show_password_fields + + socket = + socket + |> assign(:show_password_fields, show_password_fields) + |> assign_form() + + {:noreply, socket} + end + + + def handle_event("validate", %{"user" => user_params}, socket) do {:noreply, assign(socket, form: AshPhoenix.Form.validate(socket.assigns.form, user_params))} end @@ -86,12 +158,16 @@ defmodule MvWeb.UserLive.Form do defp notify_parent(msg), do: send(self(), {__MODULE__, msg}) - defp assign_form(%{assigns: %{user: user}} = socket) do + defp assign_form(%{assigns: %{user: user, show_password_fields: show_password_fields}} = socket) do form = if user do - AshPhoenix.Form.for_update(user, :update_user, domain: Mv.Accounts, as: "user") + # For existing users, use admin password action if password fields are shown + action = if show_password_fields, do: :admin_set_password, else: :update_user + AshPhoenix.Form.for_update(user, action, domain: Mv.Accounts, as: "user") else - AshPhoenix.Form.for_create(Mv.Accounts.User, :create_user, + # For new users, use password registration if password fields are shown + action = if show_password_fields, do: :register_with_password, else: :create_user + AshPhoenix.Form.for_create(Mv.Accounts.User, action, domain: Mv.Accounts, as: "user" ) diff --git a/priv/gettext/de/LC_MESSAGES/default.po b/priv/gettext/de/LC_MESSAGES/default.po index d466469..30d8ad6 100644 --- a/priv/gettext/de/LC_MESSAGES/default.po +++ b/priv/gettext/de/LC_MESSAGES/default.po @@ -502,3 +502,53 @@ msgstr "Alle Benutzer auswählen" #, elixir-autogen, elixir-format msgid "Select user" msgstr "Benutzer auswählen" + +#: lib/mv_web/live/user_live/form.ex:23 +#, elixir-autogen, elixir-format +msgid "Set Password" +msgstr "Passwort setzen" + +#: lib/mv_web/live/user_live/form.ex:35 +#, elixir-autogen, elixir-format +msgid "Password" +msgstr "Passwort" + +#: lib/mv_web/live/user_live/form.ex:42 +#, elixir-autogen, elixir-format +msgid "Confirm Password" +msgstr "Passwort bestätigen" + +#: lib/mv_web/live/user_live/form.ex:48 +#, elixir-autogen, elixir-format +msgid "Password requirements" +msgstr "Passwort-Anforderungen" + +#: lib/mv_web/live/user_live/form.ex:50 +#, elixir-autogen, elixir-format +msgid "At least 8 characters" +msgstr "Mindestens 8 Zeichen" + +#: lib/mv_web/live/user_live/form.ex:51 +#, elixir-autogen, elixir-format +msgid "Include both letters and numbers" +msgstr "Buchstaben und Zahlen verwenden" + +#: lib/mv_web/live/user_live/form.ex:52 +#, elixir-autogen, elixir-format +msgid "Consider using special characters" +msgstr "Sonderzeichen empfohlen" + +#: lib/mv_web/live/user_live/form.ex:57 +#, elixir-autogen, elixir-format +msgid "User will be created without a password. Check 'Set Password' to add one." +msgstr "Benutzer wird ohne Passwort erstellt. Aktivieren Sie 'Passwort setzen', um eines hinzuzufügen." + +#: lib/mv_web/live/user_live/form.ex:75 +#, elixir-autogen, elixir-format +msgid "As an administrator, you can directly set a new password for this user using the same secure Ash Authentication system." +msgstr "Als Administrator können Sie direkt ein neues Passwort für diesen Benutzer setzen, wobei das gleiche sichere Ash Authentication System verwendet wird." + +#: lib/mv_web/live/user_live/form.ex:85 +#, elixir-autogen, elixir-format +msgid "Check 'Change Password' above to set a new password for this user." +msgstr "Aktivieren Sie 'Passwort ändern' oben, um ein neues Passwort für diesen Benutzer zu setzen." diff --git a/priv/gettext/en/LC_MESSAGES/default.po b/priv/gettext/en/LC_MESSAGES/default.po index 4bddf9a..0536761 100644 --- a/priv/gettext/en/LC_MESSAGES/default.po +++ b/priv/gettext/en/LC_MESSAGES/default.po @@ -492,4 +492,54 @@ msgstr "" #: lib/mv_web/live/user_live/form.ex:27 #, elixir-autogen, elixir-format msgid "Users created here will need to set their password through the authentication system." -msgstr "" +msgstr "Users created here will need to set their password through the authentication system." + +#: lib/mv_web/live/user_live/form.ex:23 +#, elixir-autogen, elixir-format +msgid "Set Password" +msgstr "Set Password" + +#: lib/mv_web/live/user_live/form.ex:35 +#, elixir-autogen, elixir-format +msgid "Password" +msgstr "Password" + +#: lib/mv_web/live/user_live/form.ex:42 +#, elixir-autogen, elixir-format +msgid "Confirm Password" +msgstr "Confirm Password" + +#: lib/mv_web/live/user_live/form.ex:48 +#, elixir-autogen, elixir-format +msgid "Password requirements" +msgstr "Password requirements" + +#: lib/mv_web/live/user_live/form.ex:50 +#, elixir-autogen, elixir-format +msgid "At least 8 characters" +msgstr "At least 8 characters" + +#: lib/mv_web/live/user_live/form.ex:51 +#, elixir-autogen, elixir-format +msgid "Include both letters and numbers" +msgstr "Include both letters and numbers" + +#: lib/mv_web/live/user_live/form.ex:52 +#, elixir-autogen, elixir-format +msgid "Consider using special characters" +msgstr "Consider using special characters" + +#: lib/mv_web/live/user_live/form.ex:57 +#, elixir-autogen, elixir-format +msgid "User will be created without a password. Check 'Set Password' to add one." +msgstr "User will be created without a password. Check 'Set Password' to add one." + +#: lib/mv_web/live/user_live/form.ex:75 +#, elixir-autogen, elixir-format +msgid "As an administrator, you can directly set a new password for this user using the same secure Ash Authentication system." +msgstr "As an administrator, you can directly set a new password for this user using the same secure Ash Authentication system." + +#: lib/mv_web/live/user_live/form.ex:85 +#, elixir-autogen, elixir-format +msgid "Check 'Change Password' above to set a new password for this user." +msgstr "Check 'Change Password' above to set a new password for this user."