2026-07-11 06:09:42 +01:00
|
|
|
use std::collections::{HashMap, hash_map::Entry};
|
|
|
|
|
|
2024-07-16 08:05:25 +00:00
|
|
|
use axum::extract::State;
|
2025-02-23 01:17:45 -05:00
|
|
|
use base64::{Engine as _, engine::general_purpose};
|
2025-04-04 03:30:13 +00:00
|
|
|
use conduwuit::{
|
2026-07-11 18:09:06 +01:00
|
|
|
Err, Error, EventTypeExt, PduEvent, Result, debug, err, error,
|
2026-07-11 07:37:17 +01:00
|
|
|
matrix::{Event, StateKey},
|
|
|
|
|
result::FlatOk,
|
2026-07-11 18:09:06 +01:00
|
|
|
state_res, trace,
|
2026-07-11 06:47:26 +01:00
|
|
|
utils::hash::sha256,
|
2025-04-27 02:39:28 +00:00
|
|
|
warn,
|
2025-04-04 03:30:13 +00:00
|
|
|
};
|
2024-06-05 04:32:58 +00:00
|
|
|
use ruma::{
|
2026-07-11 06:47:26 +01:00
|
|
|
CanonicalJsonObject, CanonicalJsonValue, OwnedEventId, OwnedRoomId, OwnedUserId, ServerName,
|
|
|
|
|
UserId,
|
2026-04-13 10:23:25 -04:00
|
|
|
api::{
|
|
|
|
|
error::{ErrorKind, IncompatibleRoomVersionErrorData},
|
|
|
|
|
federation::membership::{RawStrippedState, create_invite},
|
|
|
|
|
},
|
2026-07-11 06:47:26 +01:00
|
|
|
events::{StateEventType, room::member::MembershipState},
|
|
|
|
|
room_version_rules::RoomVersionRules,
|
2024-06-05 04:32:58 +00:00
|
|
|
};
|
2026-07-11 07:37:17 +01:00
|
|
|
use serde::Deserialize;
|
2024-06-05 04:32:58 +00:00
|
|
|
|
2026-07-19 10:15:39 -07:00
|
|
|
use crate::{Ruma, server::utils::validate_any_membership_event};
|
2024-06-05 04:32:58 +00:00
|
|
|
|
|
|
|
|
/// # `PUT /_matrix/federation/v2/invite/{roomId}/{eventId}`
|
|
|
|
|
///
|
|
|
|
|
/// Invites a remote user to a room.
|
2026-07-12 17:30:05 +01:00
|
|
|
#[tracing::instrument(skip_all, name = "invite", level = "info")]
|
2024-06-10 21:04:51 -04:00
|
|
|
pub(crate) async fn create_invite_route(
|
2024-12-15 00:05:47 -05:00
|
|
|
State(services): State<crate::State>,
|
2024-07-16 08:05:25 +00:00
|
|
|
body: Ruma<create_invite::v2::Request>,
|
2024-06-10 21:04:51 -04:00
|
|
|
) -> Result<create_invite::v2::Response> {
|
2024-12-05 07:23:51 +00:00
|
|
|
if !services.server.supported_room_version(&body.room_version) {
|
2024-06-05 04:32:58 +00:00
|
|
|
return Err(Error::BadRequest(
|
2026-04-13 10:23:25 -04:00
|
|
|
ErrorKind::IncompatibleRoomVersion(IncompatibleRoomVersionErrorData::new(
|
|
|
|
|
body.room_version.clone(),
|
|
|
|
|
)),
|
2026-07-11 07:37:17 +01:00
|
|
|
"This server does not support that room version",
|
2024-06-05 04:32:58 +00:00
|
|
|
));
|
|
|
|
|
}
|
2026-04-13 10:23:25 -04:00
|
|
|
let room_version_rules = body.room_version.rules().unwrap();
|
|
|
|
|
|
2024-07-16 08:05:25 +00:00
|
|
|
if services
|
2025-04-19 23:02:43 +01:00
|
|
|
.moderation
|
2026-05-26 13:28:23 -04:00
|
|
|
.is_remote_server_forbidden(&body.identity)
|
2024-06-05 04:32:58 +00:00
|
|
|
{
|
|
|
|
|
warn!(
|
2024-10-24 12:03:56 +00:00
|
|
|
"Received federated/remote invite from banned server {} for room ID {}. Rejecting.",
|
2026-05-26 13:28:23 -04:00
|
|
|
body.identity, body.room_id
|
2024-06-05 04:32:58 +00:00
|
|
|
);
|
2024-10-03 10:03:31 +00:00
|
|
|
|
2026-07-11 07:37:17 +01:00
|
|
|
return Err!(Request(Forbidden("Federation denied with {}", body.identity)));
|
2024-06-05 04:32:58 +00:00
|
|
|
}
|
|
|
|
|
|
2026-07-11 06:47:26 +01:00
|
|
|
// First, validate the invite room state, so we can compare with the create
|
|
|
|
|
// event.
|
2026-07-11 18:09:06 +01:00
|
|
|
debug!(
|
|
|
|
|
event_id=%body.event_id,
|
|
|
|
|
room_id=%body.room_id,
|
2026-07-11 19:47:20 +01:00
|
|
|
room_version=?body.room_version,
|
|
|
|
|
via=?body.via,
|
2026-07-11 18:09:06 +01:00
|
|
|
"Validating invite room state for invite request"
|
|
|
|
|
);
|
2026-07-11 07:12:07 +01:00
|
|
|
let (create_event_id, state) = validate_invite_state(
|
2026-07-11 06:47:26 +01:00
|
|
|
&services,
|
|
|
|
|
&body.invite_room_state,
|
|
|
|
|
&room_version_rules,
|
|
|
|
|
body.room_id.clone(),
|
2025-12-21 08:41:56 +00:00
|
|
|
)
|
2026-07-11 06:47:26 +01:00
|
|
|
.await?;
|
2026-07-11 07:37:17 +01:00
|
|
|
let create_event_json = state
|
|
|
|
|
.get(&StateEventType::RoomCreate.with_state_key(""))
|
|
|
|
|
.expect("must have create event in invite state by this point");
|
|
|
|
|
|
|
|
|
|
// We can now perform the banned remote server check with the create event.
|
|
|
|
|
// N.B. this checks the sender field, which is technically incorrect for
|
|
|
|
|
// rooms v10 and below. This usually isn't the case though so sue me
|
|
|
|
|
let creator = create_event_json
|
|
|
|
|
.get("sender")
|
|
|
|
|
.and_then(|v| v.as_str())
|
|
|
|
|
.map(UserId::parse)
|
|
|
|
|
.flat_ok()
|
|
|
|
|
.expect("must have valid sender in create event");
|
|
|
|
|
if services
|
|
|
|
|
.moderation
|
|
|
|
|
.is_remote_server_forbidden(creator.server_name())
|
|
|
|
|
{
|
|
|
|
|
return Err!(Request(Forbidden("Server is banned on this homeserver.")));
|
|
|
|
|
}
|
2025-12-21 08:41:56 +00:00
|
|
|
|
2026-07-11 06:47:26 +01:00
|
|
|
// And then we can validate the member event itself
|
2026-07-11 08:14:29 +01:00
|
|
|
let (mut signed_event, sender_user, recipient_user) = validate_invite_membership_event(
|
2026-07-11 06:47:26 +01:00
|
|
|
&services,
|
|
|
|
|
&body.event,
|
|
|
|
|
&room_version_rules,
|
|
|
|
|
&body.identity,
|
2026-07-11 07:12:07 +01:00
|
|
|
create_event_id.clone(),
|
2026-07-11 06:47:26 +01:00
|
|
|
body.room_id.clone(),
|
|
|
|
|
body.event_id.clone(),
|
|
|
|
|
)
|
|
|
|
|
.await?;
|
2024-06-05 04:32:58 +00:00
|
|
|
|
2024-12-15 00:05:47 -05:00
|
|
|
if services.rooms.metadata.is_banned(&body.room_id).await
|
2025-09-21 17:03:40 +00:00
|
|
|
&& !services.users.is_admin(&recipient_user).await
|
2024-12-15 00:05:47 -05:00
|
|
|
{
|
2026-07-11 07:37:17 +01:00
|
|
|
return Err!(Request(Forbidden("That room is banned on this homeserver.")));
|
2024-06-05 04:32:58 +00:00
|
|
|
}
|
|
|
|
|
|
2025-09-21 17:03:40 +00:00
|
|
|
if services.config.block_non_admin_invites && !services.users.is_admin(&recipient_user).await
|
|
|
|
|
{
|
2024-10-03 10:03:31 +00:00
|
|
|
return Err!(Request(Forbidden("This server does not allow room invites.")));
|
2024-06-05 04:32:58 +00:00
|
|
|
}
|
|
|
|
|
|
2026-01-05 03:36:44 +00:00
|
|
|
if let Err(e) = services
|
|
|
|
|
.antispam
|
2026-04-13 18:31:16 -04:00
|
|
|
.user_may_invite(sender_user.clone(), recipient_user.clone(), body.room_id.clone())
|
2026-01-05 03:36:44 +00:00
|
|
|
.await
|
|
|
|
|
{
|
|
|
|
|
warn!("Antispam rejected invite: {e:?}");
|
|
|
|
|
return Err!(Request(Forbidden("Invite rejected by antispam service.")));
|
2026-01-05 01:19:11 +00:00
|
|
|
}
|
|
|
|
|
|
2026-07-11 07:37:17 +01:00
|
|
|
// If we're already in the room, ensure that neither the origin nor
|
|
|
|
|
// ourselves are ACL'd.
|
|
|
|
|
let resident = services
|
2026-07-11 06:09:42 +01:00
|
|
|
.rooms
|
2026-07-11 07:37:17 +01:00
|
|
|
.state_cache
|
|
|
|
|
.server_in_room(services.globals.server_name(), &body.room_id)
|
|
|
|
|
.await;
|
|
|
|
|
if resident {
|
|
|
|
|
services
|
|
|
|
|
.rooms
|
|
|
|
|
.event_handler
|
|
|
|
|
.acl_check(&body.identity, &body.room_id)
|
|
|
|
|
.await?;
|
|
|
|
|
services
|
|
|
|
|
.rooms
|
|
|
|
|
.event_handler
|
|
|
|
|
.acl_check(recipient_user.server_name(), &body.room_id)
|
|
|
|
|
.await
|
|
|
|
|
.map_err(|_| err!(Request(Forbidden("This server is ACL'd from that room"))))?;
|
|
|
|
|
}
|
2026-07-11 06:09:42 +01:00
|
|
|
|
|
|
|
|
services
|
|
|
|
|
.server_keys
|
|
|
|
|
.hash_and_sign_event(&mut signed_event, &room_version_rules)
|
|
|
|
|
.map_err(|e| err!(Request(InvalidParam("Failed to sign event: {e}"))))?;
|
|
|
|
|
|
|
|
|
|
// Add event_id back
|
2026-07-11 07:37:17 +01:00
|
|
|
signed_event
|
|
|
|
|
.insert("event_id".to_owned(), CanonicalJsonValue::String(body.event_id.to_string()));
|
2026-07-11 06:09:42 +01:00
|
|
|
|
2024-06-05 04:32:58 +00:00
|
|
|
let mut invite_state = body.invite_room_state.clone();
|
2026-07-11 07:37:17 +01:00
|
|
|
let pdu = PduEvent::from_id_val(&body.event_id, signed_event.clone())
|
|
|
|
|
.expect("must be able to create PDU object");
|
|
|
|
|
invite_state.push(RawStrippedState::Pdu(serde_json::value::to_raw_value(&signed_event)?));
|
2024-06-05 04:32:58 +00:00
|
|
|
|
2024-11-10 21:20:38 -05:00
|
|
|
// If we are active in the room, the remote server will notify us about the
|
|
|
|
|
// join/invite through /send. If we are not in the room, we need to manually
|
|
|
|
|
// record the invited state for client /sync through update_membership(),
|
|
|
|
|
// and send the invite PDU to the relevant appservices.
|
2026-07-11 07:37:17 +01:00
|
|
|
if !resident {
|
2026-07-11 07:12:07 +01:00
|
|
|
// We will start by recording the room's create event as an outlier.
|
|
|
|
|
// This will allow us to recognise it later in case the sender revokes
|
|
|
|
|
// the invite over federation later. We could store more state from
|
|
|
|
|
// the invite request, but we will get that during send_join anyway.
|
|
|
|
|
// This is safe to just add directly as an outlier as we already auth
|
|
|
|
|
// checked it during validation.
|
|
|
|
|
services
|
|
|
|
|
.rooms
|
|
|
|
|
.outlier
|
|
|
|
|
.add_pdu_outlier(&create_event_id, create_event_json);
|
|
|
|
|
|
2024-08-08 17:18:30 +00:00
|
|
|
services
|
|
|
|
|
.rooms
|
|
|
|
|
.state_cache
|
2025-10-27 17:24:02 -04:00
|
|
|
.mark_as_invited(
|
2025-09-21 17:03:40 +00:00
|
|
|
&recipient_user,
|
2025-10-27 17:24:02 -04:00
|
|
|
&body.room_id,
|
2026-04-13 10:23:25 -04:00
|
|
|
&sender_user,
|
|
|
|
|
invite_state,
|
2024-08-08 17:18:30 +00:00
|
|
|
body.via.clone(),
|
|
|
|
|
)
|
2025-11-05 14:29:03 -05:00
|
|
|
.await?;
|
2025-10-27 17:24:02 -04:00
|
|
|
|
|
|
|
|
services
|
|
|
|
|
.rooms
|
|
|
|
|
.state_cache
|
|
|
|
|
.update_joined_count(&body.room_id)
|
|
|
|
|
.await;
|
2024-06-05 04:32:58 +00:00
|
|
|
|
2026-07-04 19:27:53 -04:00
|
|
|
services.sync.wake(&recipient_user).await;
|
|
|
|
|
|
2024-12-07 01:07:01 -05:00
|
|
|
for appservice in services.appservice.read().await.values() {
|
2025-09-21 17:03:40 +00:00
|
|
|
if appservice.is_user_match(&recipient_user) {
|
2026-04-13 10:23:25 -04:00
|
|
|
let transaction_id = general_purpose::URL_SAFE_NO_PAD
|
|
|
|
|
.encode(sha256::hash(pdu.event_id.as_bytes()))
|
|
|
|
|
.into();
|
|
|
|
|
|
|
|
|
|
let request = ruma::api::appservice::event::push_events::v1::Request::new(
|
|
|
|
|
transaction_id,
|
|
|
|
|
vec![pdu.to_format()],
|
|
|
|
|
);
|
|
|
|
|
|
2024-12-07 01:07:01 -05:00
|
|
|
services
|
|
|
|
|
.sending
|
2026-02-15 17:58:48 +00:00
|
|
|
.send_appservice_request(appservice.registration.clone(), request)
|
|
|
|
|
.await
|
|
|
|
|
.map_err(|e| {
|
|
|
|
|
error!(
|
|
|
|
|
"failed to notify appservice {} about incoming invite: {e}",
|
|
|
|
|
appservice.registration.id
|
|
|
|
|
);
|
|
|
|
|
err!(BadServerResponse(
|
|
|
|
|
"Failed to notify appservice about incoming invite."
|
|
|
|
|
))
|
|
|
|
|
})?;
|
2024-12-07 01:07:01 -05:00
|
|
|
}
|
2024-11-10 21:20:38 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-13 10:23:25 -04:00
|
|
|
Ok(create_invite::v2::Response::new(
|
|
|
|
|
services
|
2024-07-18 06:37:47 +00:00
|
|
|
.sending
|
2024-08-08 17:18:30 +00:00
|
|
|
.convert_to_outgoing_federation_event(signed_event)
|
|
|
|
|
.await,
|
2026-04-13 10:23:25 -04:00
|
|
|
))
|
2024-06-05 04:32:58 +00:00
|
|
|
}
|
2026-07-11 06:47:26 +01:00
|
|
|
|
|
|
|
|
/// Validates the *membership event* in the invite request, per the steps listed
|
|
|
|
|
/// under the invite endpoint's [spec].
|
|
|
|
|
///
|
|
|
|
|
/// Returns the validated JSON body, sender user ID, and recipient user ID.
|
|
|
|
|
///
|
|
|
|
|
/// Since this function performs a PDU format check, the create event must be
|
|
|
|
|
/// known ahead of time. This implies validating the invite state before the
|
|
|
|
|
/// invite event itself.
|
|
|
|
|
///
|
|
|
|
|
/// [spec]: https://spec.matrix.org/v1.19/server-server-api/#put_matrixfederationv2inviteroomideventid
|
2026-07-11 08:14:29 +01:00
|
|
|
async fn validate_invite_membership_event(
|
2026-07-11 06:47:26 +01:00
|
|
|
services: &crate::State,
|
|
|
|
|
body: &serde_json::value::RawValue,
|
|
|
|
|
room_version_rules: &RoomVersionRules,
|
|
|
|
|
origin: &ServerName,
|
|
|
|
|
create_event_id: OwnedEventId,
|
|
|
|
|
room_id: OwnedRoomId,
|
|
|
|
|
event_id: OwnedEventId,
|
|
|
|
|
) -> Result<(CanonicalJsonObject, OwnedUserId, OwnedUserId)> {
|
2026-07-11 20:14:46 +01:00
|
|
|
trace!(?body, "Invite membership event");
|
2026-07-11 08:14:29 +01:00
|
|
|
let (pdu, target_membership, sender_user, recipient_user) = validate_any_membership_event(
|
|
|
|
|
services,
|
|
|
|
|
body,
|
|
|
|
|
room_version_rules,
|
|
|
|
|
create_event_id,
|
|
|
|
|
room_id,
|
|
|
|
|
event_id,
|
|
|
|
|
)
|
|
|
|
|
.await?;
|
2026-07-11 06:47:26 +01:00
|
|
|
|
|
|
|
|
// Ensure the sender belongs to the remote that is sending the invite
|
|
|
|
|
if sender_user.server_name() != origin {
|
|
|
|
|
return Err!(Request(Forbidden("Sender belongs to a different server")));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Ensure the target user belongs to this server
|
|
|
|
|
if !services
|
|
|
|
|
.globals
|
|
|
|
|
.server_is_ours(recipient_user.server_name())
|
|
|
|
|
{
|
|
|
|
|
return Err!(Request(InvalidParam("Recipient does not belong to this homeserver")));
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-11 08:14:29 +01:00
|
|
|
if target_membership != MembershipState::Invite {
|
|
|
|
|
return Err!(Request(BadJson("Invalid membership (expected `invite`)")));
|
|
|
|
|
}
|
2026-07-11 06:47:26 +01:00
|
|
|
|
|
|
|
|
Ok((pdu, sender_user, recipient_user))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Validates the *invite state* of an invite request, per the steps listed
|
|
|
|
|
/// under the endpoint's [spec].
|
|
|
|
|
///
|
|
|
|
|
/// Returns the create event's event ID, and the partial state map.
|
|
|
|
|
///
|
|
|
|
|
/// [spec]: https://spec.matrix.org/v1.19/server-server-api/#put_matrixfederationv2inviteroomideventid
|
|
|
|
|
async fn validate_invite_state(
|
|
|
|
|
services: &crate::State,
|
|
|
|
|
invite_state: &[RawStrippedState],
|
|
|
|
|
room_version_rules: &RoomVersionRules,
|
|
|
|
|
room_id: OwnedRoomId,
|
|
|
|
|
) -> Result<(OwnedEventId, HashMap<(StateEventType, StateKey), CanonicalJsonObject>)> {
|
2026-07-11 19:47:20 +01:00
|
|
|
trace!(?invite_state, "Raw invite state");
|
2026-07-11 06:47:26 +01:00
|
|
|
let mut invite_state_map: HashMap<(StateEventType, StateKey), _> =
|
|
|
|
|
HashMap::with_capacity(invite_state.len());
|
|
|
|
|
let mut create_event_id: Option<OwnedEventId> = None;
|
2026-07-11 18:09:06 +01:00
|
|
|
|
2026-07-11 06:47:26 +01:00
|
|
|
for (idx, invite_state_event) in invite_state.iter().cloned().enumerate() {
|
2026-07-11 18:09:06 +01:00
|
|
|
trace!(%idx, ?invite_state_event, "Invite state event");
|
2026-07-11 06:47:26 +01:00
|
|
|
// Stripped state hasn't been sent over federation since v1.16.
|
|
|
|
|
let RawStrippedState::Pdu(raw_pdu) = invite_state_event else {
|
2026-07-11 18:09:06 +01:00
|
|
|
debug!(%idx, "Invite state event is not a PDU");
|
2026-07-11 06:47:26 +01:00
|
|
|
return Err!(Request(InvalidParam(
|
|
|
|
|
"PDU in invite state (index {idx}) violates the room event format"
|
|
|
|
|
)));
|
|
|
|
|
};
|
2026-07-13 01:37:38 +01:00
|
|
|
let (state_event_room_id, state_event_id, mut state_event_json) = services
|
2026-07-11 06:47:26 +01:00
|
|
|
.rooms
|
|
|
|
|
.event_handler
|
2026-07-11 20:14:46 +01:00
|
|
|
.parse_incoming_pdu(&raw_pdu, Some(room_version_rules))
|
2026-07-11 06:47:26 +01:00
|
|
|
.await
|
2026-07-11 18:09:06 +01:00
|
|
|
.map_err(|e| {
|
|
|
|
|
err!(Request(InvalidParam(debug_warn!("Invalid PDU in invite state: {e}"))))
|
|
|
|
|
})?;
|
2026-07-11 06:47:26 +01:00
|
|
|
|
|
|
|
|
if state_event_room_id != room_id {
|
2026-07-11 18:09:06 +01:00
|
|
|
return Err!(Request(InvalidParam(debug_warn!(
|
|
|
|
|
%state_event_room_id,
|
|
|
|
|
%room_id,
|
2026-07-11 06:47:26 +01:00
|
|
|
"PDU in invite state ({state_event_id}) belongs to the wrong room"
|
2026-07-11 18:09:06 +01:00
|
|
|
))));
|
2026-07-11 06:47:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
services
|
|
|
|
|
.server_keys
|
2026-09-28 02:00:37 +01:00
|
|
|
.verify_event_json(&state_event_json, room_version_rules)
|
2026-07-11 06:47:26 +01:00
|
|
|
.await
|
|
|
|
|
.map_err(|e| {
|
|
|
|
|
err!(Request(InvalidParam("Signature verification failed on invite event: {e}")))
|
|
|
|
|
})?;
|
|
|
|
|
|
|
|
|
|
let Some(state_key) = state_event_json.get("state_key").and_then(|k| k.as_str()) else {
|
2026-07-11 18:09:06 +01:00
|
|
|
return Err!(Request(InvalidParam(debug_info!(
|
2026-07-11 06:47:26 +01:00
|
|
|
"PDU in invite state ({state_event_id}) is not a state event"
|
2026-07-11 18:09:06 +01:00
|
|
|
))));
|
2026-07-11 06:47:26 +01:00
|
|
|
};
|
2026-07-11 18:09:06 +01:00
|
|
|
let Some(event_type) = state_event_json.get("type").and_then(|k| k.as_str()) else {
|
|
|
|
|
return Err!(Request(InvalidParam(debug_warn!(
|
2026-07-11 06:47:26 +01:00
|
|
|
"PDU in invite state ({state_event_id}) is not an event?"
|
2026-07-11 18:09:06 +01:00
|
|
|
))));
|
2026-07-11 06:47:26 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let key = StateEventType::from(event_type).with_state_key(state_key);
|
|
|
|
|
match invite_state_map.entry(key) {
|
|
|
|
|
| Entry::Occupied(entry) =>
|
|
|
|
|
return Err!(Request(InvalidParam(
|
|
|
|
|
"Duplicate state events in invite state for state key: {:?}",
|
|
|
|
|
entry.key(),
|
|
|
|
|
))),
|
|
|
|
|
| Entry::Vacant(entry) => {
|
2026-07-13 01:37:38 +01:00
|
|
|
state_event_json.insert("event_id".to_owned(), state_event_id.to_string().into());
|
2026-07-11 06:47:26 +01:00
|
|
|
if entry.key().0 == StateEventType::RoomCreate {
|
2026-07-11 07:12:07 +01:00
|
|
|
// Ensure this is a legal create event.
|
|
|
|
|
let pdu_event =
|
|
|
|
|
PduEvent::from_id_val(&state_event_id, state_event_json.clone())
|
|
|
|
|
.expect("must be able to create pdu event from event json");
|
2026-07-11 18:09:06 +01:00
|
|
|
debug!("Validating discovered create event in invite room state");
|
2026-07-11 07:37:17 +01:00
|
|
|
validate_invite_create_event(&pdu_event, room_version_rules).await?;
|
2026-07-11 06:47:26 +01:00
|
|
|
create_event_id = Some(state_event_id);
|
|
|
|
|
}
|
|
|
|
|
entry.insert(state_event_json);
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
let Some(create_event_id) = create_event_id else {
|
2026-07-11 18:09:06 +01:00
|
|
|
return Err!(Request(InvalidParam(debug_warn!(
|
|
|
|
|
parsed_state=?invite_state_map,
|
2026-07-11 06:47:26 +01:00
|
|
|
"Invite state does not contain the m.room.create event"
|
2026-07-11 18:09:06 +01:00
|
|
|
))));
|
2026-07-11 06:47:26 +01:00
|
|
|
};
|
|
|
|
|
invite_state_map.iter().try_for_each(|(key, event_json)| {
|
|
|
|
|
service::rooms::event_handler::Service::pdu_format_check_1(
|
|
|
|
|
event_json,
|
|
|
|
|
room_version_rules,
|
|
|
|
|
&create_event_id,
|
|
|
|
|
)
|
|
|
|
|
.map_err(|e| {
|
|
|
|
|
err!(Request(InvalidParam(
|
|
|
|
|
"PDU in invite state for {key:?} violates the room event format: {e}"
|
|
|
|
|
)))
|
|
|
|
|
})
|
|
|
|
|
})?;
|
|
|
|
|
|
|
|
|
|
Ok((create_event_id, invite_state_map))
|
|
|
|
|
}
|
2026-07-11 07:37:17 +01:00
|
|
|
|
|
|
|
|
#[derive(Deserialize)]
|
|
|
|
|
struct MFederate {
|
|
|
|
|
#[serde(rename = "m.federate")]
|
|
|
|
|
mfederate: Option<bool>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Validates that a create event is suitable for the invite, namely:
|
|
|
|
|
///
|
|
|
|
|
/// 1. It passes auth checks (aka is valid)
|
|
|
|
|
/// 2. The room is federated (there's no point persisting unfederated rooms)
|
|
|
|
|
async fn validate_invite_create_event(
|
|
|
|
|
pdu: &PduEvent,
|
|
|
|
|
room_version_rules: &RoomVersionRules,
|
|
|
|
|
) -> Result {
|
|
|
|
|
if !state_res::auth_check(
|
|
|
|
|
room_version_rules,
|
|
|
|
|
pdu,
|
|
|
|
|
None,
|
|
|
|
|
|_, _| async {
|
|
|
|
|
unreachable!("No state should be fetched when processing a lone create event");
|
|
|
|
|
},
|
|
|
|
|
pdu,
|
|
|
|
|
)
|
|
|
|
|
.await
|
|
|
|
|
.unwrap_or_default()
|
|
|
|
|
{
|
|
|
|
|
return Err!(Request(InvalidParam("m.room.create event fails auth check")));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let can_federate = pdu.get_content::<MFederate>()?.mfederate;
|
|
|
|
|
if !can_federate.unwrap_or(true) {
|
|
|
|
|
return Err!(Request(InvalidParam(
|
|
|
|
|
"Cannot receive invites to a room with m.federate=false"
|
|
|
|
|
)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|