From e27bfcb8510b2a56f652be360dec8d4bb0b1e1eb Mon Sep 17 00:00:00 2001 From: Moritz Date: Fri, 9 Jan 2026 05:26:02 +0100 Subject: [PATCH] Pass actor parameter through email sync operations Extract actor from changeset context and pass it to all email sync loader functions to ensure proper authorization when loading linked users and members. --- .../changes/sync_member_email_to_user.ex | 4 ++- .../changes/sync_user_email_to_member.ex | 26 +++++++++++++---- lib/mv/email_sync/loader.ex | 29 ++++++++++++++----- 3 files changed, 44 insertions(+), 15 deletions(-) diff --git a/lib/mv/email_sync/changes/sync_member_email_to_user.ex b/lib/mv/email_sync/changes/sync_member_email_to_user.ex index 48c7955..0c0d8f7 100644 --- a/lib/mv/email_sync/changes/sync_member_email_to_user.ex +++ b/lib/mv/email_sync/changes/sync_member_email_to_user.ex @@ -41,8 +41,10 @@ defmodule Mv.EmailSync.Changes.SyncMemberEmailToUser do Ash.Changeset.around_transaction(changeset, fn cs, callback -> result = callback.(cs) + actor = Map.get(changeset.context, :actor) + with {:ok, member} <- Helpers.extract_record(result), - linked_user <- Loader.get_linked_user(member) do + linked_user <- Loader.get_linked_user(member, actor) do Helpers.sync_email_to_linked_record(result, linked_user, new_email) else _ -> result diff --git a/lib/mv/email_sync/changes/sync_user_email_to_member.ex b/lib/mv/email_sync/changes/sync_user_email_to_member.ex index 7148067..54829a4 100644 --- a/lib/mv/email_sync/changes/sync_user_email_to_member.ex +++ b/lib/mv/email_sync/changes/sync_user_email_to_member.ex @@ -33,7 +33,17 @@ defmodule Mv.EmailSync.Changes.SyncUserEmailToMember do if Map.get(context, :syncing_email, false) do changeset else - sync_email(changeset) + # Ensure actor is in changeset context - get it from context if available + actor = Map.get(changeset.context, :actor) || Map.get(context, :actor) + + changeset_with_actor = + if actor && !Map.has_key?(changeset.context, :actor) do + Ash.Changeset.put_context(changeset, :actor, actor) + else + changeset + end + + sync_email(changeset_with_actor) end end @@ -42,7 +52,7 @@ defmodule Mv.EmailSync.Changes.SyncUserEmailToMember do result = callback.(cs) with {:ok, record} <- Helpers.extract_record(result), - {:ok, user, member} <- get_user_and_member(record) do + {:ok, user, member} <- get_user_and_member(record, cs) do # When called from Member-side, we need to update the member in the result # When called from User-side, we update the linked member in DB only case record do @@ -61,15 +71,19 @@ defmodule Mv.EmailSync.Changes.SyncUserEmailToMember do end # Retrieves user and member - works for both resource types - defp get_user_and_member(%Mv.Accounts.User{} = user) do - case Loader.get_linked_member(user) do + defp get_user_and_member(%Mv.Accounts.User{} = user, changeset) do + actor = Map.get(changeset.context, :actor) + + case Loader.get_linked_member(user, actor) do nil -> {:error, :no_member} member -> {:ok, user, member} end end - defp get_user_and_member(%Mv.Membership.Member{} = member) do - case Loader.load_linked_user!(member) do + defp get_user_and_member(%Mv.Membership.Member{} = member, changeset) do + actor = Map.get(changeset.context, :actor) + + case Loader.load_linked_user!(member, actor) do {:ok, user} -> {:ok, user, member} error -> error end diff --git a/lib/mv/email_sync/loader.ex b/lib/mv/email_sync/loader.ex index ecb1038..f6f6ecc 100644 --- a/lib/mv/email_sync/loader.ex +++ b/lib/mv/email_sync/loader.ex @@ -6,11 +6,16 @@ defmodule Mv.EmailSync.Loader do @doc """ Loads the member linked to a user, returns nil if not linked or on error. - """ - def get_linked_member(%{member_id: nil}), do: nil - def get_linked_member(%{member_id: id}) do - case Ash.get(Mv.Membership.Member, id) do + Accepts optional actor for authorization. + """ + def get_linked_member(user, actor \\ nil) + def get_linked_member(%{member_id: nil}, _actor), do: nil + + def get_linked_member(%{member_id: id}, actor) do + opts = if actor, do: [actor: actor], else: [] + + case Ash.get(Mv.Membership.Member, id, opts) do {:ok, member} -> member {:error, _} -> nil end @@ -18,9 +23,13 @@ defmodule Mv.EmailSync.Loader do @doc """ Loads the user linked to a member, returns nil if not linked or on error. + + Accepts optional actor for authorization. """ - def get_linked_user(member) do - case Ash.load(member, :user) do + def get_linked_user(member, actor \\ nil) do + opts = if actor, do: [actor: actor], else: [] + + case Ash.load(member, :user, opts) do {:ok, %{user: user}} -> user {:error, _} -> nil end @@ -29,9 +38,13 @@ defmodule Mv.EmailSync.Loader do @doc """ Loads the user linked to a member, returning an error tuple if not linked. Useful when a link is required for the operation. + + Accepts optional actor for authorization. """ - def load_linked_user!(member) do - case Ash.load(member, :user) do + def load_linked_user!(member, actor \\ nil) do + opts = if actor, do: [actor: actor], else: [] + + case Ash.load(member, :user, opts) do {:ok, %{user: user}} when not is_nil(user) -> {:ok, user} {:ok, _} -> {:error, :no_linked_user} {:error, _} = error -> error