From cf94b8e712b0c8d31ec8a55022b78c1e112560e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20K=C3=B6sters?= Date: Tue, 4 May 2021 19:03:18 +0200 Subject: [PATCH] improvement: uiaa works like in synapse --- src/client_server/account.rs | 39 ++++++-- src/client_server/device.rs | 14 ++- src/client_server/directory.rs | 164 ++++++++++++++++----------------- src/client_server/keys.rs | 7 +- src/client_server/message.rs | 9 +- src/client_server/mod.rs | 6 +- src/database.rs | 3 +- src/database/uiaa.rs | 125 +++++++++++++++++++------ src/ruma_wrapper.rs | 56 ++++++++--- src/server_server.rs | 80 +++++++++------- 10 files changed, 326 insertions(+), 177 deletions(-) diff --git a/src/client_server/account.rs b/src/client_server/account.rs index 2241d45..6554277 100644 --- a/src/client_server/account.rs +++ b/src/client_server/account.rs @@ -1,4 +1,7 @@ -use std::{collections::BTreeMap, convert::TryInto}; +use std::{ + collections::BTreeMap, + convert::{TryFrom, TryInto}, +}; use super::{State, DEVICE_ID_LENGTH, SESSION_ID_LENGTH, TOKEN_LENGTH}; use crate::{pdu::PduBuilder, utils, ConduitResult, Database, Error, Ruma}; @@ -143,16 +146,28 @@ pub async fn register_route( if !body.from_appservice { if let Some(auth) = &body.auth { - let (worked, uiaainfo) = - db.uiaa - .try_auth(&user_id, "".into(), auth, &uiaainfo, &db.users, &db.globals)?; + let (worked, uiaainfo) = db.uiaa.try_auth( + &UserId::parse_with_server_name("", db.globals.server_name()) + .expect("we know this is valid"), + "".into(), + auth, + &uiaainfo, + &db.users, + &db.globals, + )?; if !worked { return Err(Error::Uiaa(uiaainfo)); } // Success! } else { uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH)); - db.uiaa.create(&user_id, "".into(), &uiaainfo)?; + db.uiaa.create( + &UserId::parse_with_server_name("", db.globals.server_name()) + .expect("we know this is valid"), + "".into(), + &uiaainfo, + &body.json_body.expect("body is json"), + )?; return Err(Error::Uiaa(uiaainfo)); } } @@ -526,7 +541,12 @@ pub async fn change_password_route( // Success! } else { uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH)); - db.uiaa.create(&sender_user, &sender_device, &uiaainfo)?; + db.uiaa.create( + &sender_user, + &sender_device, + &uiaainfo, + &body.json_body.expect("body is json"), + )?; return Err(Error::Uiaa(uiaainfo)); } @@ -612,7 +632,12 @@ pub async fn deactivate_route( // Success! } else { uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH)); - db.uiaa.create(&sender_user, &sender_device, &uiaainfo)?; + db.uiaa.create( + &sender_user, + &sender_device, + &uiaainfo, + &body.json_body.expect("body is json"), + )?; return Err(Error::Uiaa(uiaainfo)); } diff --git a/src/client_server/device.rs b/src/client_server/device.rs index 1950c5c..961ba97 100644 --- a/src/client_server/device.rs +++ b/src/client_server/device.rs @@ -115,7 +115,12 @@ pub async fn delete_device_route( // Success! } else { uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH)); - db.uiaa.create(&sender_user, &sender_device, &uiaainfo)?; + db.uiaa.create( + &sender_user, + &sender_device, + &uiaainfo, + &body.json_body.expect("body is json"), + )?; return Err(Error::Uiaa(uiaainfo)); } @@ -164,7 +169,12 @@ pub async fn delete_devices_route( // Success! } else { uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH)); - db.uiaa.create(&sender_user, &sender_device, &uiaainfo)?; + db.uiaa.create( + &sender_user, + &sender_device, + &uiaainfo, + &body.json_body.expect("body is json"), + )?; return Err(Error::Uiaa(uiaainfo)); } diff --git a/src/client_server/directory.rs b/src/client_server/directory.rs index 018050d..9864a5e 100644 --- a/src/client_server/directory.rs +++ b/src/client_server/directory.rs @@ -203,19 +203,20 @@ pub async fn get_public_rooms_filtered_helper( } } - let mut all_rooms = - db.rooms - .public_rooms() - .map(|room_id| { - let room_id = room_id?; + let mut all_rooms = db + .rooms + .public_rooms() + .map(|room_id| { + let room_id = room_id?; - let chunk = PublicRoomsChunk { - aliases: Vec::new(), - canonical_alias: db - .rooms - .room_state_get(&room_id, &EventType::RoomCanonicalAlias, "")? - .map_or(Ok::<_, Error>(None), |s| { - Ok(serde_json::from_value::< + let chunk = PublicRoomsChunk { + aliases: Vec::new(), + canonical_alias: db + .rooms + .room_state_get(&room_id, &EventType::RoomCanonicalAlias, "")? + .map_or(Ok::<_, Error>(None), |s| { + Ok( + serde_json::from_value::< Raw, >(s.content) .expect("from_value::> can never fail") @@ -223,62 +224,61 @@ pub async fn get_public_rooms_filtered_helper( .map_err(|_| { Error::bad_database("Invalid canonical alias event in database.") })? - .alias) - })?, - name: db - .rooms - .room_state_get(&room_id, &EventType::RoomName, "")? - .map_or(Ok::<_, Error>(None), |s| { - Ok(serde_json::from_value::>( - s.content, - ) - .expect("from_value::> can never fail") - .deserialize() - .map_err(|_| { - Error::bad_database("Invalid room name event in database.") - })? - .name() - .map(|n| n.to_owned())) - })?, - num_joined_members: (db.rooms.room_members(&room_id).count() as u32).into(), - topic: db - .rooms - .room_state_get(&room_id, &EventType::RoomTopic, "")? - .map_or(Ok::<_, Error>(None), |s| { - Ok(Some( - serde_json::from_value::>( - s.content, - ) + .alias, + ) + })?, + name: db + .rooms + .room_state_get(&room_id, &EventType::RoomName, "")? + .map_or(Ok::<_, Error>(None), |s| { + Ok( + serde_json::from_value::>(s.content) + .expect("from_value::> can never fail") + .deserialize() + .map_err(|_| { + Error::bad_database("Invalid room name event in database.") + })? + .name() + .map(|n| n.to_owned()), + ) + })?, + num_joined_members: (db.rooms.room_members(&room_id).count() as u32).into(), + topic: db + .rooms + .room_state_get(&room_id, &EventType::RoomTopic, "")? + .map_or(Ok::<_, Error>(None), |s| { + Ok(Some( + serde_json::from_value::>(s.content) .expect("from_value::> can never fail") .deserialize() .map_err(|_| { Error::bad_database("Invalid room topic event in database.") })? .topic, - )) - })?, - world_readable: db - .rooms - .room_state_get(&room_id, &EventType::RoomHistoryVisibility, "")? - .map_or(Ok::<_, Error>(false), |s| { - Ok(serde_json::from_value::< - Raw, - >(s.content) - .expect("from_value::> can never fail") - .deserialize() - .map_err(|_| { - Error::bad_database( - "Invalid room history visibility event in database.", - ) - })? - .history_visibility - == history_visibility::HistoryVisibility::WorldReadable) - })?, - guest_can_join: db - .rooms - .room_state_get(&room_id, &EventType::RoomGuestAccess, "")? - .map_or(Ok::<_, Error>(false), |s| { - Ok( + )) + })?, + world_readable: db + .rooms + .room_state_get(&room_id, &EventType::RoomHistoryVisibility, "")? + .map_or(Ok::<_, Error>(false), |s| { + Ok(serde_json::from_value::< + Raw, + >(s.content) + .expect("from_value::> can never fail") + .deserialize() + .map_err(|_| { + Error::bad_database( + "Invalid room history visibility event in database.", + ) + })? + .history_visibility + == history_visibility::HistoryVisibility::WorldReadable) + })?, + guest_can_join: db + .rooms + .room_state_get(&room_id, &EventType::RoomGuestAccess, "")? + .map_or(Ok::<_, Error>(false), |s| { + Ok( serde_json::from_value::>( s.content, ) @@ -290,33 +290,31 @@ pub async fn get_public_rooms_filtered_helper( .guest_access == guest_access::GuestAccess::CanJoin, ) - })?, - avatar_url: db - .rooms - .room_state_get(&room_id, &EventType::RoomAvatar, "")? - .map(|s| { - Ok::<_, Error>( - serde_json::from_value::>( - s.content, - ) + })?, + avatar_url: db + .rooms + .room_state_get(&room_id, &EventType::RoomAvatar, "")? + .map(|s| { + Ok::<_, Error>( + serde_json::from_value::>(s.content) .expect("from_value::> can never fail") .deserialize() .map_err(|_| { Error::bad_database("Invalid room avatar event in database.") })? .url, - ) - }) - .transpose()? - // url is now an Option so we must flatten - .flatten(), - room_id, - }; - Ok(chunk) - }) - .filter_map(|r: Result<_>| r.ok()) // Filter out buggy rooms - // We need to collect all, so we can sort by member count - .collect::>(); + ) + }) + .transpose()? + // url is now an Option so we must flatten + .flatten(), + room_id, + }; + Ok(chunk) + }) + .filter_map(|r: Result<_>| r.ok()) // Filter out buggy rooms + // We need to collect all, so we can sort by member count + .collect::>(); all_rooms.sort_by(|l, r| r.num_joined_members.cmp(&l.num_joined_members)); diff --git a/src/client_server/keys.rs b/src/client_server/keys.rs index 08bb4c6..aafa157 100644 --- a/src/client_server/keys.rs +++ b/src/client_server/keys.rs @@ -220,7 +220,12 @@ pub async fn upload_signing_keys_route( // Success! } else { uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH)); - db.uiaa.create(&sender_user, &sender_device, &uiaainfo)?; + db.uiaa.create( + &sender_user, + &sender_device, + &uiaainfo, + &body.json_body.expect("body is json"), + )?; return Err(Error::Uiaa(uiaainfo)); } diff --git a/src/client_server/message.rs b/src/client_server/message.rs index ecd2665..96de93d 100644 --- a/src/client_server/message.rs +++ b/src/client_server/message.rs @@ -56,13 +56,8 @@ pub async fn send_message_event_route( let event_id = db.rooms.build_and_append_pdu( PduBuilder { event_type: EventType::from(&body.event_type), - content: serde_json::from_str( - body.json_body - .as_ref() - .ok_or(Error::BadRequest(ErrorKind::BadJson, "Invalid JSON body."))? - .get(), - ) - .map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Invalid JSON body."))?, + content: serde_json::from_str(body.body.body.json().get()) + .map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Invalid JSON body."))?, unsigned: Some(unsigned), state_key: None, redacts: None, diff --git a/src/client_server/mod.rs b/src/client_server/mod.rs index dd8e7a6..825dbbb 100644 --- a/src/client_server/mod.rs +++ b/src/client_server/mod.rs @@ -69,9 +69,9 @@ use { ruma::api::client::r0::to_device::send_event_to_device, }; -const DEVICE_ID_LENGTH: usize = 10; -const TOKEN_LENGTH: usize = 256; -const SESSION_ID_LENGTH: usize = 256; +pub const DEVICE_ID_LENGTH: usize = 10; +pub const TOKEN_LENGTH: usize = 256; +pub const SESSION_ID_LENGTH: usize = 256; #[cfg(feature = "conduit_bin")] #[options("/<_..>")] diff --git a/src/database.rs b/src/database.rs index 06a708d..6504f9c 100644 --- a/src/database.rs +++ b/src/database.rs @@ -135,7 +135,8 @@ impl Database { todeviceid_events: db.open_tree("todeviceid_events")?, }, uiaa: uiaa::Uiaa { - userdeviceid_uiaainfo: db.open_tree("userdeviceid_uiaainfo")?, + userdevicesessionid_uiaainfo: db.open_tree("userdevicesessionid_uiaainfo")?, + userdevicesessionid_uiaarequest: db.open_tree("userdevicesessionid_uiaarequest")?, }, rooms: rooms::Rooms { edus: rooms::RoomEdus { diff --git a/src/database/uiaa.rs b/src/database/uiaa.rs index 4c33b86..3b77840 100644 --- a/src/database/uiaa.rs +++ b/src/database/uiaa.rs @@ -1,15 +1,17 @@ -use crate::{Error, Result}; +use crate::{client_server::SESSION_ID_LENGTH, utils, Error, Result}; use ruma::{ api::client::{ error::ErrorKind, r0::uiaa::{IncomingAuthData, UiaaInfo}, }, + signatures::CanonicalJsonValue, DeviceId, UserId, }; #[derive(Clone)] pub struct Uiaa { - pub(super) userdeviceid_uiaainfo: sled::Tree, // User-interactive authentication + pub(super) userdevicesessionid_uiaainfo: sled::Tree, // User-interactive authentication + pub(super) userdevicesessionid_uiaarequest: sled::Tree, // UiaaRequest = canonical json value } impl Uiaa { @@ -19,8 +21,20 @@ impl Uiaa { user_id: &UserId, device_id: &DeviceId, uiaainfo: &UiaaInfo, + json_body: &CanonicalJsonValue, ) -> Result<()> { - self.update_uiaa_session(user_id, device_id, Some(uiaainfo)) + self.set_uiaa_request( + user_id, + device_id, + uiaainfo.session.as_ref().expect("session should be set"), // TODO: better session error handling (why is it optional in ruma?) + json_body, + )?; + self.update_uiaa_session( + user_id, + device_id, + uiaainfo.session.as_ref().expect("session should be set"), + Some(uiaainfo), + ) } pub fn try_auth( @@ -45,6 +59,10 @@ impl Uiaa { }) .unwrap_or_else(|| Ok(uiaainfo.clone()))?; + if uiaainfo.session.is_none() { + uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH)); + } + // Find out what the user completed match &**kind { "m.login.password" => { @@ -130,35 +148,96 @@ impl Uiaa { } if !completed { - self.update_uiaa_session(user_id, device_id, Some(&uiaainfo))?; + self.update_uiaa_session( + user_id, + device_id, + uiaainfo.session.as_ref().expect("session is always set"), + Some(&uiaainfo), + )?; return Ok((false, uiaainfo)); } // UIAA was successful! Remove this session and return true - self.update_uiaa_session(user_id, device_id, None)?; + self.update_uiaa_session( + user_id, + device_id, + uiaainfo.session.as_ref().expect("session is always set"), + None, + )?; Ok((true, uiaainfo)) } else { panic!("FallbackAcknowledgement is not supported yet"); } } + fn set_uiaa_request( + &self, + user_id: &UserId, + device_id: &DeviceId, + session: &str, + request: &CanonicalJsonValue, + ) -> Result<()> { + let mut userdevicesessionid = user_id.as_bytes().to_vec(); + userdevicesessionid.push(0xff); + userdevicesessionid.extend_from_slice(device_id.as_bytes()); + userdevicesessionid.push(0xff); + userdevicesessionid.extend_from_slice(session.as_bytes()); + + self.userdevicesessionid_uiaarequest.insert( + &userdevicesessionid, + &*serde_json::to_string(request).expect("json value to string always works"), + )?; + + Ok(()) + } + + pub fn get_uiaa_request( + &self, + user_id: &UserId, + device_id: &DeviceId, + session: &str, + ) -> Result> { + let mut userdevicesessionid = user_id.as_bytes().to_vec(); + userdevicesessionid.push(0xff); + userdevicesessionid.extend_from_slice(device_id.as_bytes()); + userdevicesessionid.push(0xff); + userdevicesessionid.extend_from_slice(session.as_bytes()); + + self.userdevicesessionid_uiaarequest + .get(&userdevicesessionid)? + .map_or(Ok(None), |bytes| { + Ok::<_, Error>(Some( + serde_json::from_str::( + &utils::string_from_bytes(&bytes).map_err(|_| { + Error::bad_database("Invalid uiaa request bytes in db.") + })?, + ) + .map_err(|_| Error::bad_database("Invalid uiaa request in db."))?, + )) + }) + } + fn update_uiaa_session( &self, user_id: &UserId, device_id: &DeviceId, + session: &str, uiaainfo: Option<&UiaaInfo>, ) -> Result<()> { - let mut userdeviceid = user_id.as_bytes().to_vec(); - userdeviceid.push(0xff); - userdeviceid.extend_from_slice(device_id.as_bytes()); + let mut userdevicesessionid = user_id.as_bytes().to_vec(); + userdevicesessionid.push(0xff); + userdevicesessionid.extend_from_slice(device_id.as_bytes()); + userdevicesessionid.push(0xff); + userdevicesessionid.extend_from_slice(session.as_bytes()); if let Some(uiaainfo) = uiaainfo { - self.userdeviceid_uiaainfo.insert( - &userdeviceid, + self.userdevicesessionid_uiaainfo.insert( + &userdevicesessionid, &*serde_json::to_string(&uiaainfo).expect("UiaaInfo::to_string always works"), )?; } else { - self.userdeviceid_uiaainfo.remove(&userdeviceid)?; + self.userdevicesessionid_uiaainfo + .remove(&userdevicesessionid)?; } Ok(()) @@ -170,14 +249,16 @@ impl Uiaa { device_id: &DeviceId, session: &str, ) -> Result { - let mut userdeviceid = user_id.as_bytes().to_vec(); - userdeviceid.push(0xff); - userdeviceid.extend_from_slice(device_id.as_bytes()); + let mut userdevicesessionid = user_id.as_bytes().to_vec(); + userdevicesessionid.push(0xff); + userdevicesessionid.extend_from_slice(device_id.as_bytes()); + userdevicesessionid.push(0xff); + userdevicesessionid.extend_from_slice(session.as_bytes()); let uiaainfo = serde_json::from_slice::( &self - .userdeviceid_uiaainfo - .get(&userdeviceid)? + .userdevicesessionid_uiaainfo + .get(&userdevicesessionid)? .ok_or(Error::BadRequest( ErrorKind::Forbidden, "UIAA session does not exist.", @@ -185,18 +266,6 @@ impl Uiaa { ) .map_err(|_| Error::bad_database("UiaaInfo in userdeviceid_uiaainfo is invalid."))?; - if uiaainfo - .session - .as_ref() - .filter(|&s| s == session) - .is_none() - { - return Err(Error::BadRequest( - ErrorKind::Forbidden, - "UIAA session token invalid.", - )); - } - Ok(uiaainfo) } } diff --git a/src/ruma_wrapper.rs b/src/ruma_wrapper.rs index 49a9fb0..e4eda87 100644 --- a/src/ruma_wrapper.rs +++ b/src/ruma_wrapper.rs @@ -8,7 +8,7 @@ use std::ops::Deref; #[cfg(feature = "conduit_bin")] use { - crate::{server_server, utils}, + crate::server_server, log::{debug, warn}, rocket::{ data::{self, ByteUnit, Data, FromData}, @@ -35,7 +35,7 @@ pub struct Ruma { pub sender_user: Option, pub sender_device: Option>, // This is None when body is not a valid string - pub json_body: Option>, + pub json_body: Option, pub from_appservice: bool, } @@ -66,6 +66,8 @@ where let mut body = Vec::new(); handle.read_to_end(&mut body).await.unwrap(); + let mut json_body = serde_json::from_slice::(&body).ok(); + let (sender_user, sender_device, from_appservice) = if let Some((_id, registration)) = db .appservice .iter_all() @@ -115,7 +117,7 @@ where // Unknown Token None => return Failure((Status::raw(581), ())), Some((user_id, device_id)) => { - (Some(user_id), Some(device_id.into()), false) + (Some(user_id), Some(Box::::from(device_id)), false) } } } else { @@ -187,12 +189,10 @@ where } }; - let json_body = serde_json::from_slice::(&body); - let mut request_map = BTreeMap::::new(); - if let Ok(json_body) = json_body { - request_map.insert("content".to_owned(), json_body); + if let Some(json_body) = &json_body { + request_map.insert("content".to_owned(), json_body.clone()); }; request_map.insert( @@ -271,6 +271,43 @@ where http_request = http_request.header(header.name.as_str(), &*header.value); } + match &mut json_body { + Some(CanonicalJsonValue::Object(json_body)) => { + let user_id = sender_user.clone().unwrap_or_else(|| { + UserId::parse_with_server_name("", db.globals.server_name()) + .expect("we know this is valid") + }); + + if let Some(initial_request) = json_body + .get("auth") + .and_then(|auth| auth.as_object()) + .and_then(|auth| auth.get("session")) + .and_then(|session| session.as_str()) + .and_then(|session| { + db.uiaa + .get_uiaa_request( + &user_id, + &sender_device.clone().unwrap_or_else(|| "".into()), + session, + ) + .ok() + .flatten() + }) + { + match initial_request { + CanonicalJsonValue::Object(initial_request) => { + for (key, value) in initial_request.into_iter() { + json_body.entry(key).or_insert(value); + } + } + _ => {} + } + } + body = serde_json::to_vec(json_body).expect("value to bytes can't fail"); + } + _ => {} + } + let http_request = http_request.body(&*body).unwrap(); debug!("{:?}", http_request); match ::try_from_http_request(http_request) { @@ -278,11 +315,8 @@ where body: t, sender_user, sender_device, - // TODO: Can we avoid parsing it again? (We only need this for append_pdu) - json_body: utils::string_from_bytes(&body) - .ok() - .and_then(|s| serde_json::value::RawValue::from_string(s).ok()), from_appservice, + json_body, }), Err(e) => { warn!("{:?}", e); diff --git a/src/server_server.rs b/src/server_server.rs index 908a54e..3899239 100644 --- a/src/server_server.rs +++ b/src/server_server.rs @@ -1018,29 +1018,6 @@ pub fn handle_incoming_pdu<'a>( } debug!("Auth check succeeded."); - // 13. Check if the event passes auth based on the "current state" of the room, if not "soft fail" it - let current_state = db - .rooms - .room_state_full(&room_id) - .map_err(|_| "Failed to load room state.".to_owned())? - .into_iter() - .map(|(k, v)| (k, Arc::new(v))) - .collect(); - - if !state_res::event_auth::auth_check( - &room_version, - &incoming_pdu, - previous_create, - ¤t_state, - None, - ) - .map_err(|_e| "Auth check failed.".to_owned())? - { - // Soft fail, we leave the event as an outlier but don't add it to the timeline - return Err("Event has been soft failed".into()); - }; - debug!("Auth check with current state succeeded."); - // Now we calculate the set of extremities this room has after the incoming event has been // applied. We start with the previous extremities (aka leaves) let mut extremities = db @@ -1103,6 +1080,14 @@ pub fn handle_incoming_pdu<'a>( // don't just trust a set of state we got from a remote). // We do this by adding the current state to the list of fork states + let current_state = db + .rooms + .room_state_full(&room_id) + .map_err(|_| "Failed to load room state.".to_owned())? + .into_iter() + .map(|(k, v)| (k, Arc::new(v))) + .collect(); + fork_states.insert(current_state); // We also add state after incoming event to the fork states @@ -1199,18 +1184,40 @@ pub fn handle_incoming_pdu<'a>( } }; - // Now that the event has passed all auth it is added into the timeline. - // We use the `state_at_event` instead of `state_after` so we accurately - // represent the state for this event. - let pdu_id = append_incoming_pdu( - &db, + // 13. Check if the event passes auth based on the "current state" of the room, if not "soft fail" it + let soft_fail = !state_res::event_auth::auth_check( + &room_version, &incoming_pdu, - val, - extremities, - &state_at_incoming_event, + previous_create, + &new_room_state + .iter() + .filter_map(|(k, v)| { + Some((k.clone(), Arc::new(db.rooms.get_pdu(&v).ok().flatten()?))) + }) + .collect(), + None, ) - .map_err(|_| "Failed to add pdu to db.".to_owned())?; - debug!("Appended incoming pdu."); + .map_err(|_e| "Auth check failed.".to_owned())?; + + let mut pdu_id = None; + if !soft_fail { + // Now that the event has passed all auth it is added into the timeline. + // We use the `state_at_event` instead of `state_after` so we accurately + // represent the state for this event. + pdu_id = Some( + append_incoming_pdu( + &db, + &incoming_pdu, + val, + extremities, + &state_at_incoming_event, + ) + .map_err(|_| "Failed to add pdu to db.".to_owned())?, + ); + debug!("Appended incoming pdu."); + } else { + warn!("Event was soft failed: {:?}", incoming_pdu); + } // Set the new room state to the resolved state if update_state { @@ -1220,8 +1227,13 @@ pub fn handle_incoming_pdu<'a>( } debug!("Updated resolved state"); + if soft_fail { + // Soft fail, we leave the event as an outlier but don't add it to the timeline + return Err("Event has been soft failed".into()); + } + // Event has passed all auth/stateres checks - Ok(Some(pdu_id)) + Ok(pdu_id) }) }