From bfa68e7bc5ef5fb2704778968d646cae5e30244e Mon Sep 17 00:00:00 2001
From: strawberry <strawberry@puppygock.gay>
Date: Thu, 11 Apr 2024 20:17:30 -0400
Subject: [PATCH] refactor well-known stuff to use proper ruma types, config
 types, etc

this does deprecate the original `well_known_` prefixed config options
with a dedicated/proper config sub-block (`[config.well_known]`)

Signed-off-by: strawberry <strawberry@puppygock.gay>
---
 Cargo.lock                           |  1 +
 Cargo.toml                           |  3 ++
 conduwuit-example.toml               | 33 +++++++------
 src/api/client_server/session.rs     | 22 +++------
 src/api/client_server/unversioned.rs | 31 ++++++++----
 src/api/server_server.rs             | 20 ++++----
 src/config/mod.rs                    | 73 ++++++++++++++++++++++++----
 src/routes.rs                        |  4 +-
 src/service/globals/mod.rs           | 13 ++---
 9 files changed, 134 insertions(+), 66 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 6be278be..03232b2a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -522,6 +522,7 @@ dependencies = [
  "tracing-flame",
  "tracing-opentelemetry",
  "tracing-subscriber",
+ "url",
  "webpage",
 ]
 
diff --git a/Cargo.toml b/Cargo.toml
index 97a7a952..d6c05719 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -75,6 +75,9 @@ http = "0.2.12"
 # used to replace the channels of the tokio runtime
 loole = "0.3.0"
 
+# Validating urls in config, was already a transitive dependency
+url = { version = "2", features = ["serde"] }
+
 # standard date and time tools
 [dependencies.chrono]
 version = "0.4.37"
diff --git a/conduwuit-example.toml b/conduwuit-example.toml
index 8128d150..73084f96 100644
--- a/conduwuit-example.toml
+++ b/conduwuit-example.toml
@@ -2,6 +2,8 @@
 #  This is the official example config for conduwuit.
 #  If you use it for your server, you will need to adjust it to your own needs.
 #  At the very least, change the server_name field!
+#
+#  This documentation can also be found at https://conduwuit.puppyirl.gay/configuration.html
 # =============================================================================
 
 [global]
@@ -247,14 +249,6 @@ url_preview_max_spider_size = 384_000
 # Useful if the domain contains allowlist is still too broad for you but you still want to allow all the subdomains under a root domain.
 url_preview_check_root_domain = false
 
-# A single contact and/or support page for /.well-known/matrix/support
-# All options here are strings. Currently only supports 1 single contact.
-# No default.
-#well_known_support_page = ""
-#well_known_support_role = ""
-#well_known_support_email = ""
-#well_known_support_mxid = ""
-
 # Config option to allow or disallow incoming federation requests that obtain the profiles
 # of our local users from `/_matrix/federation/v1/query/profile`
 #
@@ -281,12 +275,6 @@ allow_profile_lookup_federation_requests = true
 # Defaults to false.
 #allow_check_for_updates = false
 
-# If you are using delegation via well-known files and you cannot serve them from your reverse proxy, you can
-# uncomment these to serve them directly from conduwuit. This requires proxying all requests to conduwuit, not just `/_matrix` to work.
-#well_known_server = "matrix.example.com:443"
-#well_known_client = "https://matrix.example.com"
-# Note that whatever you put will show up in the well-known JSON values.
-
 # Set to false to disable users from joining or creating room versions that aren't 100% officially supported by conduwuit.
 # conduwuit officially supports room versions 6 - 10. conduwuit has experimental/unstable support for 3 - 5, and 11.
 # Defaults to true.
@@ -623,3 +611,20 @@ allow_profile_lookup_federation_requests = true
 # This config option is only available if conduwuit was built with `axum_dual_protocol` feature (not default feature)
 # Defaults to false
 #dual_protocol = false
+
+
+# If you are using delegation via well-known files and you cannot serve them from your reverse proxy, you can
+# uncomment these to serve them directly from conduwuit. This requires proxying all requests to conduwuit, not just `/_matrix` to work.
+#
+#[global.well_known]
+#server = "matrix.example.com:443"
+#client = "https://matrix.example.com"
+#
+# A single contact and/or support page for /.well-known/matrix/support
+# All options here are strings. Currently only supports 1 single contact.
+# No default.
+#
+#well_known_support_page = ""
+#well_known_support_role = ""
+#well_known_support_email = ""
+#well_known_support_mxid = ""
diff --git a/src/api/client_server/session.rs b/src/api/client_server/session.rs
index 9ac7f918..70ed113c 100644
--- a/src/api/client_server/session.rs
+++ b/src/api/client_server/session.rs
@@ -199,15 +199,13 @@ pub async fn login_route(body: Ruma<login::v3::Request>) -> Result<login::v3::Re
 	}
 
 	// send client well-known if specified so the client knows to reconfigure itself
-	let client_discovery_info = DiscoveryInfo::new(HomeserverInfo::new(
-		services()
-			.globals
-			.well_known_client()
-			.to_owned()
-			.unwrap_or_default(),
-	));
+	let client_discovery_info: Option<DiscoveryInfo> = services()
+		.globals
+		.well_known_client()
+		.as_ref()
+		.map(|server| DiscoveryInfo::new(HomeserverInfo::new(server.to_string())));
 
-	info!("{} logged in", user_id);
+	info!("{user_id} logged in");
 
 	// home_server is deprecated but apparently must still be sent despite it being
 	// deprecated over 6 years ago. initially i thought this macro was unnecessary,
@@ -217,13 +215,7 @@ pub async fn login_route(body: Ruma<login::v3::Request>) -> Result<login::v3::Re
 		user_id,
 		access_token: token,
 		device_id,
-		well_known: {
-			if client_discovery_info.homeserver.base_url.as_str() == "" {
-				None
-			} else {
-				Some(client_discovery_info)
-			}
-		},
+		well_known: client_discovery_info,
 		expires_in: None,
 		home_server: Some(services().globals.server_name().to_owned()),
 		refresh_token: None,
diff --git a/src/api/client_server/unversioned.rs b/src/api/client_server/unversioned.rs
index a4a978ec..163837b0 100644
--- a/src/api/client_server/unversioned.rs
+++ b/src/api/client_server/unversioned.rs
@@ -3,6 +3,7 @@ use std::collections::BTreeMap;
 use axum::{response::IntoResponse, Json};
 use ruma::api::client::{
 	discovery::{
+		discover_homeserver::{self, HomeserverInfo, SlidingSyncProxyInfo},
 		discover_support::{self, Contact},
 		get_supported_versions,
 	},
@@ -57,23 +58,35 @@ pub async fn get_supported_versions_route(
 }
 
 /// # `GET /.well-known/matrix/client`
-pub async fn well_known_client_route() -> Result<impl IntoResponse> {
+///
+/// Returns the .well-known URL if it is configured, otherwise returns 404.
+pub async fn well_known_client(_body: Ruma<discover_homeserver::Request>) -> Result<discover_homeserver::Response> {
 	let client_url = match services().globals.well_known_client() {
-		Some(url) => url.clone(),
+		Some(url) => url.to_string(),
 		None => return Err(Error::BadRequest(ErrorKind::NotFound, "Not found.")),
 	};
 
-	Ok(Json(serde_json::json!({
-		"m.homeserver": {"base_url": client_url},
-		"org.matrix.msc3575.proxy": {"url": client_url}
-	})))
+	Ok(discover_homeserver::Response {
+		homeserver: HomeserverInfo {
+			base_url: client_url.clone(),
+		},
+		identity_server: None,
+		sliding_sync_proxy: Some(SlidingSyncProxyInfo {
+			url: client_url,
+		}),
+		tile_server: None,
+	})
 }
 
 /// # `GET /.well-known/matrix/support`
 ///
 /// Server support contact and support page of a homeserver's domain.
 pub async fn well_known_support(_body: Ruma<discover_support::Request>) -> Result<discover_support::Response> {
-	let support_page = services().globals.well_known_support_page().clone();
+	let support_page = services()
+		.globals
+		.well_known_support_page()
+		.as_ref()
+		.map(ToString::to_string);
 
 	let role = services().globals.well_known_support_role().clone();
 
@@ -120,9 +133,9 @@ pub async fn well_known_support(_body: Ruma<discover_support::Request>) -> Resul
 /// Web as a non-standard health check.
 pub async fn syncv3_client_server_json() -> Result<impl IntoResponse> {
 	let server_url = match services().globals.well_known_client() {
-		Some(url) => url.clone(),
+		Some(url) => url.to_string(),
 		None => match services().globals.well_known_server() {
-			Some(url) => url.clone(),
+			Some(url) => url.to_string(),
 			None => return Err(Error::BadRequest(ErrorKind::NotFound, "Not found.")),
 		},
 	};
diff --git a/src/api/server_server.rs b/src/api/server_server.rs
index 4faa4747..4fca290c 100644
--- a/src/api/server_server.rs
+++ b/src/api/server_server.rs
@@ -18,7 +18,7 @@ use ruma::{
 			backfill::get_backfill,
 			device::get_devices::{self, v1::UserDevice},
 			directory::{get_public_rooms, get_public_rooms_filtered},
-			discovery::{get_server_keys, get_server_version, ServerSigningKeys, VerifyKey},
+			discovery::{discover_homeserver, get_server_keys, get_server_version, ServerSigningKeys, VerifyKey},
 			event::{get_event, get_missing_events, get_room_state, get_room_state_ids},
 			keys::{claim_keys, get_keys},
 			membership::{create_invite, create_join_event, prepare_join_event},
@@ -1559,19 +1559,19 @@ pub async fn claim_keys_route(body: Ruma<claim_keys::v1::Request>) -> Result<cla
 }
 
 /// # `GET /.well-known/matrix/server`
-pub async fn well_known_server_route() -> Result<impl IntoResponse> {
+///
+/// Returns the .well-known URL if it is configured, otherwise returns 404.
+pub async fn well_known_server(_body: Ruma<discover_homeserver::Request>) -> Result<discover_homeserver::Response> {
 	if !services().globals.allow_federation() {
 		return Err(Error::bad_config("Federation is disabled."));
 	}
 
-	let server_url = match services().globals.well_known_server() {
-		Some(url) => url.clone(),
-		None => return Err(Error::BadRequest(ErrorKind::NotFound, "Not found.")),
-	};
-
-	Ok(Json(serde_json::json!({
-		"m.server": server_url
-	})))
+	Ok(discover_homeserver::Response {
+		server: match services().globals.well_known_server() {
+			Some(server_name) => server_name.to_owned(),
+			None => return Err(Error::BadRequest(ErrorKind::NotFound, "Not found.")),
+		},
+	})
 }
 
 /// # `GET /_matrix/federation/v1/hierarchy/{roomId}`
diff --git a/src/config/mod.rs b/src/config/mod.rs
index d57bdcc8..bfda89fe 100644
--- a/src/config/mod.rs
+++ b/src/config/mod.rs
@@ -20,6 +20,7 @@ use ruma::{
 };
 use serde::{de::IgnoredAny, Deserialize};
 use tracing::{debug, error, warn};
+use url::Url;
 
 use self::proxy::ProxyConfig;
 use crate::utils::error::Error;
@@ -158,8 +159,7 @@ pub struct Config {
 	pub allow_unstable_room_versions: bool,
 	#[serde(default = "default_default_room_version")]
 	pub default_room_version: RoomVersionId,
-	pub well_known_client: Option<String>,
-	pub well_known_server: Option<String>,
+	pub well_known: WellKnownConfig,
 	#[serde(default)]
 	pub allow_jaeger: bool,
 	#[serde(default)]
@@ -264,11 +264,6 @@ pub struct Config {
 	#[serde(default = "default_ip_range_denylist")]
 	pub ip_range_denylist: Vec<String>,
 
-	pub well_known_support_page: Option<String>,
-	pub well_known_support_role: Option<ContactRole>,
-	pub well_known_support_email: Option<String>,
-	pub well_known_support_mxid: Option<OwnedUserId>,
-
 	#[serde(default = "Vec::new")]
 	pub url_preview_domain_contains_allowlist: Vec<String>,
 	#[serde(default = "Vec::new")]
@@ -319,7 +314,25 @@ pub struct TlsConfig {
 	pub dual_protocol: bool,
 }
 
-const DEPRECATED_KEYS: &[&str] = &["cache_capacity"];
+#[derive(Clone, Debug, Deserialize)]
+pub struct WellKnownConfig {
+	pub client: Option<Url>,
+	pub server: Option<OwnedServerName>,
+	pub support_page: Option<Url>,
+	pub support_role: Option<ContactRole>,
+	pub support_email: Option<String>,
+	pub support_mxid: Option<OwnedUserId>,
+}
+
+const DEPRECATED_KEYS: &[&str] = &[
+	"cache_capacity",
+	"well_known_client",
+	"well_known_server",
+	"well_known_support_page",
+	"well_known_support_role",
+	"well_known_support_email",
+	"well_known_support_mxid",
+];
 
 impl Config {
 	/// Initialize config
@@ -367,8 +380,8 @@ impl Config {
 
 		if was_deprecated {
 			warn!(
-				"Read conduit documentation and check your configuration if any new configuration parameters should \
-				 be adjusted"
+				"Read conduwuit config documentation at https://conduwuit.puppyirl.gay/configuration.html and check \
+				 your configuration if any new configuration parameters should be adjusted"
 			);
 		}
 	}
@@ -690,6 +703,46 @@ impl fmt::Display for Config {
 			("Sentry.io send server_name in logs", &self.sentry_send_server_name.to_string()),
 			#[cfg(feature = "sentry_telemetry")]
 			("Sentry.io tracing sample rate", &self.sentry_traces_sample_rate.to_string()),
+			(
+				"Well-known server name",
+				&if let Some(server) = &self.well_known.server {
+					server.to_string()
+				} else {
+					String::new()
+				},
+			),
+			(
+				"Well-known support email",
+				&if let Some(support_email) = &self.well_known.support_email {
+					support_email.to_string()
+				} else {
+					String::new()
+				},
+			),
+			(
+				"Well-known support Matrix ID",
+				&if let Some(support_mxid) = &self.well_known.support_mxid {
+					support_mxid.to_string()
+				} else {
+					String::new()
+				},
+			),
+			(
+				"Well-known support role",
+				&if let Some(support_role) = &self.well_known.support_role {
+					support_role.to_string()
+				} else {
+					String::new()
+				},
+			),
+			(
+				"Well-known support page/URL",
+				&if let Some(support_page) = &self.well_known.support_page {
+					support_page.to_string()
+				} else {
+					String::new()
+				},
+			),
 		];
 
 		let mut msg: String = "Active config values:\n\n".to_owned();
diff --git a/src/routes.rs b/src/routes.rs
index caeeba69..8deaba62 100644
--- a/src/routes.rs
+++ b/src/routes.rs
@@ -208,12 +208,12 @@ pub fn routes() -> Router {
         .ruma_route(server_server::get_hierarchy_route)
         .ruma_route(client_server::get_mutual_rooms_route)
         .ruma_route(client_server::well_known_support)
+        .ruma_route(client_server::well_known_client)
+        .ruma_route(server_server::well_known_server)
         .route("/_conduwuit/server_version", get(client_server::conduwuit_server_version))
 		.route("/_matrix/client/r0/rooms/:room_id/initialSync", get(initial_sync))
 		.route("/_matrix/client/v3/rooms/:room_id/initialSync", get(initial_sync))
 		.route("/client/server.json", get(client_server::syncv3_client_server_json))
-		.route("/.well-known/matrix/client", get(client_server::well_known_client_route))
-		.route("/.well-known/matrix/server", get(server_server::well_known_server_route))
 		.route("/", get(it_works))
 		.fallback(not_found)
 }
diff --git a/src/service/globals/mod.rs b/src/service/globals/mod.rs
index 8c767e9e..a6a4ea40 100644
--- a/src/service/globals/mod.rs
+++ b/src/service/globals/mod.rs
@@ -27,6 +27,7 @@ use ruma::{
 use tokio::sync::{broadcast, watch::Receiver, Mutex, RwLock, Semaphore};
 use tracing::{error, info};
 use tracing_subscriber::{EnvFilter, Registry};
+use url::Url;
 
 use crate::{services, Config, Result};
 
@@ -314,13 +315,13 @@ impl Service<'_> {
 
 	pub fn ip_range_denylist(&self) -> &[String] { &self.config.ip_range_denylist }
 
-	pub fn well_known_support_page(&self) -> &Option<String> { &self.config.well_known_support_page }
+	pub fn well_known_support_page(&self) -> &Option<Url> { &self.config.well_known.support_page }
 
-	pub fn well_known_support_role(&self) -> &Option<ContactRole> { &self.config.well_known_support_role }
+	pub fn well_known_support_role(&self) -> &Option<ContactRole> { &self.config.well_known.support_role }
 
-	pub fn well_known_support_email(&self) -> &Option<String> { &self.config.well_known_support_email }
+	pub fn well_known_support_email(&self) -> &Option<String> { &self.config.well_known.support_email }
 
-	pub fn well_known_support_mxid(&self) -> &Option<OwnedUserId> { &self.config.well_known_support_mxid }
+	pub fn well_known_support_mxid(&self) -> &Option<OwnedUserId> { &self.config.well_known.support_mxid }
 
 	pub fn block_non_admin_invites(&self) -> bool { self.config.block_non_admin_invites }
 
@@ -401,9 +402,9 @@ impl Service<'_> {
 		r
 	}
 
-	pub fn well_known_client(&self) -> &Option<String> { &self.config.well_known_client }
+	pub fn well_known_client(&self) -> &Option<Url> { &self.config.well_known.client }
 
-	pub fn well_known_server(&self) -> &Option<String> { &self.config.well_known_server }
+	pub fn well_known_server(&self) -> &Option<OwnedServerName> { &self.config.well_known.server }
 
 	pub fn unix_socket_path(&self) -> &Option<PathBuf> { &self.config.unix_socket_path }