Skip to main content

tuwunel_api_client/
routes.rs

1use axum::{
2	Router,
3	response::IntoResponse,
4	routing::{MethodRouter, any, get, post, put},
5};
6use const_str::{concat, join, replace};
7use http::{HeaderValue, header};
8use ruma::api::{
9	IncomingRequest,
10	client::{message::send_message_event, state::send_state_event},
11	path_builder::PathBuilder,
12};
13use tower_http::set_header::SetResponseHeaderLayer;
14use tuwunel_api::router::RouterExt;
15use tuwunel_core::{Server, err};
16
17use crate::{
18	self as client, State, put_msc4388_route, send_message_event_or_delayed_route,
19	send_state_event_or_delayed_route, sso_callback_route,
20};
21
22/// Builds the client endpoint routes.
23///
24/// Federation and media settings select the configured route variants.
25pub fn build(server: &Server) -> Router<State> {
26	let config = &server.config;
27	let router = Router::new();
28	let router = register_client_auth_routes(router);
29	let router = register_client_profile_and_data_routes(router);
30	let router = register_client_keys_and_backup_routes(router);
31	let router = register_client_room_routes(router);
32	let router = register_client_state_and_sync_routes(router);
33	let router = register_client_media_and_device_routes(
34		router,
35		config.media.deny_framing,
36		config.media.deny_inline_styles,
37	);
38
39	let router = register_delayed_event_routes(router);
40	let router = register_client_misc_routes(router);
41	let router = register_rendezvous_routes(router);
42	let router = if config.federation.enable {
43		router.route("/_tuwunel/local_user_count", get(client::tuwunel_local_user_count))
44	} else {
45		router
46	};
47
48	register_legacy_media_routes(
49		router,
50		config.media.allow_legacy,
51		config.media.deny_framing,
52		config.media.deny_inline_styles,
53	)
54}
55
56fn register_client_auth_routes(router: Router<State>) -> Router<State> {
57	router
58		.ruma_route(&client::get_supported_versions_route)
59		.ruma_route(&client::get_register_available_route)
60		.ruma_route(&client::register_route)
61		.ruma_route(&client::get_login_types_route)
62		.ruma_route(&client::login_route)
63		.ruma_route(&client::login_token_route)
64		.ruma_route(&client::refresh_token_route)
65		.ruma_route(&client::sso_login_route)
66		.ruma_route(&client::sso_login_with_provider_route)
67		.route("/_matrix/client/unstable/login/sso/callback/{idp_id}", get(sso_callback_route))
68		.ruma_route(&client::sso_fallback_route)
69		.ruma_route(&client::whoami_route)
70		.ruma_route(&client::logout_route)
71		.ruma_route(&client::logout_all_route)
72		.ruma_route(&client::change_password_route)
73		.ruma_route(&client::deactivate_route)
74		.ruma_route(&client::third_party_route)
75		.ruma_route(&client::add_3pid_route)
76		.ruma_route(&client::delete_3pid_route)
77		.ruma_route(&client::request_3pid_management_token_via_email_route)
78		.ruma_route(&client::request_3pid_management_token_via_msisdn_route)
79		.ruma_route(&client::request_registration_token_via_email_route)
80		.ruma_route(&client::request_password_change_token_via_email_route)
81		.ruma_route(&client::check_registration_token_validity)
82		.ruma_route(&client::create_openid_token_route)
83		.route("/_tuwunel/sso/complete.js", get(client::sso_complete_js_route))
84		.route("/_tuwunel/sso/sso.css", get(client::sso_css_route))
85}
86
87fn register_client_profile_and_data_routes(router: Router<State>) -> Router<State> {
88	router
89		.ruma_route(&client::get_profile_field_route)
90		.ruma_route(&client::set_profile_field_route)
91		.ruma_route(&client::delete_profile_field_route)
92		.ruma_route(&client::get_profile_route)
93		.ruma_route(&client::set_presence_route)
94		.ruma_route(&client::get_presence_route)
95		.ruma_route(&client::get_filter_route)
96		.ruma_route(&client::create_filter_route)
97		.ruma_route(&client::set_global_account_data_route)
98		.ruma_route(&client::set_room_account_data_route)
99		.ruma_route(&client::get_global_account_data_route)
100		.ruma_route(&client::get_room_account_data_route)
101		.ruma_route(&client::delete_global_account_data_route)
102		.ruma_route(&client::delete_room_account_data_route)
103		.ruma_route(&client::get_tags_route)
104		.ruma_route(&client::update_tag_route)
105		.ruma_route(&client::delete_tag_route)
106		.ruma_route(&client::get_pushrules_all_route)
107		.ruma_route(&client::get_pushrules_global_route)
108		.ruma_route(&client::set_pushrule_route)
109		.ruma_route(&client::get_pushrule_route)
110		.ruma_route(&client::set_pushrule_enabled_route)
111		.ruma_route(&client::get_pushrule_enabled_route)
112		.ruma_route(&client::get_pushrule_actions_route)
113		.ruma_route(&client::set_pushrule_actions_route)
114		.ruma_route(&client::delete_pushrule_route)
115		.ruma_route(&client::get_pushers_route)
116		.ruma_route(&client::set_pushers_route)
117		.ruma_route(&client::get_notifications_route)
118		.ruma_route(&client::get_capabilities_route)
119}
120
121fn register_client_keys_and_backup_routes(router: Router<State>) -> Router<State> {
122	router
123		.ruma_route(&client::upload_keys_route)
124		.ruma_route(&client::get_keys_route)
125		.ruma_route(&client::claim_keys_route)
126		.ruma_route(&client::upload_signing_keys_route)
127		.ruma_route(&client::upload_signatures_route)
128		.ruma_route(&client::get_key_changes_route)
129		.ruma_route(&client::create_backup_version_route)
130		.ruma_route(&client::update_backup_version_route)
131		.ruma_route(&client::delete_backup_version_route)
132		.ruma_route(&client::get_latest_backup_info_route)
133		.ruma_route(&client::get_backup_info_route)
134		.ruma_route(&client::add_backup_keys_route)
135		.ruma_route(&client::add_backup_keys_for_room_route)
136		.ruma_route(&client::add_backup_keys_for_session_route)
137		.ruma_route(&client::delete_backup_keys_for_room_route)
138		.ruma_route(&client::delete_backup_keys_for_session_route)
139		.ruma_route(&client::delete_backup_keys_route)
140		.ruma_route(&client::get_backup_keys_for_room_route)
141		.ruma_route(&client::get_backup_keys_for_session_route)
142		.ruma_route(&client::get_backup_keys_route)
143}
144
145fn register_client_room_routes(router: Router<State>) -> Router<State> {
146	router
147		.ruma_route(&client::appservice_ping)
148		.ruma_route(&client::set_read_marker_route)
149		.ruma_route(&client::create_receipt_route)
150		.ruma_route(&client::create_typing_event_route)
151		.ruma_route(&client::create_room_route)
152		.ruma_route(&client::redact_event_route)
153		.ruma_route(&client::report_event_route)
154		.ruma_route(&client::report_room_route)
155		.ruma_route(&client::report_user_route)
156		.ruma_route(&client::create_alias_route)
157		.ruma_route(&client::delete_alias_route)
158		.ruma_route(&client::get_alias_route)
159		.ruma_route(&client::join_room_by_id_route)
160		.ruma_route(&client::join_room_by_id_or_alias_route)
161		.ruma_route(&client::joined_members_route)
162		.ruma_route(&client::knock_room_route)
163		.ruma_route(&client::leave_room_route)
164		.ruma_route(&client::forget_room_route)
165		.ruma_route(&client::joined_rooms_route)
166		.ruma_route(&client::kick_user_route)
167		.ruma_route(&client::ban_user_route)
168		.ruma_route(&client::unban_user_route)
169		.ruma_route(&client::invite_user_route)
170		.ruma_route(&client::set_room_visibility_route)
171		.ruma_route(&client::get_room_visibility_route)
172		.ruma_route(&client::get_public_rooms_route)
173		.ruma_route(&client::get_public_rooms_filtered_route)
174		.ruma_route(&client::search_users_route)
175		.ruma_route(&client::get_member_events_route)
176		.ruma_route(&client::get_protocols_route)
177		.ruma_route(&client::get_protocol_route)
178		.ruma_route(&client::get_user_for_protocol_route)
179		.ruma_route(&client::get_location_for_protocol_route)
180		.ruma_route(&client::get_user_for_user_id_route)
181		.ruma_route(&client::get_location_for_room_alias_route)
182		.ruma_route(&client::upgrade_room_route)
183		.ruma_route(&client::get_mutual_rooms_route)
184		.ruma_route(&client::get_room_summary)
185		.route(
186			"/_matrix/client/unstable/im.nheko.summary/rooms/{room_id_or_alias}/summary",
187			get(client::get_room_summary_legacy),
188		)
189		.ruma_route(&client::room_initial_sync_route)
190		.ruma_route(&client::get_room_event_route)
191		.ruma_route(&client::get_room_aliases_route)
192}
193
194fn register_client_state_and_sync_routes(router: Router<State>) -> Router<State> {
195	// MSC4140: these paths also schedule an event under a query parameter, and
196	// a scheduling answers with a different response type than the send.
197	let router = route_paths::<send_message_event::v3::Request>(
198		router,
199		&put(send_message_event_or_delayed_route),
200	);
201
202	let router = route_paths::<send_state_event::v3::Request>(
203		router,
204		&put(send_state_event_or_delayed_route),
205	);
206
207	router
208		.ruma_route(&client::get_state_events_route)
209		.ruma_route(&client::get_state_events_for_key_route)
210		// Ruma doesn't have support for multiple paths for a single endpoint yet, and these
211		// routes share one Ruma request / response type pair with
212		// {get,send}_state_event_for_key_route
213		.route(
214			"/_matrix/client/r0/rooms/{room_id}/state/{event_type}",
215			get(client::get_state_events_for_empty_key_route)
216				.put(send_state_event_or_delayed_route),
217		)
218		.route(
219			"/_matrix/client/v3/rooms/{room_id}/state/{event_type}",
220			get(client::get_state_events_for_empty_key_route)
221				.put(send_state_event_or_delayed_route),
222		)
223		// These two endpoints allow trailing slashes
224		.route(
225			"/_matrix/client/r0/rooms/{room_id}/state/{event_type}/",
226			get(client::get_state_events_for_empty_key_route)
227				.put(send_state_event_or_delayed_route),
228		)
229		.route(
230			"/_matrix/client/v3/rooms/{room_id}/state/{event_type}/",
231			get(client::get_state_events_for_empty_key_route)
232				.put(send_state_event_or_delayed_route),
233		)
234		.ruma_route(&client::events_route)
235		.ruma_route(&client::sync_events_route)
236		.ruma_route(&client::sync_events_v5_route)
237		.ruma_route(&client::get_context_route)
238		.ruma_route(&client::get_event_by_timestamp_route)
239		.ruma_route(&client::get_message_events_route)
240		.ruma_route(&client::search_events_route)
241		.ruma_route(&client::get_threads_route)
242		.ruma_route(&client::get_relating_events_with_rel_type_and_event_type_route)
243		.ruma_route(&client::get_relating_events_with_rel_type_route)
244		.ruma_route(&client::get_relating_events_route)
245		.ruma_route(&client::get_hierarchy_route)
246}
247
248/// Registers one handler at every path a Ruma request type declares.
249///
250/// It serves a handler that answers with something other than the type's own
251/// response, which `ruma_route` cannot register.
252fn route_paths<Req: IncomingRequest>(
253	router: Router<State>,
254	handler: &MethodRouter<State>,
255) -> Router<State> {
256	Req::PATH_BUILDER
257		.all_paths()
258		.fold(router, |router, path| router.route(path, handler.clone()))
259}
260
261fn register_client_media_and_device_routes(
262	router: Router<State>,
263	media_deny_framing: bool,
264	media_deny_inline_styles: bool,
265) -> Router<State> {
266	let media_content_router = Router::new()
267		.ruma_route(&client::get_content_thumbnail_route)
268		.ruma_route(&client::get_content_route)
269		.ruma_route(&client::get_content_as_filename_route);
270
271	let media_content_router =
272		media_content_headers(media_content_router, media_deny_framing, media_deny_inline_styles);
273
274	router
275		.ruma_route(&client::create_content_route)
276		.ruma_route(&client::create_mxc_uri_route)
277		.ruma_route(&client::create_content_async_route)
278		.ruma_route(&client::get_media_preview_route)
279		.ruma_route(&client::get_media_config_route)
280		.ruma_route(&client::get_devices_route)
281		.ruma_route(&client::get_device_route)
282		.ruma_route(&client::update_device_route)
283		.ruma_route(&client::delete_device_route)
284		.ruma_route(&client::delete_devices_route)
285		.ruma_route(&client::put_dehydrated_device_route)
286		.ruma_route(&client::delete_dehydrated_device_route)
287		.ruma_route(&client::get_dehydrated_device_route)
288		.ruma_route(&client::get_dehydrated_events_route)
289		.ruma_route(&client::get_dehydrated_events_by_body_route)
290		.ruma_route(&client::send_event_to_device_route)
291		.merge(media_content_router)
292}
293
294fn register_delayed_event_routes(router: Router<State>) -> Router<State> {
295	router
296		.ruma_route(&client::send_delayed_event_route)
297		.ruma_route(&client::update_delayed_event_route)
298		.ruma_route(&client::update_delayed_event_by_body_route)
299		.ruma_route(&client::get_delayed_event_route)
300		.ruma_route(&client::get_delayed_events_route)
301}
302
303fn register_client_misc_routes(router: Router<State>) -> Router<State> {
304	router
305		.ruma_route(&client::turn_server_route)
306		.ruma_route(&client::get_transports_route)
307		.ruma_route(&client::well_known_support)
308		.ruma_route(&client::well_known_client)
309		.ruma_route(&client::tuwunel_remote_version)
310		.route("/_tuwunel/server_version", get(client::tuwunel_server_version))
311		.route(
312			"/_tuwunel/3pid/email/validate",
313			get(client::get_email_validate_route).post(client::post_email_validate_route),
314		)
315}
316
317fn register_rendezvous_routes(router: Router<State>) -> Router<State> {
318	let router = router
319		.ruma_route(&client::discover_msc4388_route)
320		.ruma_route(&client::create_msc4388_route)
321		.ruma_route(&client::get_msc4388_route)
322		.ruma_route(&client::delete_msc4388_route)
323		.route(
324			"/_matrix/client/unstable/io.element.msc4388/rendezvous/{id}/{txn_id}",
325			put(put_msc4388_route),
326		);
327
328	let session_routes = get(client::get_rendezvous_route)
329		.put(client::put_rendezvous_route)
330		.delete(client::delete_rendezvous_route);
331
332	router
333		.route(
334			"/_matrix/client/unstable/org.matrix.msc4108/rendezvous",
335			post(client::create_rendezvous_route),
336		)
337		.route("/_matrix/client/unstable/org.matrix.msc4108/rendezvous/{id}", session_routes)
338}
339
340fn register_legacy_media_routes(
341	router: Router<State>,
342	allow_legacy_media: bool,
343	media_deny_framing: bool,
344	media_deny_inline_styles: bool,
345) -> Router<State> {
346	if allow_legacy_media {
347		let media_content_router = Router::new()
348			.route(
349				"/_matrix/media/r0/download/{server_name}/{media_id}",
350				get(client::get_content_legacy_route),
351			)
352			.route(
353				"/_matrix/media/v3/download/{server_name}/{media_id}",
354				get(client::get_content_legacy_route),
355			)
356			.route(
357				"/_matrix/media/r0/download/{server_name}/{media_id}/{filename}",
358				get(client::get_content_as_filename_legacy_route),
359			)
360			.route(
361				"/_matrix/media/v3/download/{server_name}/{media_id}/{filename}",
362				get(client::get_content_as_filename_legacy_route),
363			)
364			.route(
365				"/_matrix/media/r0/thumbnail/{server_name}/{media_id}",
366				get(client::get_content_thumbnail_legacy_route),
367			)
368			.route(
369				"/_matrix/media/v3/thumbnail/{server_name}/{media_id}",
370				get(client::get_content_thumbnail_legacy_route),
371			);
372
373		let media_content_router = media_content_headers(
374			media_content_router,
375			media_deny_framing,
376			media_deny_inline_styles,
377		);
378
379		router
380			.ruma_route(&client::get_media_config_legacy_route)
381			.ruma_route(&client::get_media_preview_legacy_route)
382			.merge(media_content_router)
383	} else {
384		router
385			.route("/_matrix/media/v3/config", any(legacy_media_disabled))
386			.route("/_matrix/media/v3/download/{*path}", any(legacy_media_disabled))
387			.route("/_matrix/media/v3/thumbnail/{*path}", any(legacy_media_disabled))
388			.route("/_matrix/media/v3/preview_url", any(legacy_media_disabled))
389	}
390}
391
392fn media_content_headers(
393	router: Router<State>,
394	deny_framing: bool,
395	deny_inline_styles: bool,
396) -> Router<State> {
397	// The MSC4149 media policy, stricter than the spec's recommendation.
398	const MEDIA_CSP: &[&str] = &[
399		"sandbox",
400		"default-src 'none'",
401		"script-src 'none'",
402		"font-src 'none'",
403		"frame-ancestors 'none'",
404		"form-action 'none'",
405		"base-uri 'none'",
406	];
407
408	const POLICY: &str = join!(MEDIA_CSP, ";");
409	const FRAMING_POLICY: &str = replace!(POLICY, "frame-ancestors 'none';", "");
410	const STYLED_POLICY: &str = concat!(POLICY, ";style-src 'unsafe-inline'");
411	const FRAMING_STYLED_POLICY: &str = concat!(FRAMING_POLICY, ";style-src 'unsafe-inline'");
412
413	let policy = match (deny_framing, deny_inline_styles) {
414		| (false, false) => FRAMING_STYLED_POLICY,
415		| (false, true) => FRAMING_POLICY,
416		| (true, false) => STYLED_POLICY,
417		| (true, true) => POLICY,
418	};
419
420	router.route_layer(SetResponseHeaderLayer::overriding(
421		header::CONTENT_SECURITY_POLICY,
422		HeaderValue::from_static(policy),
423	))
424}
425
426async fn legacy_media_disabled() -> impl IntoResponse {
427	err!(Request(Forbidden("Unauthenticated media is disabled.")))
428}