Complete Permissions for Groups, Membership Fees, and User Role Assignment closes #404 #405
7 changed files with 180 additions and 4 deletions
58
lib/mv_web/helpers/user_helpers.ex
Normal file
58
lib/mv_web/helpers/user_helpers.ex
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
defmodule MvWeb.Helpers.UserHelpers do
|
||||||
|
@moduledoc """
|
||||||
|
Helper functions for user-related display in the web layer.
|
||||||
|
|
||||||
|
Provides utilities for showing authentication status without exposing
|
||||||
|
sensitive attributes (e.g. hashed_password).
|
||||||
|
"""
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Returns whether the user has password authentication set.
|
||||||
|
|
||||||
|
Only returns true when `hashed_password` is a non-empty string. This avoids
|
||||||
|
treating `nil`, empty string, or forbidden/redacted values (e.g. when the
|
||||||
|
attribute is not visible to the actor) as "has password".
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> user = %{hashed_password: nil}
|
||||||
|
iex> MvWeb.Helpers.UserHelpers.has_password?(user)
|
||||||
|
false
|
||||||
|
|
||||||
|
iex> user = %{hashed_password: "$2b$12$..."}
|
||||||
|
iex> MvWeb.Helpers.UserHelpers.has_password?(user)
|
||||||
|
true
|
||||||
|
|
||||||
|
iex> user = %{hashed_password: ""}
|
||||||
|
iex> MvWeb.Helpers.UserHelpers.has_password?(user)
|
||||||
|
false
|
||||||
|
"""
|
||||||
|
@spec has_password?(map() | struct()) :: boolean()
|
||||||
|
def has_password?(user) when is_map(user) do
|
||||||
|
case Map.get(user, :hashed_password) do
|
||||||
|
hash when is_binary(hash) and byte_size(hash) > 0 -> true
|
||||||
|
_ -> false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Returns whether the user is linked via OIDC/SSO (has a non-empty oidc_id).
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> user = %{oidc_id: nil}
|
||||||
|
iex> MvWeb.Helpers.UserHelpers.has_oidc?(user)
|
||||||
|
false
|
||||||
|
|
||||||
|
iex> user = %{oidc_id: "sub-from-rauthy"}
|
||||||
|
iex> MvWeb.Helpers.UserHelpers.has_oidc?(user)
|
||||||
|
true
|
||||||
|
"""
|
||||||
|
@spec has_oidc?(map() | struct()) :: boolean()
|
||||||
|
def has_oidc?(user) when is_map(user) do
|
||||||
|
case Map.get(user, :oidc_id) do
|
||||||
|
id when is_binary(id) and byte_size(id) > 0 -> true
|
||||||
|
_ -> false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -35,7 +35,7 @@ defmodule MvWeb.UserLive.Index do
|
||||||
users =
|
users =
|
||||||
Mv.Accounts.User
|
Mv.Accounts.User
|
||||||
|> Ash.Query.filter(email != ^Mv.Helpers.SystemActor.system_user_email())
|
|> Ash.Query.filter(email != ^Mv.Helpers.SystemActor.system_user_email())
|
||||||
|> Ash.read!(domain: Mv.Accounts, load: [:member], actor: actor)
|
|> Ash.read!(domain: Mv.Accounts, load: [:member, :role], actor: actor)
|
||||||
|
|
||||||
sorted = Enum.sort_by(users, & &1.email)
|
sorted = Enum.sort_by(users, & &1.email)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -56,11 +56,28 @@
|
||||||
>
|
>
|
||||||
{user.email}
|
{user.email}
|
||||||
</:col>
|
</:col>
|
||||||
|
<:col :let={user} label={gettext("Role")}>
|
||||||
|
{user.role.name}
|
||||||
|
</:col>
|
||||||
<:col :let={user} label={gettext("Linked Member")}>
|
<:col :let={user} label={gettext("Linked Member")}>
|
||||||
<%= if user.member do %>
|
<%= if user.member do %>
|
||||||
{MvWeb.Helpers.MemberHelpers.display_name(user.member)}
|
{MvWeb.Helpers.MemberHelpers.display_name(user.member)}
|
||||||
<% else %>
|
<% else %>
|
||||||
<span class="text-base-content/50">{gettext("No member linked")}</span>
|
<span class="text-base-content/70">{gettext("No member linked")}</span>
|
||||||
|
<% end %>
|
||||||
|
</:col>
|
||||||
|
<:col :let={user} label={gettext("Password")}>
|
||||||
|
<%= if MvWeb.Helpers.UserHelpers.has_password?(user) do %>
|
||||||
|
<span>{gettext("Enabled")}</span>
|
||||||
|
<% else %>
|
||||||
|
<span class="text-base-content/70">—</span>
|
||||||
|
<% end %>
|
||||||
|
</:col>
|
||||||
|
<:col :let={user} label={gettext("OIDC")}>
|
||||||
|
<%= if user.oidc_id do %>
|
||||||
|
<span>{gettext("Linked")}</span>
|
||||||
|
<% else %>
|
||||||
|
<span class="text-base-content/70">—</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</:col>
|
</:col>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -55,8 +55,14 @@ defmodule MvWeb.UserLive.Show do
|
||||||
|
|
||||||
<.list>
|
<.list>
|
||||||
<:item title={gettext("Email")}>{@user.email}</:item>
|
<:item title={gettext("Email")}>{@user.email}</:item>
|
||||||
|
<:item title={gettext("Role")}>{@user.role.name}</:item>
|
||||||
<:item title={gettext("Password Authentication")}>
|
<:item title={gettext("Password Authentication")}>
|
||||||
{if @user.hashed_password, do: gettext("Enabled"), else: gettext("Not enabled")}
|
{if MvWeb.Helpers.UserHelpers.has_password?(@user),
|
||||||
|
do: gettext("Enabled"),
|
||||||
|
else: gettext("Not enabled")}
|
||||||
|
</:item>
|
||||||
|
<:item title={gettext("OIDC")}>
|
||||||
|
{if @user.oidc_id, do: gettext("Linked"), else: gettext("Not linked")}
|
||||||
</:item>
|
</:item>
|
||||||
<:item title={gettext("Linked Member")}>
|
<:item title={gettext("Linked Member")}>
|
||||||
<%= if @user.member do %>
|
<%= if @user.member do %>
|
||||||
|
|
@ -79,7 +85,9 @@ defmodule MvWeb.UserLive.Show do
|
||||||
@impl true
|
@impl true
|
||||||
def mount(%{"id" => id}, _session, socket) do
|
def mount(%{"id" => id}, _session, socket) do
|
||||||
actor = current_actor(socket)
|
actor = current_actor(socket)
|
||||||
user = Ash.get!(Mv.Accounts.User, id, domain: Mv.Accounts, load: [:member], actor: actor)
|
|
||||||
|
user =
|
||||||
|
Ash.get!(Mv.Accounts.User, id, domain: Mv.Accounts, load: [:member, :role], actor: actor)
|
||||||
|
|
||||||
if Mv.Helpers.SystemActor.system_user?(user) do
|
if Mv.Helpers.SystemActor.system_user?(user) do
|
||||||
{:ok,
|
{:ok,
|
||||||
|
|
|
||||||
|
|
@ -294,6 +294,7 @@ msgstr "Beschreibung"
|
||||||
msgid "Edit User"
|
msgid "Edit User"
|
||||||
msgstr "Benutzer*in bearbeiten"
|
msgstr "Benutzer*in bearbeiten"
|
||||||
|
|
||||||
|
#: lib/mv_web/live/user_live/index.html.heex
|
||||||
#: lib/mv_web/live/user_live/show.ex
|
#: lib/mv_web/live/user_live/show.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Enabled"
|
msgid "Enabled"
|
||||||
|
|
@ -471,6 +472,7 @@ msgid "Include both letters and numbers"
|
||||||
msgstr "Buchstaben und Zahlen verwenden"
|
msgstr "Buchstaben und Zahlen verwenden"
|
||||||
|
|
||||||
#: lib/mv_web/live/user_live/form.ex
|
#: lib/mv_web/live/user_live/form.ex
|
||||||
|
#: lib/mv_web/live/user_live/index.html.heex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Password"
|
msgid "Password"
|
||||||
msgstr "Passwort"
|
msgstr "Passwort"
|
||||||
|
|
@ -1670,6 +1672,8 @@ msgstr "Profil"
|
||||||
#: lib/mv_web/live/role_live/form.ex
|
#: lib/mv_web/live/role_live/form.ex
|
||||||
#: lib/mv_web/live/role_live/show.ex
|
#: lib/mv_web/live/role_live/show.ex
|
||||||
#: lib/mv_web/live/user_live/form.ex
|
#: lib/mv_web/live/user_live/form.ex
|
||||||
|
#: lib/mv_web/live/user_live/index.html.heex
|
||||||
|
#: lib/mv_web/live/user_live/show.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Role"
|
msgid "Role"
|
||||||
msgstr "Rolle"
|
msgstr "Rolle"
|
||||||
|
|
@ -2312,3 +2316,30 @@ msgstr "Du hast keine Berechtigung, diese Aktion auszuführen."
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Select a membership fee type"
|
msgid "Select a membership fee type"
|
||||||
msgstr "Mitgliedsbeitragstyp auswählen"
|
msgstr "Mitgliedsbeitragstyp auswählen"
|
||||||
|
|
||||||
|
#: lib/mv_web/live/user_live/index.html.heex
|
||||||
|
#: lib/mv_web/live/user_live/show.ex
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "Linked"
|
||||||
|
msgstr "Verknüpft"
|
||||||
|
|
||||||
|
#: lib/mv_web/live/user_live/index.html.heex
|
||||||
|
#: lib/mv_web/live/user_live/show.ex
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "OIDC"
|
||||||
|
msgstr "OIDC"
|
||||||
|
|
||||||
|
#: lib/mv_web/live/user_live/show.ex
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "Not linked"
|
||||||
|
msgstr "Nicht verknüpft"
|
||||||
|
|
||||||
|
#: lib/mv_web/live/user_live/form.ex
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "SSO / OIDC user"
|
||||||
|
msgstr "SSO-/OIDC-Benutzer*in"
|
||||||
|
|
||||||
|
#: lib/mv_web/live/user_live/form.ex
|
||||||
|
#, elixir-autogen, elixir-format, fuzzy
|
||||||
|
msgid "This user is linked via SSO (Single Sign-On). A password set or changed here only affects login with email and password in this application. It does not change the password in your identity provider (e.g. Authentik). To change the SSO password, use the identity provider or your organization's IT."
|
||||||
|
msgstr "Dieser*e Benutzer*in ist per SSO (Single Sign-On) angebunden. Ein hier gesetztes oder geändertes Passwort betrifft nur die Anmeldung mit E-Mail und Passwort in dieser Anwendung. Es ändert nicht das Passwort beim Identity-Provider (z. B. Authentik). Zum Ändern des SSO-Passworts nutzen Sie den Identity-Provider oder die IT Ihrer Organisation."
|
||||||
|
|
|
||||||
|
|
@ -295,6 +295,7 @@ msgstr ""
|
||||||
msgid "Edit User"
|
msgid "Edit User"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/live/user_live/index.html.heex
|
||||||
#: lib/mv_web/live/user_live/show.ex
|
#: lib/mv_web/live/user_live/show.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Enabled"
|
msgid "Enabled"
|
||||||
|
|
@ -472,6 +473,7 @@ msgid "Include both letters and numbers"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/user_live/form.ex
|
#: lib/mv_web/live/user_live/form.ex
|
||||||
|
#: lib/mv_web/live/user_live/index.html.heex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Password"
|
msgid "Password"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
@ -1671,6 +1673,8 @@ msgstr ""
|
||||||
#: lib/mv_web/live/role_live/form.ex
|
#: lib/mv_web/live/role_live/form.ex
|
||||||
#: lib/mv_web/live/role_live/show.ex
|
#: lib/mv_web/live/role_live/show.ex
|
||||||
#: lib/mv_web/live/user_live/form.ex
|
#: lib/mv_web/live/user_live/form.ex
|
||||||
|
#: lib/mv_web/live/user_live/index.html.heex
|
||||||
|
#: lib/mv_web/live/user_live/show.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Role"
|
msgid "Role"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
@ -2313,3 +2317,30 @@ msgstr ""
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Select a membership fee type"
|
msgid "Select a membership fee type"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/live/user_live/index.html.heex
|
||||||
|
#: lib/mv_web/live/user_live/show.ex
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "Linked"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/live/user_live/index.html.heex
|
||||||
|
#: lib/mv_web/live/user_live/show.ex
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "OIDC"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/live/user_live/show.ex
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "Not linked"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/live/user_live/form.ex
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "SSO / OIDC user"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/live/user_live/form.ex
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "This user is linked via SSO (Single Sign-On). A password set or changed here only affects login with email and password in this application. It does not change the password in your identity provider (e.g. Authentik). To change the SSO password, use the identity provider or your organization's IT."
|
||||||
|
msgstr ""
|
||||||
|
|
|
||||||
|
|
@ -295,6 +295,7 @@ msgstr ""
|
||||||
msgid "Edit User"
|
msgid "Edit User"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/live/user_live/index.html.heex
|
||||||
#: lib/mv_web/live/user_live/show.ex
|
#: lib/mv_web/live/user_live/show.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Enabled"
|
msgid "Enabled"
|
||||||
|
|
@ -472,6 +473,7 @@ msgid "Include both letters and numbers"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/mv_web/live/user_live/form.ex
|
#: lib/mv_web/live/user_live/form.ex
|
||||||
|
#: lib/mv_web/live/user_live/index.html.heex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Password"
|
msgid "Password"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
@ -1671,6 +1673,8 @@ msgstr ""
|
||||||
#: lib/mv_web/live/role_live/form.ex
|
#: lib/mv_web/live/role_live/form.ex
|
||||||
#: lib/mv_web/live/role_live/show.ex
|
#: lib/mv_web/live/role_live/show.ex
|
||||||
#: lib/mv_web/live/user_live/form.ex
|
#: lib/mv_web/live/user_live/form.ex
|
||||||
|
#: lib/mv_web/live/user_live/index.html.heex
|
||||||
|
#: lib/mv_web/live/user_live/show.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Role"
|
msgid "Role"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
@ -2313,3 +2317,30 @@ msgstr ""
|
||||||
#, elixir-autogen, elixir-format, fuzzy
|
#, elixir-autogen, elixir-format, fuzzy
|
||||||
msgid "Select a membership fee type"
|
msgid "Select a membership fee type"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/live/user_live/index.html.heex
|
||||||
|
#: lib/mv_web/live/user_live/show.ex
|
||||||
|
#, elixir-autogen, elixir-format, fuzzy
|
||||||
|
msgid "Linked"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/live/user_live/index.html.heex
|
||||||
|
#: lib/mv_web/live/user_live/show.ex
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "OIDC"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/live/user_live/show.ex
|
||||||
|
#, elixir-autogen, elixir-format, fuzzy
|
||||||
|
msgid "Not linked"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/live/user_live/form.ex
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "SSO / OIDC user"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/mv_web/live/user_live/form.ex
|
||||||
|
#, elixir-autogen, elixir-format, fuzzy
|
||||||
|
msgid "This user is linked via SSO (Single Sign-On). A password set or changed here only affects login with email and password in this application. It does not change the password in your identity provider (e.g. Authentik). To change the SSO password, use the identity provider or your organization's IT."
|
||||||
|
msgstr ""
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue