Recreate user profile on account reactivation (#19902)

Co-authored-by: Andrew Morgan <1342360+anoadragon453@users.noreply.github.com>
This commit is contained in:
Lukas Jung
2026-07-06 12:38:24 +00:00
committed by GitHub
co-authored by Andrew Morgan
parent 5f89ff2b31
commit 963e486a70
4 changed files with 77 additions and 3 deletions
+2
View File
@@ -0,0 +1,2 @@
Fix a bug introduced in Synapse v1.150.0 where reactivating a deactivated and erased user did not restore their profile, breaking login, name changes, and invitations.
Contributed by @m4us1ne.
+3
View File
@@ -349,6 +349,9 @@ class DeactivateAccountHandler:
# Ensure the user is not marked as erased.
await self.store.mark_user_not_erased(user_id)
# The profile row is deleted on erasure, so recreate it if missing.
await self.store.create_profile(user)
# Mark the user as active.
await self.store.set_user_deactivated_status(user_id, False)
+5 -3
View File
@@ -293,15 +293,17 @@ class ProfileWorkerStore(SQLBaseStore):
async def create_profile(self, user_id: UserID) -> None:
"""
Create a blank profile for a user.
Create a blank profile for a user, if one does not already exist.
Args:
user_id: The user to create the profile for.
"""
user_localpart = user_id.localpart
await self.db_pool.simple_insert(
await self.db_pool.simple_upsert(
table="profiles",
values={"user_id": user_localpart, "full_user_id": user_id.to_string()},
keyvalues={"full_user_id": user_id.to_string()},
values={},
insertion_values={"user_id": user_localpart},
desc="create_profile",
)
+67
View File
@@ -483,3 +483,70 @@ class DeactivateAccountTestCase(HomeserverTestCase):
# Validate that the created room is forgotten
self.assertTrue(room_id in forgotten_rooms)
def _get_profile_row(self) -> str | None:
"""Return the `user_id` of `self.user`'s `profiles` row, or None if absent."""
return self.get_success(
self._store.db_pool.simple_select_one_onecol(
table="profiles",
keyvalues={"full_user_id": self.user},
retcol="user_id",
allow_none=True,
desc="_get_profile_row",
)
)
def test_reactivation_recreates_profile(self) -> None:
"""
Tests that reactivating an erased user recreates their profile row, so
that subsequent profile operations work.
"""
self.assertIsNotNone(self._get_profile_row())
# Erasure deletes the whole profiles row.
self._deactivate_my_account()
self.assertIsNone(self._get_profile_row())
# Reactivating recreates a blank profile row.
deactivate_handler = self.hs.get_deactivate_account_handler()
self.get_success(deactivate_handler.activate_account(self.user))
self.assertIsNotNone(self._get_profile_row())
# Setting a display name now works again.
user = UserID.from_string(self.user)
self.get_success(
self.hs.get_profile_handler().set_displayname(
user, create_requester(user), "Reactivated", by_admin=True
)
)
self.assertEqual(
self.get_success(self._store.get_profile_displayname(user)),
"Reactivated",
)
def test_reactivation_without_erasure_keeps_profile(self) -> None:
"""
Reactivating a user whose profile row still exists leaves it untouched.
"""
user = UserID.from_string(self.user)
self.get_success(
self.hs.get_profile_handler().set_displayname(
user, create_requester(user), "Original", by_admin=True
)
)
# Deactivate without erasure, so the profile row is left intact.
deactivate_handler = self.hs.get_deactivate_account_handler()
self.get_success(
deactivate_handler.deactivate_account(
self.user, erase_data=False, requester=create_requester(user)
)
)
self.assertIsNotNone(self._get_profile_row())
# Reactivating must not raise despite the existing profile row.
self.get_success(deactivate_handler.activate_account(self.user))
self.assertEqual(
self.get_success(self._store.get_profile_displayname(user)),
"Original",
)