From 09a4b7c937eb9a88b786dc31042851e26773a689 Mon Sep 17 00:00:00 2001 From: Moritz Date: Wed, 4 Feb 2026 15:17:49 +0100 Subject: [PATCH] Seeds: use ADMIN_PASSWORD/ADMIN_PASSWORD_FILE; fallback only in dev/test No fallback in production; prod uses Release.seed_admin in entrypoint. --- priv/repo/seeds.exs | 73 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 55 insertions(+), 18 deletions(-) diff --git a/priv/repo/seeds.exs b/priv/repo/seeds.exs index e97e7c2..705217e 100644 --- a/priv/repo/seeds.exs +++ b/priv/repo/seeds.exs @@ -135,6 +135,23 @@ end # Get admin email from environment variable or use default admin_email = System.get_env("ADMIN_EMAIL") || "admin@localhost" +# Admin password: use ADMIN_PASSWORD or ADMIN_PASSWORD_FILE if set; otherwise fallback +# only in dev/test (no fallback in production - prod uses Release.seed_admin in entrypoint) +get_admin_password = fn -> + from_file = + System.get_env("ADMIN_PASSWORD_FILE") |> then(fn path -> path && File.read(path) end) + + from_env = System.get_env("ADMIN_PASSWORD") + + case {from_file, from_env} do + {{:ok, content}, _} -> String.trim_trailing(content) + {_, p} when is_binary(p) and p != "" -> p + _ -> if Mix.env() in [:dev, :test], do: "testpassword", else: nil + end +end + +admin_password = get_admin_password.() + # Create all authorization roles (idempotent - creates only if they don't exist) # Roles are created using create_role_with_system_flag to allow setting is_system_role role_configs = [ @@ -215,34 +232,50 @@ if is_nil(admin_role) do end # Assign admin role to user with ADMIN_EMAIL (if user exists) -# This handles both existing users (e.g., from OIDC) and newly created users +# This handles both existing users (e.g., from OIDC) and newly created users. +# Password: use admin_password (from ENV or dev/test fallback); if nil, do not set password (prod-safe). case Accounts.User |> Ash.Query.filter(email == ^admin_email) |> Ash.read_one(domain: Mv.Accounts, authorize?: false) do {:ok, existing_admin_user} when not is_nil(existing_admin_user) -> - # User already exists (e.g., via OIDC) - assign admin role - # Use authorize?: false for bootstrap - this is initial setup - existing_admin_user + # User already exists (e.g., via OIDC) - set password if we have one, then assign admin role + user_after_password = + if is_binary(admin_password) and admin_password != "" do + existing_admin_user + |> Ash.Changeset.for_update(:admin_set_password, %{password: admin_password}) + |> Ash.update!(authorize?: false) + else + existing_admin_user + end + + user_after_password |> Ash.Changeset.for_update(:update, %{}) |> Ash.Changeset.manage_relationship(:role, admin_role, type: :append_and_remove) |> Ash.update!(authorize?: false) {:ok, nil} -> - # User doesn't exist - create admin user and set password (so Password column shows "Enabled") + # User doesn't exist - create admin user; set password only if we have one (no fallback in prod) # Use authorize?: false for bootstrap - no admin user exists yet to use as actor - Accounts.create_user!(%{email: admin_email}, - upsert?: true, - upsert_identity: :unique_email, - authorize?: false - ) - |> Ash.Changeset.for_update(:admin_set_password, %{password: "testpassword"}) + user = + Accounts.create_user!(%{email: admin_email}, + upsert?: true, + upsert_identity: :unique_email, + authorize?: false + ) + + user = + if is_binary(admin_password) and admin_password != "" do + user + |> Ash.Changeset.for_update(:admin_set_password, %{password: admin_password}) + |> Ash.update!(authorize?: false) + else + user + end + + user + |> Ash.Changeset.for_update(:update, %{}) + |> Ash.Changeset.manage_relationship(:role, admin_role, type: :append_and_remove) |> Ash.update!(authorize?: false) - |> then(fn user -> - user - |> Ash.Changeset.for_update(:update, %{}) - |> Ash.Changeset.manage_relationship(:role, admin_role, type: :append_and_remove) - |> Ash.update!(authorize?: false) - end) {:error, error} -> raise "Failed to check for existing admin user: #{inspect(error)}" @@ -747,7 +780,11 @@ IO.puts("📝 Created sample data:") IO.puts(" - Global settings: club_name = #{default_club_name}") IO.puts(" - Membership fee types: 4 types (Yearly, Half-yearly, Quarterly, Monthly)") IO.puts(" - Custom fields: 12 fields (String, Date, Boolean, Email, + 8 realistic fields)") -IO.puts(" - Admin user: #{admin_email} (password: testpassword)") + +IO.puts( + " - Admin user: #{admin_email} (password: #{if admin_password, do: "set", else: "not set"})" +) + IO.puts(" - Sample members: Hans, Greta, Friedrich") IO.puts(