Skip to main content

tuwunel_api/client/room/
create.rs

1use std::collections::BTreeMap;
2
3use axum::extract::State;
4use futures::{FutureExt, StreamExt};
5use itertools::Itertools;
6use ruma::{
7	CanonicalJsonObject, EventEncryptionAlgorithm, Int, OwnedRoomAliasId, OwnedRoomId,
8	OwnedUserId, RoomAliasId, RoomId, RoomVersionId, UserId,
9	api::client::room::{
10		self,
11		create_room::{
12			self, RoomPowerLevelsContentOverride,
13			v3::{CreationContent, RoomPreset},
14		},
15	},
16	events::{
17		StateEventType, TimelineEventType,
18		room::{
19			canonical_alias::RoomCanonicalAliasEventContent,
20			create::RoomCreateEventContent,
21			encryption::RoomEncryptionEventContent,
22			guest_access::{GuestAccess, RoomGuestAccessEventContent},
23			history_visibility::{HistoryVisibility, RoomHistoryVisibilityEventContent},
24			join_rules::{JoinRule, RoomJoinRulesEventContent},
25			member::{MembershipState, RoomMemberEventContent},
26			name::RoomNameEventContent,
27			power_levels::RoomPowerLevelsEventContent,
28			topic::RoomTopicEventContent,
29		},
30	},
31	int,
32	room_version_rules::{RoomIdFormatVersion, RoomVersionRules},
33	serde::{JsonObject, Raw},
34};
35use serde::Deserialize;
36use serde_json::{json, value::to_raw_value};
37use tuwunel_core::{
38	Err, Result, debug_info, debug_warn, err, info,
39	matrix::{
40		StateKey,
41		pdu::{Content, PduBuilder},
42		room_version,
43	},
44	utils::{BoolExt, IterStream, ReadyExt, option::OptionExt},
45	warn,
46};
47use tuwunel_service::{Services, appservice::RegistrationInfo, rooms::state::RoomMutexGuard};
48
49use crate::{Ruma, client::utils::invite_check};
50
51pub(crate) async fn create_room_route(
52	State(services): State<crate::State>,
53	body: Ruma<create_room::v3::Request>,
54) -> Result<create_room::v3::Response> {
55	can_create_room_check(&services, &body).await?;
56	can_publish_directory_check(&services, &body).await?;
57
58	// Figure out preset. We need it for preset specific events
59	let preset = body
60		.preset
61		.clone()
62		.unwrap_or(match &body.visibility {
63			| room::Visibility::Public => RoomPreset::PublicChat,
64			| _ => RoomPreset::PrivateChat, // Room visibility should not be custom
65		});
66
67	// Determine room version
68	let (room_version, version_rules) = body
69		.room_version
70		.as_ref()
71		.map_or(Ok(&services.server.config.default_room_version), |version| {
72			services
73				.config
74				.supported_room_version(version)
75				.then_ok_or_else(version, || {
76					err!(Request(UnsupportedRoomVersion(
77						"This server does not support room version {version:?}"
78					)))
79				})
80		})
81		.and_then(|version| Ok((version, room_version::rules(version)?)))?;
82
83	// Error on existing alias before committing to creation.
84	let alias = body
85		.room_alias_name
86		.as_ref()
87		.map_async(|alias| room_alias_check(&services, alias, body.appservice_info.as_ref()))
88		.await
89		.transpose()?;
90
91	// Increment and hold the counter; the room will sync atomically to clients
92	// which is preferable.
93	let next_count = services.globals.next_count();
94
95	// 1. Create the create event.
96	let (room_id, state_lock) = match version_rules.room_id_format {
97		| RoomIdFormatVersion::V1 =>
98			create_create_event_legacy(&services, &body, room_version, &version_rules).await?,
99		| RoomIdFormatVersion::V2 =>
100			create_create_event(&services, &body, &preset, room_version, &version_rules)
101				.await
102				.map_err(|e| {
103					err!(Request(InvalidParam("Error while creating m.room.create event: {e}")))
104				})?,
105	};
106
107	let sender_user = body.sender_user();
108
109	// 2. Let the room creator join
110	apply_creator_join_pdu(&services, &body, sender_user, &room_id, &state_lock)
111		.boxed()
112		.await?;
113
114	// 3. Power levels
115	apply_power_levels_pdu(
116		&services,
117		&body,
118		&preset,
119		&version_rules,
120		sender_user,
121		&room_id,
122		&state_lock,
123	)
124	.boxed()
125	.await?;
126
127	// 4. Canonical room alias
128	if let Some(room_alias_id) = &alias {
129		apply_canonical_alias_pdu(&services, room_alias_id, sender_user, &room_id, &state_lock)
130			.boxed()
131			.await?;
132	}
133
134	// 5. Events set by preset
135	let initial_state =
136		apply_preset_state_pdus(&services, &body, &preset, sender_user, &room_id, &state_lock)
137			.boxed()
138			.await?;
139
140	// 6. Events listed in initial_state
141	apply_initial_state_pdus(
142		&services,
143		initial_state,
144		&preset,
145		sender_user,
146		&room_id,
147		&state_lock,
148	)
149	.boxed()
150	.await?;
151
152	// 7. Events implied by name and topic
153	apply_name_and_topic_pdus(&services, &body, sender_user, &room_id, &state_lock)
154		.boxed()
155		.await?;
156
157	drop(next_count);
158	drop(state_lock);
159
160	// if inviting anyone with room creation and invite check passes
161	if (!body.invite.is_empty() || !body.invite_3pid.is_empty())
162		&& invite_check(&services, sender_user, &room_id)
163			.await
164			.is_ok()
165	{
166		process_invites(&services, &body, sender_user, &room_id)
167			.boxed()
168			.await;
169	}
170
171	finalize_alias_and_directory(&services, &body, alias.as_deref(), sender_user, &room_id)
172		.await?;
173
174	info!("{sender_user} created a room with room ID {room_id}");
175
176	Ok(create_room::v3::Response::new(room_id))
177}
178
179async fn apply_creator_join_pdu(
180	services: &Services,
181	body: &Ruma<create_room::v3::Request>,
182	sender_user: &UserId,
183	room_id: &RoomId,
184	state_lock: &RoomMutexGuard,
185) -> Result {
186	let mut content = RoomMemberEventContent {
187		is_direct: Some(body.is_direct),
188		..RoomMemberEventContent::new(MembershipState::Join)
189	};
190
191	services
192		.profile
193		.fill_profile_data(sender_user, &mut content)
194		.await;
195
196	services
197		.timeline
198		.build_and_append_pdu(
199			PduBuilder::state(sender_user.to_string(), &content),
200			sender_user,
201			room_id,
202			state_lock,
203		)
204		.await
205		.map(|_| ())
206}
207
208async fn apply_power_levels_pdu(
209	services: &Services,
210	body: &Ruma<create_room::v3::Request>,
211	preset: &RoomPreset,
212	version_rules: &RoomVersionRules,
213	sender_user: &UserId,
214	room_id: &RoomId,
215	state_lock: &RoomMutexGuard,
216) -> Result {
217	let users =
218		build_power_levels_users(services, body, preset, version_rules, sender_user).await;
219
220	let power_levels_content = default_power_levels_content(
221		version_rules,
222		body.power_level_content_override.as_ref(),
223		preset,
224		users,
225	)?;
226
227	services
228		.timeline
229		.build_and_append_pdu(
230			PduBuilder {
231				event_type: TimelineEventType::RoomPowerLevels,
232				content: to_raw_value(&power_levels_content)?.into(),
233				state_key: Some(StateKey::new()),
234				..Default::default()
235			},
236			sender_user,
237			room_id,
238			state_lock,
239		)
240		.await
241		.map(|_| ())
242}
243
244async fn build_power_levels_users(
245	services: &Services,
246	body: &Ruma<create_room::v3::Request>,
247	preset: &RoomPreset,
248	version_rules: &RoomVersionRules,
249	sender_user: &UserId,
250) -> BTreeMap<OwnedUserId, Int> {
251	let seed = version_rules
252		.authorization
253		.explicitly_privilege_room_creators
254		.or(|| (sender_user.to_owned(), int!(100)))
255		.into_iter()
256		.collect::<BTreeMap<_, _>>();
257
258	let trusted_invitees = *preset == RoomPreset::TrustedPrivateChat
259		&& !version_rules
260			.authorization
261			.additional_room_creators;
262
263	if !trusted_invitees {
264		return seed;
265	}
266
267	body.invite
268		.iter()
269		.stream()
270		.filter(|&invite| invite_allowed(services, sender_user, invite))
271		.ready_fold(seed, |mut users, invite| {
272			users.insert(invite.clone(), int!(100));
273			users
274		})
275		.await
276}
277
278async fn apply_canonical_alias_pdu(
279	services: &Services,
280	room_alias_id: &RoomAliasId,
281	sender_user: &UserId,
282	room_id: &RoomId,
283	state_lock: &RoomMutexGuard,
284) -> Result {
285	services
286		.timeline
287		.build_and_append_pdu(
288			PduBuilder::state(String::new(), &RoomCanonicalAliasEventContent {
289				alias: Some(room_alias_id.to_owned()),
290				alt_aliases: vec![],
291			}),
292			sender_user,
293			room_id,
294			state_lock,
295		)
296		.await
297		.map(|_| ())
298}
299
300async fn apply_preset_state_pdus(
301	services: &Services,
302	body: &Ruma<create_room::v3::Request>,
303	preset: &RoomPreset,
304	sender_user: &UserId,
305	room_id: &RoomId,
306	state_lock: &RoomMutexGuard,
307) -> Result<Vec<InitialEvent>> {
308	let mut initial_state = body
309		.initial_state
310		.iter()
311		.map(|state| Ok(state.deserialize_as_unchecked::<InitialEvent>()?))
312		.filter_ok(|event| {
313			services.config.allow_encryption || event.event_type != StateEventType::RoomEncryption
314		})
315		.filter_ok(|event| {
316			// client/appservice workaround: if a user sends an initial_state event with a
317			// state event in there with the content of literally `{}` (not null or empty
318			// string), let's just skip it over and warn.
319			if event.content.json().get() == "{}" {
320				debug_warn!("skipping empty initial state event of type {}", event.event_type);
321				false
322			} else {
323				true
324			}
325		})
326		.filter_ok(|event| body.name.is_none() || event.event_type != StateEventType::RoomName)
327		.filter_ok(|event| body.topic.is_none() || event.event_type != StateEventType::RoomTopic)
328		.collect::<Result<Vec<_>>>()?;
329
330	let join_rule_pdubuilder =
331		take_initial(&mut initial_state, &StateEventType::RoomJoinRules, "")
332			.map(Into::into)
333			.unwrap_or_else(|| {
334				PduBuilder::state(
335					String::new(),
336					&RoomJoinRulesEventContent::new(match preset {
337						| RoomPreset::PublicChat => JoinRule::Public,
338						// according to spec "invite" is the default
339						| _ => JoinRule::Invite,
340					}),
341				)
342			});
343
344	let history_visibility_pdubuilder =
345		take_initial(&mut initial_state, &StateEventType::RoomHistoryVisibility, "")
346			.map(Into::into)
347			.unwrap_or_else(|| {
348				PduBuilder::state(
349					String::new(),
350					&RoomHistoryVisibilityEventContent::new(HistoryVisibility::Shared),
351				)
352			});
353
354	let guest_access_pdubuilder =
355		take_initial(&mut initial_state, &StateEventType::RoomGuestAccess, "")
356			.map(Into::into)
357			.unwrap_or_else(|| {
358				PduBuilder::state(
359					String::new(),
360					&RoomGuestAccessEventContent::new(match preset {
361						| RoomPreset::PublicChat => GuestAccess::Forbidden,
362						| _ => GuestAccess::CanJoin,
363					}),
364				)
365			});
366
367	// 5.1 Join Rules
368	services
369		.timeline
370		.build_and_append_pdu(join_rule_pdubuilder, sender_user, room_id, state_lock)
371		.boxed()
372		.await?;
373
374	// 5.2 History Visibility
375	services
376		.timeline
377		.build_and_append_pdu(history_visibility_pdubuilder, sender_user, room_id, state_lock)
378		.boxed()
379		.await?;
380
381	// 5.3 Guest Access
382	services
383		.timeline
384		.build_and_append_pdu(guest_access_pdubuilder, sender_user, room_id, state_lock)
385		.boxed()
386		.await?;
387
388	Ok(initial_state)
389}
390
391async fn apply_initial_state_pdus(
392	services: &Services,
393	initial_state: Vec<InitialEvent>,
394	preset: &RoomPreset,
395	sender_user: &UserId,
396	room_id: &RoomId,
397	state_lock: &RoomMutexGuard,
398) -> Result {
399	let is_encrypted = initial_state
400		.iter()
401		.any(|event| event.event_type == StateEventType::RoomEncryption);
402
403	for event in initial_state {
404		services
405			.timeline
406			.build_and_append_pdu(event.into(), sender_user, room_id, state_lock)
407			.boxed()
408			.await?;
409	}
410
411	if !services.config.allow_encryption || is_encrypted {
412		return Ok(());
413	}
414
415	let config = services
416		.config
417		.encryption_enabled_by_default_for_room_type
418		.as_deref();
419
420	let should_encrypt = match config {
421		| Some("all") => true,
422		| Some("invite") =>
423			matches!(preset, RoomPreset::PrivateChat | RoomPreset::TrustedPrivateChat),
424		| _ => false,
425	};
426
427	if !should_encrypt {
428		return Ok(());
429	}
430
431	let algorithm = EventEncryptionAlgorithm::MegolmV1AesSha2;
432	let content = RoomEncryptionEventContent::new(algorithm);
433	services
434		.timeline
435		.build_and_append_pdu(
436			PduBuilder::state(String::new(), &content),
437			sender_user,
438			room_id,
439			state_lock,
440		)
441		.boxed()
442		.await?;
443
444	Ok(())
445}
446
447async fn apply_name_and_topic_pdus(
448	services: &Services,
449	body: &Ruma<create_room::v3::Request>,
450	sender_user: &UserId,
451	room_id: &RoomId,
452	state_lock: &RoomMutexGuard,
453) -> Result {
454	if let Some(name) = &body.name {
455		services
456			.timeline
457			.build_and_append_pdu(
458				PduBuilder::state(String::new(), &RoomNameEventContent::new(name.clone())),
459				sender_user,
460				room_id,
461				state_lock,
462			)
463			.boxed()
464			.await?;
465	}
466
467	if let Some(topic) = &body.topic {
468		services
469			.timeline
470			.build_and_append_pdu(
471				PduBuilder::state(String::new(), &RoomTopicEventContent::new(topic.clone())),
472				sender_user,
473				room_id,
474				state_lock,
475			)
476			.boxed()
477			.await?;
478	}
479
480	Ok(())
481}
482
483async fn process_invites(
484	services: &Services,
485	body: &Ruma<create_room::v3::Request>,
486	sender_user: &UserId,
487	room_id: &RoomId,
488) {
489	// 8. Events implied by invite (and TODO: invite_3pid)
490	body.invite
491		.iter()
492		.stream()
493		.filter(|&user_id| invite_allowed(services, sender_user, user_id))
494		.for_each(|user_id| async {
495			if let Err(e) = services
496				.membership
497				.invite(sender_user, user_id, room_id, None, body.is_direct)
498				.boxed()
499				.await
500			{
501				warn!(%e, "Failed to send invite");
502			}
503		})
504		.await;
505}
506
507/// Gate an invitee against the sender's ignore list, the recipient's ignore
508/// list, and MSC4380 `m.invite_permission_config`.
509async fn invite_allowed(services: &Services, sender_user: &UserId, invitee: &UserId) -> bool {
510	!(services
511		.users
512		.user_is_ignored(sender_user, invitee)
513		.await || services
514		.users
515		.user_is_ignored(invitee, sender_user)
516		.await || services.users.invites_blocked(invitee).await)
517}
518
519async fn finalize_alias_and_directory(
520	services: &Services,
521	body: &Ruma<create_room::v3::Request>,
522	alias: Option<&RoomAliasId>,
523	sender_user: &UserId,
524	room_id: &RoomId,
525) -> Result {
526	if let Some(alias) = alias {
527		services
528			.alias
529			.set_alias_by(alias, room_id, sender_user)?;
530	}
531
532	if body.visibility == room::Visibility::Public {
533		services.directory.set_public(room_id);
534
535		if services.server.config.admin_room_notices {
536			services
537				.admin
538				.send_text(&format!("{sender_user} made {room_id} public to the room directory"))
539				.await;
540		}
541		info!("{sender_user} made {0} public to the room directory", room_id);
542	}
543
544	Ok(())
545}
546
547async fn create_create_event(
548	services: &Services,
549	body: &Ruma<create_room::v3::Request>,
550	preset: &RoomPreset,
551	room_version: &RoomVersionId,
552	version_rules: &RoomVersionRules,
553) -> Result<(OwnedRoomId, RoomMutexGuard)> {
554	let _sender_user = body.sender_user();
555
556	let mut create_content = match &body.creation_content {
557		| Some(content) => {
558			let mut content = content
559				.deserialize_as_unchecked::<CanonicalJsonObject>()
560				.map_err(|e| {
561					err!(Request(BadJson(error!(
562						"Failed to deserialise content as canonical JSON: {e}"
563					))))
564				})?;
565
566			if !services.config.federate_created_rooms
567				&& (!services.config.allow_federation || !content.contains_key("m.federate"))
568			{
569				content.insert("m.federate".into(), json!(false).try_into()?);
570			}
571
572			content.insert(
573				"room_version".into(),
574				json!(room_version.as_str())
575					.try_into()
576					.map_err(|e| err!(Request(BadJson("Invalid creation content: {e}"))))?,
577			);
578
579			content
580		},
581		| None => {
582			let content = RoomCreateEventContent::new_v11();
583
584			let mut content =
585				serde_json::from_str::<CanonicalJsonObject>(to_raw_value(&content)?.get())?;
586
587			if !services.config.federate_created_rooms {
588				content.insert("m.federate".into(), json!(false).try_into()?);
589			}
590
591			content.insert("room_version".into(), json!(room_version.as_str()).try_into()?);
592			content
593		},
594	};
595
596	if version_rules
597		.authorization
598		.additional_room_creators
599	{
600		let mut additional_creators = body
601			.creation_content
602			.as_ref()
603			.and_then(|c| {
604				c.deserialize_as_unchecked::<CreationContent>()
605					.ok()
606			})
607			.unwrap_or_default()
608			.additional_creators;
609
610		if *preset == RoomPreset::TrustedPrivateChat {
611			additional_creators.extend(body.invite.clone());
612		}
613
614		additional_creators.sort();
615		additional_creators.dedup();
616		if !additional_creators.is_empty() {
617			create_content
618				.insert("additional_creators".into(), json!(additional_creators).try_into()?);
619		}
620	}
621
622	// 1. The room create event, using a placeholder room_id
623	let room_id = ruma::room_id!("!thiswillbereplaced").to_owned();
624	let state_lock = services.state.mutex.lock(&room_id).await;
625	let create_event_id = services
626		.timeline
627		.build_and_append_pdu(
628			PduBuilder {
629				event_type: TimelineEventType::RoomCreate,
630				content: to_raw_value(&create_content)?.into(),
631				state_key: Some(StateKey::new()),
632				..Default::default()
633			},
634			body.sender_user(),
635			&room_id,
636			&state_lock,
637		)
638		.boxed()
639		.await?;
640
641	drop(state_lock);
642
643	// The real room_id is now the event_id.
644	let room_id = OwnedRoomId::from_parts('!', create_event_id.localpart(), None)?;
645	let state_lock = services.state.mutex.lock(&room_id).await;
646
647	Ok((room_id, state_lock))
648}
649
650async fn create_create_event_legacy(
651	services: &Services,
652	body: &Ruma<create_room::v3::Request>,
653	room_version: &RoomVersionId,
654	_version_rules: &RoomVersionRules,
655) -> Result<(OwnedRoomId, RoomMutexGuard)> {
656	let room_id: OwnedRoomId = match &body.room_id {
657		| None => RoomId::new_v1(&services.server.name),
658		| Some(custom_id) => custom_room_id_check(services, custom_id).await?,
659	};
660
661	let state_lock = services.state.mutex.lock(&room_id).await;
662
663	let _short_id = services
664		.short
665		.get_or_create_shortroomid(&room_id)
666		.await;
667
668	let create_content = match &body.creation_content {
669		| Some(content) => {
670			use RoomVersionId::*;
671
672			let mut content = content
673				.deserialize_as_unchecked::<CanonicalJsonObject>()
674				.map_err(|e| {
675					err!(Request(BadJson(error!(
676						"Failed to deserialise content as canonical JSON: {e}"
677					))))
678				})?;
679
680			match room_version {
681				| V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 => {
682					content.insert(
683						"creator".into(),
684						json!(body.sender_user())
685							.try_into()
686							.map_err(|e| {
687								err!(Request(BadJson(debug_error!(
688									"Invalid creation content: {e}"
689								))))
690							})?,
691					);
692				},
693				| _ => {
694					// V11+ removed the "creator" key
695				},
696			}
697
698			if !services.config.federate_created_rooms
699				&& (!services.config.allow_federation || !content.contains_key("m.federate"))
700			{
701				content.insert("m.federate".into(), json!(false).try_into()?);
702			}
703
704			content.insert(
705				"room_version".into(),
706				json!(room_version.as_str())
707					.try_into()
708					.map_err(|e| err!(Request(BadJson("Invalid creation content: {e}"))))?,
709			);
710
711			content
712		},
713		| None => {
714			use RoomVersionId::*;
715
716			let content = match room_version {
717				| V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 =>
718					RoomCreateEventContent::new_v1(body.sender_user().to_owned()),
719				| _ => RoomCreateEventContent::new_v11(),
720			};
721
722			let mut content =
723				serde_json::from_str::<CanonicalJsonObject>(to_raw_value(&content)?.get())?;
724
725			if !services.config.federate_created_rooms {
726				content.insert("m.federate".into(), json!(false).try_into()?);
727			}
728
729			content.insert("room_version".into(), json!(room_version.as_str()).try_into()?);
730			content
731		},
732	};
733
734	// 1. The room create event
735	services
736		.timeline
737		.build_and_append_pdu(
738			PduBuilder {
739				event_type: TimelineEventType::RoomCreate,
740				content: to_raw_value(&create_content)?.into(),
741				state_key: Some(StateKey::new()),
742				..Default::default()
743			},
744			body.sender_user(),
745			&room_id,
746			&state_lock,
747		)
748		.boxed()
749		.await?;
750
751	Ok((room_id, state_lock))
752}
753
754/// creates the power_levels_content for the PDU builder
755fn default_power_levels_content(
756	version_rules: &RoomVersionRules,
757	power_level_content_override: Option<&Raw<RoomPowerLevelsContentOverride>>,
758	preset: &RoomPreset,
759	users: BTreeMap<OwnedUserId, Int>,
760) -> Result<serde_json::Value> {
761	use serde_json::to_value;
762
763	let mut power_levels_content = RoomPowerLevelsEventContent::new(&version_rules.authorization);
764	power_levels_content.users = users;
765
766	let mut power_levels_content = to_value(power_levels_content)?;
767
768	// secure proper defaults of sensitive/dangerous permissions that moderators
769	// (power level 50) should not have easy access to
770	power_levels_content["events"]["m.room.power_levels"] = json!(100);
771	power_levels_content["events"]["m.room.server_acl"] = json!(100);
772	power_levels_content["events"]["m.room.encryption"] = json!(100);
773	power_levels_content["events"]["m.room.history_visibility"] = json!(100);
774
775	if version_rules
776		.authorization
777		.explicitly_privilege_room_creators
778	{
779		power_levels_content["events"]["m.room.tombstone"] = json!(150);
780	} else {
781		power_levels_content["events"]["m.room.tombstone"] = json!(100);
782	}
783
784	// always allow users to respond (not post new) to polls. this is primarily
785	// useful in read-only announcement rooms that post a public poll.
786	power_levels_content["events"]["org.matrix.msc3381.poll.response"] = json!(0);
787	power_levels_content["events"]["m.poll.response"] = json!(0);
788
789	// public_chat: pin invite and call-setup events at PL 50. Synapse pins
790	// invite and m.call.invite here; the MSC3401 entries are tuwunel-only.
791	if *preset == RoomPreset::PublicChat {
792		power_levels_content["invite"] = json!(50);
793		power_levels_content["events"]["m.call.invite"] = json!(50);
794		power_levels_content["events"]["m.call"] = json!(50);
795		power_levels_content["events"]["m.call.member"] = json!(50);
796		power_levels_content["events"]["org.matrix.msc3401.call"] = json!(50);
797		power_levels_content["events"]["org.matrix.msc3401.call.member"] = json!(50);
798	}
799
800	if let Some(power_level_content_override) = power_level_content_override {
801		let json: JsonObject = serde_json::from_str(power_level_content_override.json().get())
802			.map_err(|e| err!(Request(BadJson("Invalid power_level_content_override: {e:?}"))))?;
803
804		for (key, value) in json {
805			power_levels_content[key] = value;
806		}
807	}
808
809	Ok(power_levels_content)
810}
811
812/// if a room is being created with a room alias, run our checks
813async fn room_alias_check(
814	services: &Services,
815	room_alias_name: &str,
816	appservice_info: Option<&RegistrationInfo>,
817) -> Result<OwnedRoomAliasId> {
818	// Basic checks on the room alias validity
819	if room_alias_name.contains(':') {
820		return Err!(Request(InvalidParam(
821			"Room alias contained `:` which is not allowed. Please note that this expects a \
822			 localpart, not the full room alias.",
823		)));
824	} else if room_alias_name.contains(char::is_whitespace) {
825		return Err!(Request(InvalidParam(
826			"Room alias contained spaces which is not a valid room alias.",
827		)));
828	}
829
830	// check if room alias is forbidden
831	if services
832		.config
833		.forbidden_alias_names
834		.is_match(room_alias_name)
835	{
836		return Err!(Request(Unknown("Room alias name is forbidden.")));
837	}
838
839	let server_name = services.globals.server_name();
840	let full_room_alias = OwnedRoomAliasId::parse(format!("#{room_alias_name}:{server_name}"))
841		.map_err(|e| {
842			err!(Request(InvalidParam(debug_error!(
843				?e,
844				?room_alias_name,
845				"Failed to parse room alias.",
846			))))
847		})?;
848
849	if services
850		.alias
851		.resolve_local_alias(&full_room_alias)
852		.await
853		.is_ok()
854	{
855		return Err!(Request(RoomInUse("Room alias already exists.")));
856	}
857
858	if let Some(info) = appservice_info {
859		if !info.aliases.is_match(full_room_alias.as_str()) {
860			return Err!(Request(Exclusive("Room alias is not in namespace.")));
861		}
862	} else if services
863		.appservice
864		.is_exclusive_alias(&full_room_alias)
865		.await
866	{
867		return Err!(Request(Exclusive("Room alias reserved by appservice.",)));
868	}
869
870	debug_info!("Full room alias: {full_room_alias}");
871
872	Ok(full_room_alias)
873}
874
875/// if a room is being created with a custom room ID, run our checks against it
876async fn custom_room_id_check(services: &Services, custom_room_id: &str) -> Result<OwnedRoomId> {
877	// apply forbidden room alias checks to custom room IDs too
878	if services
879		.config
880		.forbidden_alias_names
881		.is_match(custom_room_id)
882	{
883		return Err!(Request(Unknown("Custom room ID is forbidden.")));
884	}
885
886	if custom_room_id.contains(':') {
887		return Err!(Request(InvalidParam(
888			"Custom room ID contained `:` which is not allowed. Please note that this expects a \
889			 localpart, not the full room ID.",
890		)));
891	} else if custom_room_id.contains(char::is_whitespace) {
892		return Err!(Request(InvalidParam(
893			"Custom room ID contained spaces which is not valid."
894		)));
895	}
896
897	let server_name = services.globals.server_name();
898	let full_room_id = format!("!{custom_room_id}:{server_name}");
899
900	let room_id = OwnedRoomId::parse(full_room_id)
901		.inspect(|full_room_id| debug_info!(?full_room_id, "Full custom room ID"))
902		.inspect_err(|e| {
903			warn!(?e, ?custom_room_id, "Failed to create room with custom room ID");
904		})?;
905
906	// check if room ID doesn't already exist instead of erroring on auth check
907	if services
908		.short
909		.get_shortroomid(&room_id)
910		.await
911		.is_ok()
912	{
913		return Err!(Request(RoomInUse("Room with that custom room ID already exists",)));
914	}
915
916	Ok(room_id)
917}
918
919async fn can_publish_directory_check(
920	services: &Services,
921	body: &Ruma<create_room::v3::Request>,
922) -> Result {
923	if !services
924		.server
925		.config
926		.lockdown_public_room_directory
927		|| body.appservice_info.is_some()
928		|| body.visibility != room::Visibility::Public
929		|| services
930			.admin
931			.user_is_admin(body.sender_user())
932			.await
933	{
934		return Ok(());
935	}
936
937	let msg = format!(
938		"Non-admin user {} tried to publish new to the directory while \
939		 lockdown_public_room_directory is enabled",
940		body.sender_user(),
941	);
942
943	warn!("{msg}");
944	if services.server.config.admin_room_notices {
945		services.admin.notice(&msg).await;
946	}
947
948	Err!(Request(Forbidden("Publishing rooms to the room directory is not allowed")))
949}
950
951async fn can_create_room_check(
952	services: &Services,
953	body: &Ruma<create_room::v3::Request>,
954) -> Result {
955	if !services.config.allow_room_creation
956		&& body.appservice_info.is_none()
957		&& !services
958			.admin
959			.user_is_admin(body.sender_user())
960			.await
961	{
962		return Err!(Request(Forbidden("Room creation has been disabled.",)));
963	}
964
965	Ok(())
966}
967
968#[derive(Deserialize)]
969struct InitialEvent {
970	#[serde(rename = "type")]
971	event_type: StateEventType,
972
973	#[serde(default = "StateKey::new")]
974	state_key: StateKey,
975
976	content: Content,
977}
978
979impl From<InitialEvent> for PduBuilder {
980	fn from(value: InitialEvent) -> Self {
981		Self {
982			event_type: value.event_type.into(),
983			content: value.content,
984			unsigned: None,
985			state_key: Some(value.state_key),
986			redacts: None,
987			timestamp: None,
988		}
989	}
990}
991
992fn take_initial(
993	initial_state: &mut Vec<InitialEvent>,
994	event_type: &StateEventType,
995	state_key: &str,
996) -> Option<InitialEvent> {
997	initial_state
998		.extract_if(.., |event| &event.event_type == event_type && event.state_key == state_key)
999		.next()
1000}