From cd20f63e9391cd2d9b219435c35454e15650a77e Mon Sep 17 00:00:00 2001 From: Tom Foster Date: Sun, 10 Aug 2025 13:17:05 +0100 Subject: [PATCH] fix(appservice): create sender_localpart user during appservice startup Fixes #813: Application services were unable to work because their sender_localpart user was never created in the database, preventing authentication. This fix ensures the appservice user account is created when: - The server starts up and loads existing appservices from the database - A new appservice is registered via the admin command Additionally, if an appservice user has been accidentally deactivated, it will be automatically reactivated when the appservice starts. The solution centralises all appservice startup logic into a single `start_appservice` helper method, eliminating code duplication between the registration and startup paths. --- src/service/appservice/mod.rs | 45 +++++++++++++++++++++++++---------- 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/src/service/appservice/mod.rs b/src/service/appservice/mod.rs index 7be8a471..b00acc0b 100644 --- a/src/service/appservice/mod.rs +++ b/src/service/appservice/mod.rs @@ -11,7 +11,7 @@ use ruma::{RoomAliasId, RoomId, UserId, api::appservice::Registration}; use tokio::sync::{RwLock, RwLockReadGuard}; pub use self::{namespace_regex::NamespaceRegex, registration_info::RegistrationInfo}; -use crate::{Dep, sending}; +use crate::{Dep, globals, sending, users}; pub struct Service { registration_info: RwLock, @@ -20,7 +20,9 @@ pub struct Service { } struct Services { + globals: Dep, sending: Dep, + users: Dep, } struct Data { @@ -35,7 +37,9 @@ impl crate::Service for Service { Ok(Arc::new(Self { registration_info: RwLock::new(BTreeMap::new()), services: Services { + globals: args.depend::("globals"), sending: args.depend::("sending"), + users: args.depend::("users"), }, db: Data { id_appserviceregistrations: args.db["id_appserviceregistrations"].clone(), @@ -44,15 +48,10 @@ impl crate::Service for Service { } async fn worker(self: Arc) -> Result { - // Inserting registrations into cache self.iter_db_ids() .try_for_each(async |appservice| { - self.registration_info - .write() - .await - .insert(appservice.0, appservice.1.try_into()?); - - Ok(()) + let (id, registration) = appservice; + self.start_appservice(id, registration).await }) .await } @@ -61,6 +60,30 @@ impl crate::Service for Service { } impl Service { + /// Starts an appservice, ensuring its sender_localpart user exists and is active. + /// Creates the user if it doesn't exist, or reactivates it if it was deactivated. + /// Then registers the appservice in memory for request handling. + async fn start_appservice(&self, id: String, registration: Registration) -> Result { + let appservice_user_id = UserId::parse_with_server_name( + registration.sender_localpart.as_str(), + self.services.globals.server_name(), + )?; + + if !self.services.users.exists(&appservice_user_id).await { + self.services.users.create(&appservice_user_id, None)?; + } else if self.services.users.is_deactivated(&appservice_user_id).await.unwrap_or(false) { + // Reactivate the appservice user if it was accidentally deactivated + self.services.users.set_password(&appservice_user_id, None)?; + } + + self.registration_info + .write() + .await + .insert(id, registration.try_into()?); + + Ok(()) + } + /// Registers an appservice and returns the ID to the caller pub async fn register_appservice( &self, @@ -68,15 +91,13 @@ impl Service { appservice_config_body: &str, ) -> Result { //TODO: Check for collisions between exclusive appservice namespaces - self.registration_info - .write() - .await - .insert(registration.id.clone(), registration.clone().try_into()?); self.db .id_appserviceregistrations .insert(®istration.id, appservice_config_body); + self.start_appservice(registration.id.clone(), registration.clone()).await?; + Ok(()) }