From 2b4e1e3963a31ae78a0d7ddb04e36b8deea94741 Mon Sep 17 00:00:00 2001 From: Moritz Date: Tue, 27 Jan 2026 16:05:50 +0100 Subject: [PATCH] Sync user email to member when changing password (admin_set_password) Add SyncUserEmailToMember change to admin_set_password so email+password updates in the user form sync the new email to the linked member. --- lib/accounts/user.ex | 5 ++++ test/accounts/user_email_sync_test.exs | 37 ++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/lib/accounts/user.ex b/lib/accounts/user.ex index 875925b..4015aaa 100644 --- a/lib/accounts/user.ex +++ b/lib/accounts/user.ex @@ -194,6 +194,11 @@ defmodule Mv.Accounts.User do # Use the official Ash Authentication password change change AshAuthentication.Strategy.Password.HashPasswordChange + + # Sync email changes to linked member when email is changed (e.g. form changes both) + change Mv.EmailSync.Changes.SyncUserEmailToMember do + where [changing(:email)] + end end # Action to link an OIDC account to an existing password-only user diff --git a/test/accounts/user_email_sync_test.exs b/test/accounts/user_email_sync_test.exs index d324783..eab4e38 100644 --- a/test/accounts/user_email_sync_test.exs +++ b/test/accounts/user_email_sync_test.exs @@ -120,6 +120,43 @@ defmodule Mv.Accounts.UserEmailSyncTest do {:ok, member_after_unlink} = Ash.get(Mv.Membership.Member, member.id, actor: actor) assert member_after_unlink.email == "user@example.com" end + + test "admin_set_password with email change syncs to linked member", %{actor: actor} do + # Create member and user linked to it (with password so admin_set_password applies) + {:ok, member} = Membership.create_member(@valid_member_attrs, actor: actor) + + {:ok, user} = + Mv.Accounts.User + |> Ash.Changeset.for_create(:register_with_password, %{ + email: "user@example.com", + password: "initialpass123" + }) + |> Ash.create(actor: actor) + + {:ok, user} = + user + |> Ash.Changeset.for_update(:update_user, %{member: %{id: member.id}}) + |> Ash.update(actor: actor) + + assert user.member_id == member.id + {:ok, m} = Ash.get(Mv.Membership.Member, member.id, actor: actor) + assert m.email == "user@example.com" + + # Change both email and password via admin_set_password (e.g. user form "Change Password") + {:ok, updated_user} = + user + |> Ash.Changeset.for_update(:admin_set_password, %{ + email: "newemail@example.com", + password: "newpassword123" + }) + |> Ash.update(actor: actor) + + assert to_string(updated_user.email) == "newemail@example.com" + + # Member email must be synced (Option A: SyncUserEmailToMember on admin_set_password) + {:ok, synced_member} = Ash.get(Mv.Membership.Member, member.id, actor: actor) + assert synced_member.email == "newemail@example.com" + end end describe "AshAuthentication compatibility" do -- 2.47.2