diff --git a/lib/membership/setting.ex b/lib/membership/setting.ex
index 154288b..6e987de 100644
--- a/lib/membership/setting.ex
+++ b/lib/membership/setting.ex
@@ -79,7 +79,13 @@ defmodule Mv.Membership.Setting do
:vereinfacht_api_url,
:vereinfacht_api_key,
:vereinfacht_club_id,
- :vereinfacht_app_url
+ :vereinfacht_app_url,
+ :oidc_client_id,
+ :oidc_base_url,
+ :oidc_redirect_uri,
+ :oidc_client_secret,
+ :oidc_admin_group_name,
+ :oidc_groups_claim
]
end
@@ -96,7 +102,13 @@ defmodule Mv.Membership.Setting do
:vereinfacht_api_url,
:vereinfacht_api_key,
:vereinfacht_club_id,
- :vereinfacht_app_url
+ :vereinfacht_app_url,
+ :oidc_client_id,
+ :oidc_base_url,
+ :oidc_redirect_uri,
+ :oidc_client_secret,
+ :oidc_admin_group_name,
+ :oidc_groups_claim
]
end
@@ -322,6 +334,44 @@ defmodule Mv.Membership.Setting do
description "Vereinfacht app base URL for contact view links (e.g. https://app.verein.visuel.dev)"
end
+ # OIDC authentication (can be overridden by ENV)
+ attribute :oidc_client_id, :string do
+ allow_nil? true
+ public? true
+ description "OIDC client ID (e.g. from OIDC_CLIENT_ID)"
+ end
+
+ attribute :oidc_base_url, :string do
+ allow_nil? true
+ public? true
+ description "OIDC provider base URL (e.g. from OIDC_BASE_URL)"
+ end
+
+ attribute :oidc_redirect_uri, :string do
+ allow_nil? true
+ public? true
+ description "OIDC redirect URI for callback (e.g. from OIDC_REDIRECT_URI)"
+ end
+
+ attribute :oidc_client_secret, :string do
+ allow_nil? true
+ public? false
+ description "OIDC client secret (e.g. from OIDC_CLIENT_SECRET)"
+ sensitive? true
+ end
+
+ attribute :oidc_admin_group_name, :string do
+ allow_nil? true
+ public? true
+ description "OIDC group name that maps to Admin role (e.g. from OIDC_ADMIN_GROUP_NAME)"
+ end
+
+ attribute :oidc_groups_claim, :string do
+ allow_nil? true
+ public? true
+ description "JWT claim name for group list (e.g. from OIDC_GROUPS_CLAIM, default 'groups')"
+ end
+
timestamps()
end
diff --git a/lib/mv/config.ex b/lib/mv/config.ex
index d2ad66c..f70a07e 100644
--- a/lib/mv/config.ex
+++ b/lib/mv/config.ex
@@ -263,6 +263,10 @@ defmodule Mv.Config do
end
defp get_vereinfacht_from_settings(key) do
+ get_from_settings(key)
+ end
+
+ defp get_from_settings(key) do
case Mv.Membership.get_settings() do
{:ok, settings} -> settings |> Map.get(key) |> trim_nil()
{:error, _} -> nil
@@ -298,4 +302,77 @@ defmodule Mv.Config do
defp present?(nil), do: false
defp present?(s) when is_binary(s), do: String.trim(s) != ""
defp present?(_), do: false
+
+ # ---------------------------------------------------------------------------
+ # OIDC authentication
+ # ENV variables take priority; fallback to Settings from database.
+ # ---------------------------------------------------------------------------
+
+ @doc """
+ Returns the OIDC client ID. ENV first, then Settings.
+ """
+ @spec oidc_client_id() :: String.t() | nil
+ def oidc_client_id do
+ env_or_setting("OIDC_CLIENT_ID", :oidc_client_id)
+ end
+
+ @doc """
+ Returns the OIDC provider base URL. ENV first, then Settings.
+ """
+ @spec oidc_base_url() :: String.t() | nil
+ def oidc_base_url do
+ env_or_setting("OIDC_BASE_URL", :oidc_base_url)
+ end
+
+ @doc """
+ Returns the OIDC redirect URI. ENV first, then Settings.
+ """
+ @spec oidc_redirect_uri() :: String.t() | nil
+ def oidc_redirect_uri do
+ env_or_setting("OIDC_REDIRECT_URI", :oidc_redirect_uri)
+ end
+
+ @doc """
+ Returns the OIDC client secret. ENV first, then Settings.
+ """
+ @spec oidc_client_secret() :: String.t() | nil
+ def oidc_client_secret do
+ env_or_setting("OIDC_CLIENT_SECRET", :oidc_client_secret)
+ end
+
+ @doc """
+ Returns the OIDC admin group name (for role sync). ENV first, then Settings.
+ """
+ @spec oidc_admin_group_name() :: String.t() | nil
+ def oidc_admin_group_name do
+ env_or_setting("OIDC_ADMIN_GROUP_NAME", :oidc_admin_group_name)
+ end
+
+ @doc """
+ Returns the OIDC groups claim name (default "groups"). ENV first, then Settings.
+ """
+ @spec oidc_groups_claim() :: String.t() | nil
+ def oidc_groups_claim do
+ case env_or_setting("OIDC_GROUPS_CLAIM", :oidc_groups_claim) do
+ nil -> "groups"
+ v -> v
+ end
+ end
+
+ @doc """
+ Returns true if any OIDC ENV variable is set (used to show hint in Settings UI).
+ """
+ @spec oidc_env_configured?() :: boolean()
+ def oidc_env_configured? do
+ oidc_client_id_env_set?() or oidc_base_url_env_set?() or
+ oidc_redirect_uri_env_set?() or oidc_client_secret_env_set?() or
+ oidc_admin_group_name_env_set?() or oidc_groups_claim_env_set?()
+ end
+
+ def oidc_client_id_env_set?, do: env_set?("OIDC_CLIENT_ID")
+ def oidc_base_url_env_set?, do: env_set?("OIDC_BASE_URL")
+ def oidc_redirect_uri_env_set?, do: env_set?("OIDC_REDIRECT_URI")
+ def oidc_client_secret_env_set?, do: env_set?("OIDC_CLIENT_SECRET")
+ def oidc_admin_group_name_env_set?, do: env_set?("OIDC_ADMIN_GROUP_NAME")
+ def oidc_groups_claim_env_set?, do: env_set?("OIDC_GROUPS_CLAIM")
end
diff --git a/lib/mv/oidc_role_sync_config.ex b/lib/mv/oidc_role_sync_config.ex
index 493a435..2a8574c 100644
--- a/lib/mv/oidc_role_sync_config.ex
+++ b/lib/mv/oidc_role_sync_config.ex
@@ -2,23 +2,19 @@ defmodule Mv.OidcRoleSyncConfig do
@moduledoc """
Runtime configuration for OIDC group → role sync (e.g. admin group → Admin role).
- Reads from Application config `:mv, :oidc_role_sync`:
- - `:admin_group_name` – OIDC group name that maps to Admin role (optional; when nil, no sync).
- - `:groups_claim` – JWT/user_info claim name for groups (default: `"groups"`).
+ Reads from Mv.Config (ENV first, then Settings):
+ - `oidc_admin_group_name/0` – OIDC group name that maps to Admin role (optional; when nil, no sync).
+ - `oidc_groups_claim/0` – JWT/user_info claim name for groups (default: `"groups"`).
- Set via ENV in production: OIDC_ADMIN_GROUP_NAME, OIDC_GROUPS_CLAIM (see config/runtime.exs).
+ Set via ENV: OIDC_ADMIN_GROUP_NAME, OIDC_GROUPS_CLAIM; or via Settings (Basic settings → OIDC).
"""
@doc "Returns the OIDC group name that maps to Admin role, or nil if not configured."
def oidc_admin_group_name do
- get(:admin_group_name)
+ Mv.Config.oidc_admin_group_name()
end
@doc "Returns the JWT/user_info claim name for groups; defaults to \"groups\"."
def oidc_groups_claim do
- get(:groups_claim) || "groups"
- end
-
- defp get(key) do
- Application.get_env(:mv, :oidc_role_sync, []) |> Keyword.get(key)
+ Mv.Config.oidc_groups_claim() || "groups"
end
end
diff --git a/lib/mv/secrets.ex b/lib/mv/secrets.ex
index d3bc30d..f315ea3 100644
--- a/lib/mv/secrets.ex
+++ b/lib/mv/secrets.ex
@@ -7,12 +7,12 @@ defmodule Mv.Secrets do
particularly for OIDC (Rauthy) authentication.
## Configuration Source
- Secrets are read from the `:oidc` key in the application configuration,
- which is typically set in `config/runtime.exs` from environment variables:
- - `OIDC_CLIENT_ID`
- - `OIDC_CLIENT_SECRET`
- - `OIDC_BASE_URL`
- - `OIDC_REDIRECT_URI`
+ Secrets are read via `Mv.Config` which prefers environment variables and
+ falls back to Settings from the database:
+ - OIDC_CLIENT_ID / settings.oidc_client_id
+ - OIDC_CLIENT_SECRET / settings.oidc_client_secret
+ - OIDC_BASE_URL / settings.oidc_base_url
+ - OIDC_REDIRECT_URI / settings.oidc_redirect_uri
## Usage
This module is automatically called by AshAuthentication when resolving
@@ -26,7 +26,7 @@ defmodule Mv.Secrets do
_opts,
_meth
) do
- get_config(:client_id)
+ {:ok, Mv.Config.oidc_client_id()}
end
def secret_for(
@@ -35,7 +35,7 @@ defmodule Mv.Secrets do
_opts,
_meth
) do
- get_config(:redirect_uri)
+ {:ok, Mv.Config.oidc_redirect_uri()}
end
def secret_for(
@@ -44,7 +44,7 @@ defmodule Mv.Secrets do
_opts,
_meth
) do
- get_config(:client_secret)
+ {:ok, Mv.Config.oidc_client_secret()}
end
def secret_for(
@@ -53,13 +53,6 @@ defmodule Mv.Secrets do
_opts,
_meth
) do
- get_config(:base_url)
- end
-
- defp get_config(key) do
- :mv
- |> Application.fetch_env!(:oidc)
- |> Keyword.fetch!(key)
- |> then(&{:ok, &1})
+ {:ok, Mv.Config.oidc_base_url()}
end
end
diff --git a/lib/mv_web/live/global_settings_live.ex b/lib/mv_web/live/global_settings_live.ex
index c710f79..b67b6ac 100644
--- a/lib/mv_web/live/global_settings_live.ex
+++ b/lib/mv_web/live/global_settings_live.ex
@@ -42,7 +42,6 @@ defmodule MvWeb.GlobalSettingsLive do
socket
|> assign(:page_title, gettext("Settings"))
|> assign(:settings, settings)
- |> 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?())
@@ -52,6 +51,14 @@ defmodule MvWeb.GlobalSettingsLive do
|> assign(:vereinfacht_api_key_set, present?(settings.vereinfacht_api_key))
|> assign(:last_vereinfacht_sync_result, nil)
|> assign(:vereinfacht_test_result, nil)
+ |> assign(:oidc_env_configured, Mv.Config.oidc_env_configured?())
+ |> assign(:oidc_client_id_env_set, Mv.Config.oidc_client_id_env_set?())
+ |> assign(:oidc_base_url_env_set, Mv.Config.oidc_base_url_env_set?())
+ |> assign(:oidc_redirect_uri_env_set, Mv.Config.oidc_redirect_uri_env_set?())
+ |> assign(:oidc_client_secret_env_set, Mv.Config.oidc_client_secret_env_set?())
+ |> assign(:oidc_admin_group_name_env_set, Mv.Config.oidc_admin_group_name_env_set?())
+ |> assign(:oidc_groups_claim_env_set, Mv.Config.oidc_groups_claim_env_set?())
+ |> assign(:oidc_client_secret_set, present?(settings.oidc_client_secret))
|> assign_form()
{:ok, socket}
@@ -196,21 +203,110 @@ defmodule MvWeb.GlobalSettingsLive do
<% end %>
- <%!-- Memberdata Section --%>
- <.form_section title={gettext("Memberdata")}>
- <.live_component
- :if={@active_editing_section != :custom_fields}
- module={MvWeb.MemberFieldLive.IndexComponent}
- id="member-fields-component"
- settings={@settings}
- />
- <%!-- Custom Fields Section --%>
- <.live_component
- :if={@active_editing_section != :member_fields}
- module={MvWeb.CustomFieldLive.IndexComponent}
- id="custom-fields-component"
- actor={@current_user}
- />
+ <%!-- OIDC Section --%>
+ <.form_section title={gettext("OIDC")}>
+ <%= if @oidc_env_configured do %>
+
+ {gettext("Some values are set via environment variables. Those fields are read-only.")}
+
+ <% end %>
+ <.form for={@form} id="oidc-form" phx-change="validate" phx-submit="save">
+
+ <.input
+ field={@form[:oidc_client_id]}
+ type="text"
+ label={gettext("Client ID")}
+ disabled={@oidc_client_id_env_set}
+ placeholder={
+ if(@oidc_client_id_env_set, do: gettext("From OIDC_CLIENT_ID"), else: "mv")
+ }
+ />
+ <.input
+ field={@form[:oidc_base_url]}
+ type="text"
+ label={gettext("Base URL")}
+ disabled={@oidc_base_url_env_set}
+ placeholder={
+ if(@oidc_base_url_env_set,
+ do: gettext("From OIDC_BASE_URL"),
+ else: "http://localhost:8080/auth/v1"
+ )
+ }
+ />
+ <.input
+ field={@form[:oidc_redirect_uri]}
+ type="text"
+ label={gettext("Redirect URI")}
+ disabled={@oidc_redirect_uri_env_set}
+ placeholder={
+ if(@oidc_redirect_uri_env_set,
+ do: gettext("From OIDC_REDIRECT_URI"),
+ else: "http://localhost:4000/auth/user/oidc/callback"
+ )
+ }
+ />
+
+
+ <.input
+ field={@form[:oidc_client_secret]}
+ type="password"
+ label=""
+ disabled={@oidc_client_secret_env_set}
+ placeholder={
+ if(@oidc_client_secret_env_set,
+ do: gettext("From OIDC_CLIENT_SECRET"),
+ else:
+ if(@oidc_client_secret_set,
+ do: gettext("Leave blank to keep current"),
+ else: nil
+ )
+ )
+ }
+ />
+
+ <.input
+ field={@form[:oidc_admin_group_name]}
+ type="text"
+ label={gettext("Admin group name")}
+ disabled={@oidc_admin_group_name_env_set}
+ placeholder={
+ if(@oidc_admin_group_name_env_set,
+ do: gettext("From OIDC_ADMIN_GROUP_NAME"),
+ else: gettext("e.g. admin")
+ )
+ }
+ />
+ <.input
+ field={@form[:oidc_groups_claim]}
+ type="text"
+ label={gettext("Groups claim")}
+ disabled={@oidc_groups_claim_env_set}
+ placeholder={
+ if(@oidc_groups_claim_env_set,
+ do: gettext("From OIDC_GROUPS_CLAIM"),
+ else: "groups"
+ )
+ }
+ />
+
+ <.button
+ :if={
+ not (@oidc_client_id_env_set and @oidc_base_url_env_set and
+ @oidc_redirect_uri_env_set and @oidc_client_secret_env_set and
+ @oidc_admin_group_name_env_set and @oidc_groups_claim_env_set)
+ }
+ phx-disable-with={gettext("Saving...")}
+ variant="primary"
+ class="mt-2"
+ >
+ {gettext("Save OIDC Settings")}
+
+
"""
@@ -265,8 +361,12 @@ 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)
+ # Never send blank API key / client secret so we do not overwrite stored secrets
+ setting_params_clean =
+ setting_params
+ |> drop_blank_vereinfacht_api_key()
+ |> drop_blank_oidc_client_secret()
+
saves_vereinfacht = vereinfacht_params?(setting_params_clean)
case MvWeb.LiveHelpers.submit_form(socket.assigns.form, setting_params_clean, actor) do
@@ -280,6 +380,7 @@ defmodule MvWeb.GlobalSettingsLive do
socket
|> assign(:settings, fresh_settings)
|> assign(:vereinfacht_api_key_set, present?(fresh_settings.vereinfacht_api_key))
+ |> assign(:oidc_client_secret_set, present?(fresh_settings.oidc_client_secret))
|> assign(:vereinfacht_test_result, test_result)
|> put_flash(:info, gettext("Settings updated successfully"))
|> assign_form()
@@ -307,88 +408,19 @@ defmodule MvWeb.GlobalSettingsLive do
end
end
- @impl true
- def handle_info({:custom_field_saved, _custom_field, action}, socket) do
- send_update(MvWeb.CustomFieldLive.IndexComponent,
- id: "custom-fields-component",
- show_form: false
- )
+ defp drop_blank_oidc_client_secret(params) when is_map(params) do
+ case params do
+ %{"oidc_client_secret" => v} when v in [nil, ""] ->
+ Map.delete(params, "oidc_client_secret")
- {:noreply,
- socket
- |> assign(:active_editing_section, nil)
- |> put_flash(:info, gettext("Data field %{action} successfully", action: action))}
- end
-
- @impl true
- def handle_info({:custom_field_deleted, _custom_field}, socket) do
- {:noreply, put_flash(socket, :info, gettext("Data field deleted successfully"))}
- end
-
- @impl true
- def handle_info({:custom_field_delete_error, error}, socket) do
- {:noreply,
- put_flash(
- socket,
- :error,
- gettext("Failed to delete data field: %{error}", error: inspect(error))
- )}
- end
-
- @impl true
- def handle_info(:custom_field_slug_mismatch, socket) do
- {:noreply, put_flash(socket, :error, gettext("Slug does not match. Deletion cancelled."))}
- end
-
- def handle_info({:custom_fields_load_error, _error}, socket) do
- {:noreply,
- put_flash(
- socket,
- :error,
- gettext("Could not load data fields. Please check your permissions.")
- )}
- end
-
- @impl true
- def handle_info({:editing_section_changed, section}, socket) do
- {:noreply, assign(socket, :active_editing_section, section)}
- end
-
- @impl true
- def handle_info({:member_field_saved, _member_field, action}, socket) do
- # Reload settings to get updated member_field_visibility
- {:ok, updated_settings} = Membership.get_settings()
-
- # Send update to member fields component to close form
- send_update(MvWeb.MemberFieldLive.IndexComponent,
- id: "member-fields-component",
- show_form: false,
- settings: updated_settings
- )
-
- {:noreply,
- socket
- |> assign(:settings, updated_settings)
- |> assign(:active_editing_section, nil)
- |> put_flash(:info, gettext("Member field %{action} successfully", action: action))}
- end
-
- @impl true
- def handle_info({:member_field_visibility_updated}, socket) do
- # Legacy event - reload settings and update component
- {:ok, updated_settings} = Membership.get_settings()
-
- send_update(MvWeb.MemberFieldLive.IndexComponent,
- id: "member-fields-component",
- settings: updated_settings
- )
-
- {:noreply, assign(socket, :settings, updated_settings)}
+ _ ->
+ params
+ end
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}
+ # Never put API key / client secret into form/DOM to avoid secret leak
+ settings_for_form = %{settings | vereinfacht_api_key: nil, oidc_client_secret: nil}
form =
AshPhoenix.Form.for_update(
diff --git a/priv/repo/migrations/20260224122831_add_oidc_to_settings.exs b/priv/repo/migrations/20260224122831_add_oidc_to_settings.exs
new file mode 100644
index 0000000..154709a
--- /dev/null
+++ b/priv/repo/migrations/20260224122831_add_oidc_to_settings.exs
@@ -0,0 +1,29 @@
+defmodule Mv.Repo.Migrations.AddOidcToSettings do
+ @moduledoc """
+ Adds OIDC configuration columns to settings (ENV-overridable in UI).
+ """
+
+ use Ecto.Migration
+
+ def up do
+ alter table(:settings) do
+ add :oidc_client_id, :string
+ add :oidc_base_url, :string
+ add :oidc_redirect_uri, :string
+ add :oidc_client_secret, :string
+ add :oidc_admin_group_name, :string
+ add :oidc_groups_claim, :string
+ end
+ end
+
+ def down do
+ alter table(:settings) do
+ remove :oidc_client_id
+ remove :oidc_base_url
+ remove :oidc_redirect_uri
+ remove :oidc_client_secret
+ remove :oidc_admin_group_name
+ remove :oidc_groups_claim
+ end
+ end
+end
diff --git a/priv/resource_snapshots/repo/settings/20260224122831.json b/priv/resource_snapshots/repo/settings/20260224122831.json
new file mode 100644
index 0000000..b1ad095
--- /dev/null
+++ b/priv/resource_snapshots/repo/settings/20260224122831.json
@@ -0,0 +1,164 @@
+{
+ "attributes": [
+ {
+ "allow_nil?": false,
+ "default": "fragment(\"gen_random_uuid()\")",
+ "generated?": false,
+ "precision": null,
+ "primary_key?": true,
+ "references": null,
+ "scale": null,
+ "size": null,
+ "source": "id",
+ "type": "uuid"
+ },
+ {
+ "allow_nil?": false,
+ "default": "nil",
+ "generated?": false,
+ "precision": null,
+ "primary_key?": false,
+ "references": null,
+ "scale": null,
+ "size": null,
+ "source": "club_name",
+ "type": "text"
+ },
+ {
+ "allow_nil?": true,
+ "default": "nil",
+ "generated?": false,
+ "precision": null,
+ "primary_key?": false,
+ "references": null,
+ "scale": null,
+ "size": null,
+ "source": "member_field_visibility",
+ "type": "map"
+ },
+ {
+ "allow_nil?": true,
+ "default": "nil",
+ "generated?": false,
+ "precision": null,
+ "primary_key?": false,
+ "references": null,
+ "scale": null,
+ "size": null,
+ "source": "member_field_required",
+ "type": "map"
+ },
+ {
+ "allow_nil?": false,
+ "default": "true",
+ "generated?": false,
+ "precision": null,
+ "primary_key?": false,
+ "references": null,
+ "scale": null,
+ "size": null,
+ "source": "include_joining_cycle",
+ "type": "boolean"
+ },
+ {
+ "allow_nil?": true,
+ "default": "nil",
+ "generated?": false,
+ "precision": null,
+ "primary_key?": false,
+ "references": null,
+ "scale": null,
+ "size": null,
+ "source": "default_membership_fee_type_id",
+ "type": "uuid"
+ },
+ {
+ "allow_nil?": true,
+ "default": "nil",
+ "generated?": false,
+ "precision": null,
+ "primary_key?": false,
+ "references": null,
+ "scale": null,
+ "size": null,
+ "source": "vereinfacht_api_url",
+ "type": "text"
+ },
+ {
+ "allow_nil?": true,
+ "default": "nil",
+ "generated?": false,
+ "precision": null,
+ "primary_key?": false,
+ "references": null,
+ "scale": null,
+ "size": null,
+ "source": "vereinfacht_api_key",
+ "type": "text"
+ },
+ {
+ "allow_nil?": true,
+ "default": "nil",
+ "generated?": false,
+ "precision": null,
+ "primary_key?": false,
+ "references": null,
+ "scale": null,
+ "size": null,
+ "source": "vereinfacht_club_id",
+ "type": "text"
+ },
+ {
+ "allow_nil?": true,
+ "default": "nil",
+ "generated?": false,
+ "precision": null,
+ "primary_key?": false,
+ "references": null,
+ "scale": null,
+ "size": null,
+ "source": "vereinfacht_app_url",
+ "type": "text"
+ },
+ {
+ "allow_nil?": false,
+ "default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
+ "generated?": false,
+ "precision": null,
+ "primary_key?": false,
+ "references": null,
+ "scale": null,
+ "size": null,
+ "source": "inserted_at",
+ "type": "utc_datetime_usec"
+ },
+ {
+ "allow_nil?": false,
+ "default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
+ "generated?": false,
+ "precision": null,
+ "primary_key?": false,
+ "references": null,
+ "scale": null,
+ "size": null,
+ "source": "updated_at",
+ "type": "utc_datetime_usec"
+ }
+ ],
+ "base_filter": null,
+ "check_constraints": [],
+ "create_table_options": null,
+ "custom_indexes": [],
+ "custom_statements": [],
+ "has_create_action": true,
+ "hash": "C84FC81A2A446451D6B5EA72F9BBB3593CD7F0D71C4B7C9CE04934414FDB52EB",
+ "identities": [],
+ "multitenancy": {
+ "attribute": null,
+ "global": null,
+ "strategy": null
+ },
+ "repo": "Elixir.Mv.Repo",
+ "schema": null,
+ "table": "settings"
+}
\ No newline at end of file
diff --git a/test/mv/oidc_role_sync_config_test.exs b/test/mv/oidc_role_sync_config_test.exs
index b4664aa..4b77378 100644
--- a/test/mv/oidc_role_sync_config_test.exs
+++ b/test/mv/oidc_role_sync_config_test.exs
@@ -1,21 +1,22 @@
defmodule Mv.OidcRoleSyncConfigTest do
@moduledoc """
Tests for OIDC role sync configuration (OIDC_ADMIN_GROUP_NAME, OIDC_GROUPS_CLAIM).
+ Reads via Mv.Config (ENV first, then Settings).
"""
- use ExUnit.Case, async: false
+ use Mv.DataCase, async: false
alias Mv.OidcRoleSyncConfig
describe "oidc_admin_group_name/0" do
test "returns nil when OIDC_ADMIN_GROUP_NAME is not configured" do
- restore = put_config(admin_group_name: nil)
+ restore = clear_env("OIDC_ADMIN_GROUP_NAME")
on_exit(restore)
assert OidcRoleSyncConfig.oidc_admin_group_name() == nil
end
- test "returns configured admin group name when set" do
- restore = put_config(admin_group_name: "mila-admin")
+ test "returns configured admin group name when set via ENV" do
+ restore = set_env("OIDC_ADMIN_GROUP_NAME", "mila-admin")
on_exit(restore)
assert OidcRoleSyncConfig.oidc_admin_group_name() == "mila-admin"
@@ -24,26 +25,35 @@ defmodule Mv.OidcRoleSyncConfigTest do
describe "oidc_groups_claim/0" do
test "returns default \"groups\" when OIDC_GROUPS_CLAIM is not configured" do
- restore = put_config(groups_claim: nil)
+ restore = clear_env("OIDC_GROUPS_CLAIM")
on_exit(restore)
assert OidcRoleSyncConfig.oidc_groups_claim() == "groups"
end
- test "returns configured claim name when OIDC_GROUPS_CLAIM is set" do
- restore = put_config(groups_claim: "ak_groups")
+ test "returns configured claim name when OIDC_GROUPS_CLAIM is set via ENV" do
+ restore = set_env("OIDC_GROUPS_CLAIM", "ak_groups")
on_exit(restore)
assert OidcRoleSyncConfig.oidc_groups_claim() == "ak_groups"
end
end
- defp put_config(opts) do
- current = Application.get_env(:mv, :oidc_role_sync, [])
- Application.put_env(:mv, :oidc_role_sync, Keyword.merge(current, opts))
+ defp set_env(key, value) do
+ previous = System.get_env(key)
+ System.put_env(key, value)
fn ->
- Application.put_env(:mv, :oidc_role_sync, current)
+ if previous, do: System.put_env(key, previous), else: System.delete_env(key)
+ end
+ end
+
+ defp clear_env(key) do
+ previous = System.get_env(key)
+ System.delete_env(key)
+
+ fn ->
+ if previous, do: System.put_env(key, previous)
end
end
end