Repository: matrix-org/synapse

Issue:
Reactivate an account
### Description

Deactivating an account works but trying to reactivate an account does not.
As far as I can tell from synapse code there is no handler for account reactivation.
https://github.com/matrix-org/synapse/blob/master/synapse/rest/admin/users.py#L229
If this is not implemented, wouldn't it be good to point it out in docs? https://github.com/matrix-org/synapse/blob/master/docs/admin_api/user_admin_api.rst

### Steps to reproduce

- deactivate an account using rest api
- account is properly deactivated
- try and reactivate the account

### Version information

- **Homeserver**: 1.12.3+bionic1

- **Install method**: apt

- **Platform**: ubuntu 18.04



Discussion:
AFAIK it is not implemented.
I don't believe this is implemented, the API should probably reject attempting to set deactivated to `False` if the account has previously been deactivated.

I agree that the documentation could be clearer here, I'm unsure how difficult it would be to implement reactivating of accounts.
Would this be possible via some `psql` command?
> Would this be possible via some `psql` command?

Probably. From the top of my head, reactivating an account should only need to update the user's row in the `users` table, by switching `deactivated` to false and setting a password. Bear in mind, though, that the deactivation process in Synapse makes the user leave every room they're in and unbind the 3PIDs they had bound, and reactivating won't magically undo that.
For the upcoming swing at this work it's only necessary to allow the user to log in with their previously-deactivated username - there's no need to try and restore all of their old room memberships.
In fact it could be important to **not** access previous data, if some company decides to recycle usernames in ldap/SSO.
How do I use the reactivation feature for my Matrix account?
> How do I use the reactivation feature for my Matrix account?

An administrator would need to use the admin API to re-activate the account. This also isn't released yet, but will be in the next version of Synapse.
@clokep When the admin reactivate the account, it will not be removed from database table ```erased_users```? Is this on purpose?
@dklimpel No, that is not on purpose. Looks like that was missed when implementing it. I'll take a look!

Resolving patch:
diff --git a/synapse/handlers/deactivate_account.py b/synapse/handlers/deactivate_account.py
--- a/synapse/handlers/deactivate_account.py
+++ b/synapse/handlers/deactivate_account.py
@@ -30,6 +30,7 @@ class DeactivateAccountHandler(BaseHandler):
 
     def __init__(self, hs):
         super(DeactivateAccountHandler, self).__init__(hs)
+        self.hs = hs
         self._auth_handler = hs.get_auth_handler()
         self._device_handler = hs.get_device_handler()
         self._room_member_handler = hs.get_room_member_handler()
@@ -222,13 +223,26 @@ async def activate_account(self, user_id: str) -> None:
         """
         Activate an account that was previously deactivated.
 
-        This simply marks the user as activate in the database and does not
-        attempt to rejoin rooms, re-add threepids, etc.
+        This marks the user as active and not erased in the database, but does
+        not attempt to rejoin rooms, re-add threepids, etc.
+
+        If enabled, the user will be re-added to the user directory.
 
         The user will also need a password hash set to actually login.
 
         Args:
-            user_id: ID of user to be deactivated
+            user_id: ID of user to be re-activated
         """
-        # Mark the user as activate.
+        # Add the user to the directory, if necessary.
+        user = UserID.from_string(user_id)
+        if self.hs.config.user_directory_search_all_users:
+            profile = await self.store.get_profileinfo(user.localpart)
+            await self.user_directory_handler.handle_local_profile_change(
+                user_id, profile
+            )
+
+        # Ensure the user is not marked as erased.
+        await self.store.mark_user_not_erased(user_id)
+
+        # Mark the user as active.
         await self.store.set_user_deactivated_status(user_id, False)
diff --git a/synapse/storage/data_stores/main/user_erasure_store.py b/synapse/storage/data_stores/main/user_erasure_store.py
--- a/synapse/storage/data_stores/main/user_erasure_store.py
+++ b/synapse/storage/data_stores/main/user_erasure_store.py
@@ -70,11 +70,11 @@ def are_users_erased(self, user_ids):
 
 
 class UserErasureStore(UserErasureWorkerStore):
-    def mark_user_erased(self, user_id):
+    def mark_user_erased(self, user_id: str) -> None:
         """Indicate that user_id wishes their message history to be erased.
 
         Args:
-            user_id (str): full user_id to be erased
+            user_id: full user_id to be erased
         """
 
         def f(txn):
@@ -89,3 +89,25 @@ def f(txn):
             self._invalidate_cache_and_stream(txn, self.is_user_erased, (user_id,))
 
         return self.db.runInteraction("mark_user_erased", f)
+
+    def mark_user_not_erased(self, user_id: str) -> None:
+        """Indicate that user_id is no longer erased.
+
+        Args:
+            user_id: full user_id to be un-erased
+        """
+
+        def f(txn):
+            # first check if they are already in the list
+            txn.execute("SELECT 1 FROM erased_users WHERE user_id = ?", (user_id,))
+            if not txn.fetchone():
+                return
+
+            # They are there, delete them.
+            self.simple_delete_one_txn(
+                txn, "erased_users", keyvalues={"user_id": user_id}
+            )
+
+            self._invalidate_cache_and_stream(txn, self.is_user_erased, (user_id,))
+
+        return self.db.runInteraction("mark_user_not_erased", f)


Test patch: