Skip to main content

tuwunel_core/config/
well_known.rs

1//! Converts discovery configuration into Matrix API values.
2//!
3//! Helpers build support contacts, policies, registration terms, and MatrixRTC
4//! transports from config. Endpoint handlers share these conversions.
5
6use std::collections::BTreeMap;
7
8use ruma::api::{
9	client::{
10		discovery::discover_support::Contact,
11		rtc::RtcTransport,
12		uiaa::{LoginTermsParams, PolicyDefinition, PolicyTranslation},
13	},
14	identity_service::tos::get_terms_of_service::v2::{LocalizedPolicy, Policies},
15};
16use tuwunel_macros::implement;
17
18use crate::{Result, err, error::inspect_log};
19
20/// Builds the support contacts advertised through well-known discovery.
21///
22/// Named contact entries are converted first. Legacy single-contact fields
23/// append one additional contact when a support role is configured.
24#[implement(super::WellKnownConfig)]
25pub fn get_contacts(&self) -> Vec<Contact> {
26	let single_contact = self.support_role.clone().map(|role| Contact {
27		role,
28		email_address: self.support_email.clone(),
29		matrix_id: self.support_mxid.clone(),
30		pgp_key: self.support_pgp_key.clone(),
31	});
32
33	let contacts = self
34		.support_contact
35		.clone()
36		.into_values()
37		.map(Into::into);
38
39	contacts.chain(single_contact).collect()
40}
41
42/// Builds the localized support policies advertised through discovery.
43///
44/// Outer map keys remain policy identifiers. Each configured language entry is
45/// converted into the corresponding Matrix policy representation.
46#[implement(super::WellKnownConfig)]
47#[must_use]
48pub fn get_policies(&self) -> BTreeMap<String, Policies> {
49	self.support_policy
50		.iter()
51		.map(|(id, policy)| {
52			let localized = policy
53				.policy_translation
54				.iter()
55				.map(|(language, translation)| {
56					(language.clone(), LocalizedPolicy::from(translation.clone()))
57				})
58				.collect();
59
60			(id.clone(), Policies {
61				version: policy.version.clone(),
62				localized,
63			})
64		})
65		.collect()
66}
67
68/// Builds registration terms parameters from configured policy documents.
69///
70/// Policy identifiers and language keys are preserved in the Matrix UIA shape.
71/// An empty policy map returns `None` and does not require a terms stage.
72#[implement(super::Config)]
73#[must_use]
74pub fn login_terms_params(&self) -> Option<LoginTermsParams> {
75	let policies: BTreeMap<_, _> = self
76		.registration_terms
77		.iter()
78		.map(|(id, policy)| {
79			let translations = policy
80				.translations
81				.iter()
82				.map(|(language, translation)| {
83					let translation = PolicyTranslation::new(
84						translation.name.clone(),
85						translation.url.to_string(),
86					);
87
88					(language.clone(), translation)
89				})
90				.collect();
91
92			(id.clone(), PolicyDefinition::new(policy.version.clone(), translations))
93		})
94		.collect();
95
96	(!policies.is_empty()).then(|| LoginTermsParams::new(policies))
97}
98
99/// Build the configured RTC transports as `RtcTransport` values, the typed
100/// form shared between `.well-known/matrix/client.rtc_foci` and the
101/// `/rtc/transports` endpoint.
102#[implement(super::WellKnownConfig)]
103pub fn get_transports(&self) -> Result<Vec<RtcTransport>> {
104	let custom = self.rtc_transports.iter().map(|item| {
105		let mut data = item
106			.as_object()
107			.cloned()
108			.ok_or_else(|| err!("`rtc_transport` is not a valid object"))?;
109
110		let transport_type = data
111			.remove("type")
112			.and_then(|v| v.as_str().map(str::to_owned))
113			.ok_or_else(|| err!("`type` is not a valid string"))?;
114
115		RtcTransport::new(&transport_type, data).map_err(|e| {
116			err!(Config("global.well_known.rtc_transports", "Malformed value(s): {e:?}"))
117		})
118	});
119
120	let livekit_url = self
121		.livekit_url
122		.iter()
123		.cloned()
124		.map(|url| Ok(RtcTransport::livekit(url)));
125
126	custom
127		.chain(livekit_url)
128		.collect::<Result<Vec<_>>>()
129		.inspect_err(inspect_log)
130}