diff --git a/src/api/client/session/password.rs b/src/api/client/session/password.rs index 2d7d919d28..6e8290a9c5 100644 --- a/src/api/client/session/password.rs +++ b/src/api/client/session/password.rs @@ -38,7 +38,7 @@ pub(super) async fn handle_login( let lowercased_user_id = UserId::parse_with_server_name( user_id.localpart().to_lowercase(), - &services.config.server_name, + user_id.server_name(), )?; let user_is_remote = !services.globals.user_is_local(&user_id) diff --git a/src/core/config/mod.rs b/src/core/config/mod.rs index 590df103e6..bb119a6e73 100644 --- a/src/core/config/mod.rs +++ b/src/core/config/mod.rs @@ -88,6 +88,31 @@ pub struct Config { #[cfg_attr(test, serde(default = "default_server_name"))] pub server_name: OwnedServerName, + /// Alternate server names this homeserver is authoritative for. Users + /// with a Matrix ID on any of these domains can authenticate and use this + /// server, in addition to users on the primary `server_name`. + /// + /// Each name must be a valid Matrix server name. + /// + /// Alternate server names can be added after initialization, but should not + /// be removed while there exist accounts or appservice registrations using + /// that domain. + /// + /// Each alternate domain must independently satisfy the Matrix + /// well-known/delegation requirements so that federation peers and clients + /// can resolve it to this server. + /// + /// Note that servers providing the legacy registration endpoint can block + /// registration of users on alternate server names by including the full + /// User ID (e.g. "@user:example.com") or just the domain (e.g. ":example.com") in + /// `forbidden_usernames`. + /// + /// example: ["legacy.example.com", "alias.example.org"] + /// + /// default: [] + #[serde(default)] + pub alternate_server_names: Vec, + /// This is the only directory where tuwunel will save its data, including /// media. Note: this was previously "/var/lib/matrix-conduit". /// diff --git a/src/main/tests/alternate_domains.rs b/src/main/tests/alternate_domains.rs new file mode 100644 index 0000000000..ba8fef3d9f --- /dev/null +++ b/src/main/tests/alternate_domains.rs @@ -0,0 +1,81 @@ +#![cfg(test)] + +use std::sync::Arc; + +use tuwunel::{Args, Runtime, Server}; +use tuwunel_core::{ + Err, Result, ruma:: + UserId + , +}; +use tuwunel_service::Services; +use tuwunel_service::users::Register; + + +const PRIMARY: &str = "primary.example.test"; +const ALT: &str = "alt.example.test"; + +fn alt_domain_user_id() -> &'static UserId { + "@bob:alt.example.test" + .try_into() + .expect("valid user id") +} + +#[test] +fn alternate_server_names_register_login_message_federate() -> Result { + let mut args = Args::default_test(&["fresh", "cleanup"]); + + args.option + .push(format!("server_name=\"{PRIMARY}\"")); + args.option + .push(format!("alternate_server_names=[\"{ALT}\"]")); + + args.maintenance = true; + + let runtime = Runtime::new(Some(&args))?; + let server = Server::new(Some(&args), Some(&runtime))?; + + let result: Result = runtime.block_on(async { + let services = tuwunel::async_start(&server).await?; + + let outcome = run_tests(&services).await; + + server.server.shutdown()?; + drop(services); + tuwunel::async_run(&server).await?; + tuwunel::async_stop(&server).await?; + + outcome + }); + + drop(runtime); + result +} + +async fn run_tests(services: &Arc) -> Result { + test_register(services).await?; + Ok(()) +} + +/// Test that an alternate-domain user can be registered. +async fn test_register(services: &Arc) -> Result { + let alternate_user_id = alt_domain_user_id(); + + services + .users + .full_register(Register { + user_id: Some(&alternate_user_id), + password: Some("alternateuserpassword"), + is_appservice: false, + is_guest: false, + grant_first_user_admin: false, + ..Default::default() + }) + .await?; + + if !services.users.exists(alternate_user_id).await { + return Err!("({alternate_user_id}) was not found after registration"); + } + + Ok(()) +} diff --git a/src/service/globals/mod.rs b/src/service/globals/mod.rs index fb48acf5ea..17a8d6fe72 100644 --- a/src/service/globals/mod.rs +++ b/src/service/globals/mod.rs @@ -100,6 +100,12 @@ impl Service { #[must_use] pub fn server_is_ours(&self, server_name: &ServerName) -> bool { server_name == self.server_name() + || self + .server + .config + .alternate_server_names + .iter() + .any(|s| s == server_name) } #[inline] diff --git a/src/service/rooms/timeline/build.rs b/src/service/rooms/timeline/build.rs index 6e8231d336..42032a1b28 100644 --- a/src/service/rooms/timeline/build.rs +++ b/src/service/rooms/timeline/build.rs @@ -160,9 +160,9 @@ pub async fn build_and_append_pdu( servers.insert(state_key_uid.server_name().to_owned()); } - // Remove our server from the server list since it will be added to it by - // room_servers() and/or the if statement above - servers.remove(self.services.globals.server_name()); + // Remove all servers we are authoritative for from the federation list, + // since they will be added to it by room_servers() and/or the if statement above. + servers.retain(|s| !self.services.globals.server_is_ours(s)); self.services .sending diff --git a/tuwunel-example.toml b/tuwunel-example.toml index 06176342a9..7a0142ea9c 100644 --- a/tuwunel-example.toml +++ b/tuwunel-example.toml @@ -37,6 +37,29 @@ # #server_name = +# Alternate server names this homeserver is authoritative for. Users +# with a Matrix ID on any of these domains can authenticate and use this +# server, in addition to users on the primary `server_name`. +# +# Each name must be a valid Matrix server name. +# +# Alternate server names can be added after initialization, but should not +# be removed while there exist accounts or appservice registrations using +# that domain. +# +# Each alternate domain must independently satisfy the Matrix +# well-known/delegation requirements so that federation peers and clients +# can resolve it to this server. +# +# Note that servers providing the legacy registration endpoint can block +# registration of users on alternate server names by including the full +# User ID (e.g. "@user:example.com") or just the domain (e.g. ":example.com") in +# `forbidden_usernames`. +# +# example: ["legacy.example.com", "alias.example.org"] +# +#alternate_server_names = [] + # This is the only directory where tuwunel will save its data, including # media. Note: this was previously "/var/lib/matrix-conduit". #