Skip to main content

tuwunel_api/client/
alias.rs

1use axum::extract::State;
2use futures::StreamExt;
3use rand::seq::SliceRandom;
4use ruma::{
5	OwnedServerName, RoomAliasId, RoomId,
6	api::client::alias::{create_alias, delete_alias, get_alias},
7};
8use tuwunel_core::{Err, Result, debug, err};
9use tuwunel_service::Services;
10
11use crate::Ruma;
12
13/// # `PUT /_matrix/client/v3/directory/room/{roomAlias}`
14///
15/// Creates a new room alias on this server.
16pub(crate) async fn create_alias_route(
17	State(services): State<crate::State>,
18	body: Ruma<create_alias::v3::Request>,
19) -> Result<create_alias::v3::Response> {
20	let sender_user = body.sender_user();
21	services
22		.alias
23		.appservice_checks(&body.room_alias, &body.appservice_info)
24		.await?;
25
26	// this isn't apart of alias_checks or delete alias route because we should
27	// allow removing forbidden room aliases
28	if services
29		.config
30		.forbidden_alias_names
31		.is_match(body.room_alias.alias())
32	{
33		return Err!(Request(Forbidden("Room alias is forbidden.")));
34	}
35
36	if services
37		.alias
38		.resolve_local_alias(&body.room_alias)
39		.await
40		.is_ok()
41	{
42		return Err!(Conflict("Alias already exists."));
43	}
44
45	services
46		.alias
47		.set_alias_by(&body.room_alias, &body.room_id, sender_user)?;
48
49	Ok(create_alias::v3::Response::new())
50}
51
52/// # `DELETE /_matrix/client/v3/directory/room/{roomAlias}`
53///
54/// Deletes a room alias from this server.
55///
56/// - TODO: Update canonical alias event
57pub(crate) async fn delete_alias_route(
58	State(services): State<crate::State>,
59	body: Ruma<delete_alias::v3::Request>,
60) -> Result<delete_alias::v3::Response> {
61	let sender_user = body.sender_user();
62	services
63		.alias
64		.appservice_checks(&body.room_alias, &body.appservice_info)
65		.await?;
66
67	services
68		.alias
69		.remove_alias_by(&body.room_alias, sender_user)
70		.await?;
71
72	// TODO: update alt_aliases?
73
74	Ok(delete_alias::v3::Response::new())
75}
76
77/// # `GET /_matrix/client/v3/directory/room/{roomAlias}`
78///
79/// Resolve an alias locally or over federation.
80pub(crate) async fn get_alias_route(
81	State(services): State<crate::State>,
82	body: Ruma<get_alias::v3::Request>,
83) -> Result<get_alias::v3::Response> {
84	let room_alias = body.body.room_alias;
85
86	let (room_id, servers) = services
87		.alias
88		.resolve_alias(&room_alias)
89		.await
90		.map_err(|_| err!(Request(NotFound("Room with alias not found."))))?;
91
92	let servers = room_available_servers(&services, &room_id, &room_alias, servers).await;
93	debug!(?room_alias, ?room_id, "available servers: {servers:?}");
94
95	Ok(get_alias::v3::Response::new(room_id, servers))
96}
97
98async fn room_available_servers(
99	services: &Services,
100	room_id: &RoomId,
101	room_alias: &RoomAliasId,
102	pre_servers: Vec<OwnedServerName>,
103) -> Vec<OwnedServerName> {
104	// find active servers in room state cache to suggest
105	let mut servers: Vec<OwnedServerName> = services
106		.state_cache
107		.room_servers(room_id)
108		.map(ToOwned::to_owned)
109		.collect()
110		.await;
111
112	// push any servers we want in the list already (e.g. responded remote alias
113	// servers, room alias server itself)
114	servers.extend(pre_servers);
115
116	servers.sort_unstable();
117	servers.dedup();
118
119	// shuffle list of servers randomly after sort and dedupe
120	servers.shuffle(&mut rand::rng());
121
122	// insert our server as the very first choice if in list, else check if we can
123	// prefer the room alias server first
124	if let Some(server_index) = servers
125		.iter()
126		.position(|server_name| services.globals.server_is_ours(server_name))
127	{
128		servers.swap(0, server_index);
129	} else if let Some(alias_server_index) = servers
130		.iter()
131		.position(|server| server == room_alias.server_name())
132	{
133		servers.swap(0, alias_server_index);
134	}
135
136	servers
137}