diff --git a/lib/mv_web/live/global_settings_live.ex b/lib/mv_web/live/global_settings_live.ex index 1a7e13b..3da4aa6 100644 --- a/lib/mv_web/live/global_settings_live.ex +++ b/lib/mv_web/live/global_settings_live.ex @@ -45,12 +45,21 @@ defmodule MvWeb.GlobalSettingsLive do |> assign(:active_editing_section, nil) |> assign(:locale, locale) |> assign(:vereinfacht_env_configured, Mv.Config.vereinfacht_env_configured?()) + |> assign(:vereinfacht_api_url_env_set, Mv.Config.vereinfacht_api_url_env_set?()) + |> assign(:vereinfacht_api_key_env_set, Mv.Config.vereinfacht_api_key_env_set?()) + |> assign(:vereinfacht_club_id_env_set, Mv.Config.vereinfacht_club_id_env_set?()) + |> assign(:vereinfacht_api_key_set, present?(settings.vereinfacht_api_key)) |> assign(:last_vereinfacht_sync_result, nil) |> assign_form() {:ok, socket} end + defp present?(nil), do: false + defp present?(""), do: false + defp present?(s) when is_binary(s), do: String.trim(s) != "" + defp present?(_), do: false + @impl true def render(assigns) do ~H""" @@ -83,9 +92,7 @@ defmodule MvWeb.GlobalSettingsLive do <.form_section title={gettext("Vereinfacht Integration")}> <%= if @vereinfacht_env_configured do %>

- {gettext( - "Configured via environment variables (VEREINFACHT_API_URL, VEREINFACHT_API_KEY, VEREINFACHT_CLUB_ID). Fields below are read-only." - )} + {gettext("Some values are set via environment variables. Those fields are read-only.")}

<% end %> <.form for={@form} id="vereinfacht-form" phx-change="validate" phx-submit="save"> @@ -94,35 +101,53 @@ defmodule MvWeb.GlobalSettingsLive do field={@form[:vereinfacht_api_url]} type="text" label={gettext("API URL")} - disabled={@vereinfacht_env_configured} + disabled={@vereinfacht_api_url_env_set} placeholder={ - if(@vereinfacht_env_configured, + if(@vereinfacht_api_url_env_set, do: gettext("From VEREINFACHT_API_URL"), else: "https://api.verein.visuel.dev/api/v1" ) } /> - <.input - field={@form[:vereinfacht_api_key]} - type="password" - label={gettext("API Key")} - disabled={@vereinfacht_env_configured} - placeholder={ - if(@vereinfacht_env_configured, do: gettext("From VEREINFACHT_API_KEY"), else: nil) - } - /> +
+ + <.input + field={@form[:vereinfacht_api_key]} + type="password" + label="" + disabled={@vereinfacht_api_key_env_set} + placeholder={ + if(@vereinfacht_api_key_env_set, + do: gettext("From VEREINFACHT_API_KEY"), + else: + if(@vereinfacht_api_key_set, + do: gettext("Leave blank to keep current"), + else: nil + ) + ) + } + /> +
<.input field={@form[:vereinfacht_club_id]} type="text" label={gettext("Club ID")} - disabled={@vereinfacht_env_configured} + disabled={@vereinfacht_club_id_env_set} placeholder={ - if(@vereinfacht_env_configured, do: gettext("From VEREINFACHT_CLUB_ID"), else: "2") + if(@vereinfacht_club_id_env_set, do: gettext("From VEREINFACHT_CLUB_ID"), else: "2") } /> <.button - :if={not @vereinfacht_env_configured} + :if={ + not (@vereinfacht_api_url_env_set and @vereinfacht_api_key_env_set and + @vereinfacht_club_id_env_set) + } phx-disable-with={gettext("Saving...")} variant="primary" class="mt-2" @@ -206,15 +231,17 @@ defmodule MvWeb.GlobalSettingsLive do @impl true def handle_event("save", %{"setting" => setting_params}, socket) do actor = MvWeb.LiveHelpers.current_actor(socket) + # Never send blank API key so we do not overwrite the stored secret (security) + setting_params_clean = drop_blank_vereinfacht_api_key(setting_params) - case MvWeb.LiveHelpers.submit_form(socket.assigns.form, setting_params, actor) do + case MvWeb.LiveHelpers.submit_form(socket.assigns.form, setting_params_clean, actor) do {:ok, _updated_settings} -> - # Reload settings from database to ensure all dependent data is updated {:ok, fresh_settings} = Membership.get_settings() socket = socket |> assign(:settings, fresh_settings) + |> assign(:vereinfacht_api_key_set, present?(fresh_settings.vereinfacht_api_key)) |> put_flash(:info, gettext("Settings updated successfully")) |> assign_form() @@ -225,6 +252,16 @@ defmodule MvWeb.GlobalSettingsLive do end end + defp drop_blank_vereinfacht_api_key(params) when is_map(params) do + case params do + %{"vereinfacht_api_key" => v} when v in [nil, ""] -> + Map.delete(params, "vereinfacht_api_key") + + _ -> + params + end + end + @impl true def handle_info({:custom_field_saved, _custom_field, action}, socket) do send_update(MvWeb.CustomFieldLive.IndexComponent, @@ -305,9 +342,12 @@ defmodule MvWeb.GlobalSettingsLive do end defp assign_form(%{assigns: %{settings: settings}} = socket) do + # Never put API key into form/DOM to avoid secret leak in source or DevTools + settings_for_form = %{settings | vereinfacht_api_key: nil} + form = AshPhoenix.Form.for_update( - settings, + settings_for_form, :update, api: Membership, as: "setting",