diff --git a/docs/configuration.md b/docs/configuration.md index 9687ead1..4318e5ee 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -45,6 +45,8 @@ The `global` section contains the following fields: | `allow_federation` | `boolean` | Allow federation with other servers | `true` | | `allow_room_creation` | `boolean` | Allow users to create rooms | `true` | | `allow_unstable_room_versions` | `boolean` | Allow users to create and join rooms with unstable versions | `true` | +| `default_rooms` | `array` | The list of rooms that will be joined by default on registration | [] | +| `default_rooms_join_reason` | `string` | The reason for joining the rooms | "Hello from Conduit!" | | `default_room_version` | `string` | The default room version (`"6"`-`"10"`)| `"10"` | | `allow_jaeger` | `boolean` | Allow Jaeger tracing | `false` | | `tracing_flame` | `boolean` | Enable flame tracing | `false` | @@ -111,3 +113,14 @@ exclude = ["*.clearnet.onion"] [global] {{#include ../conduit-example.toml:22:}} ``` + + +### Default rooms +This list should contain either room identifiers or aliases. +Opaque identifiers will be interpreted as incomplete, local aliases. + +#### Example +In this example, the opaque identifier and alias are the same: +```toml +default_rooms = ["home", "#home:conduit.rs"] +``` diff --git a/src/api/client_server/account.rs b/src/api/client_server/account.rs index 47ccdc83..152b82bd 100644 --- a/src/api/client_server/account.rs +++ b/src/api/client_server/account.rs @@ -1,5 +1,8 @@ use super::{DEVICE_ID_LENGTH, SESSION_ID_LENGTH, TOKEN_LENGTH}; -use crate::{api::client_server, services, utils, Error, Result, Ruma}; +use crate::{ + api::client_server::{self, membership::join_room_by_id_helper}, + services, utils, Error, Result, Ruma, +}; use ruma::{ api::client::{ account::{ @@ -287,6 +290,26 @@ pub async fn register_route(body: Ruma) -> Result = default_rooms + .iter() + .filter_map(|r| r.server_name().map(ToOwned::to_owned)) + .collect(); + + for room_id in &default_rooms { + if let Err(e) = join_room_by_id_helper( + Some(&user_id), + room_id, + services().globals.config.default_rooms.join_reason.clone(), + &servers, + None, + ) + .await + { + warn!("Failed to join default room: {}", e); + } + } } Ok(register::v3::Response { diff --git a/src/api/client_server/membership.rs b/src/api/client_server/membership.rs index baf2f239..b5a83d1c 100644 --- a/src/api/client_server/membership.rs +++ b/src/api/client_server/membership.rs @@ -518,7 +518,7 @@ pub async fn joined_members_route( Ok(joined_members::v3::Response { joined }) } -async fn join_room_by_id_helper( +pub async fn join_room_by_id_helper( sender_user: Option<&UserId>, room_id: &RoomId, reason: Option, diff --git a/src/config/mod.rs b/src/config/mod.rs index 378ab929..5f5355a1 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -61,6 +61,8 @@ pub struct Config { pub default_room_version: RoomVersionId, #[serde(default)] pub well_known: WellKnownConfig, + #[serde(default)] + pub default_rooms: DefaultRoomsConfig, #[serde(default = "false_fn")] pub allow_jaeger: bool, #[serde(default = "false_fn")] @@ -101,6 +103,13 @@ pub struct WellKnownConfig { pub server: Option, } +#[derive(Clone, Debug, Deserialize, Default)] +pub struct DefaultRoomsConfig { + #[serde(default = "Vec::new")] + pub rooms: Vec, + pub join_reason: Option, +} + const DEPRECATED_KEYS: &[&str] = &["cache_capacity"]; impl Config { diff --git a/src/service/globals/mod.rs b/src/service/globals/mod.rs index 3325e518..d7306569 100644 --- a/src/service/globals/mod.rs +++ b/src/service/globals/mod.rs @@ -1,11 +1,12 @@ mod data; pub use data::{Data, SigningKeys}; + use ruma::{ serde::Base64, MilliSecondsSinceUnixEpoch, OwnedDeviceId, OwnedEventId, OwnedRoomAliasId, - OwnedRoomId, OwnedServerName, OwnedUserId, RoomAliasId, + OwnedRoomId, OwnedRoomOrAliasId, OwnedServerName, OwnedUserId, RoomAliasId, }; -use crate::api::server_server::DestinationResponse; +use crate::api::{client_server::get_alias_helper, server_server::DestinationResponse}; use crate::{services, Config, Error, Result}; use futures_util::FutureExt; @@ -17,7 +18,7 @@ use ruma::{ DeviceId, RoomVersionId, ServerName, UserId, }; use std::{ - collections::{BTreeMap, HashMap}, + collections::{BTreeMap, BTreeSet, HashMap}, error::Error as StdError, fs, future::{self, Future}, @@ -33,7 +34,7 @@ use std::{ }; use tokio::sync::{broadcast, watch::Receiver, Mutex, RwLock, Semaphore}; use tower_service::Service as TowerService; -use tracing::{error, info}; +use tracing::{error, info, warn}; use base64::{engine::general_purpose, Engine as _}; @@ -505,6 +506,33 @@ impl Service { self.config.well_known_client() } + pub async fn default_rooms(&self) -> Result> { + let server_name = &self.config.server_name; + + let f = |s| async move { + match OwnedRoomOrAliasId::from_str(s).map(OwnedRoomId::try_from) { + Ok(Ok(room_id)) => room_id + .server_name() + .ok_or_else(|| Error::bad_config("Invalid room ID due to missing server name.")) + .map(|_| room_id.clone()), + result => get_alias_helper(result.map(Result::unwrap_err).or_else(|_| { + format!("#{s}:{server_name}") + .parse() + .map_err(|_| Error::bad_config("Invalid opaque identifier.")) + })?) + .await + .map(|response| response.room_id), + } + }; + + let rooms = self.config.default_rooms.rooms.iter().map(String::as_str); + + futures_util::future::try_join_all(rooms.map(f)) + .await + .map(Vec::into_iter) + .map(BTreeSet::from_iter) + } + pub fn shutdown(&self) { self.shutdown.store(true, atomic::Ordering::Relaxed); // On shutdown