mirror of
https://gitlab.com/famedly/conduit.git
synced 2024-12-27 21:13:47 +03:00
Merge pull request 'More Federation' (#181) from docs into master
Reviewed-on: https://git.koesters.xyz/timo/conduit/pulls/181
This commit is contained in:
commit
8e55623bde
22 changed files with 974 additions and 308 deletions
27
Cargo.lock
generated
27
Cargo.lock
generated
|
@ -267,6 +267,7 @@ checksum = "0dbbb57365263e881e805dc77d94697c9118fd94d8da011240555aa7b23445bd"
|
|||
name = "conduit"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"base64 0.12.3",
|
||||
"directories",
|
||||
"http",
|
||||
"image",
|
||||
|
@ -1559,7 +1560,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "ruma"
|
||||
version = "0.0.1"
|
||||
source = "git+https://github.com/ruma/ruma?rev=987d48666cf166cf12100b5dbc61b5e3385c4014#987d48666cf166cf12100b5dbc61b5e3385c4014"
|
||||
source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#c2adc9ecb85538505ff351dbd883c9106f651744"
|
||||
dependencies = [
|
||||
"ruma-api",
|
||||
"ruma-client-api",
|
||||
|
@ -1573,7 +1574,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "ruma-api"
|
||||
version = "0.17.0-alpha.1"
|
||||
source = "git+https://github.com/ruma/ruma?rev=987d48666cf166cf12100b5dbc61b5e3385c4014#987d48666cf166cf12100b5dbc61b5e3385c4014"
|
||||
source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#c2adc9ecb85538505ff351dbd883c9106f651744"
|
||||
dependencies = [
|
||||
"http",
|
||||
"percent-encoding",
|
||||
|
@ -1588,7 +1589,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "ruma-api-macros"
|
||||
version = "0.17.0-alpha.1"
|
||||
source = "git+https://github.com/ruma/ruma?rev=987d48666cf166cf12100b5dbc61b5e3385c4014#987d48666cf166cf12100b5dbc61b5e3385c4014"
|
||||
source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#c2adc9ecb85538505ff351dbd883c9106f651744"
|
||||
dependencies = [
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
|
@ -1599,7 +1600,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "ruma-client-api"
|
||||
version = "0.10.0-alpha.1"
|
||||
source = "git+https://github.com/ruma/ruma?rev=987d48666cf166cf12100b5dbc61b5e3385c4014#987d48666cf166cf12100b5dbc61b5e3385c4014"
|
||||
source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#c2adc9ecb85538505ff351dbd883c9106f651744"
|
||||
dependencies = [
|
||||
"assign",
|
||||
"http",
|
||||
|
@ -1617,7 +1618,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "ruma-common"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/ruma/ruma?rev=987d48666cf166cf12100b5dbc61b5e3385c4014#987d48666cf166cf12100b5dbc61b5e3385c4014"
|
||||
source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#c2adc9ecb85538505ff351dbd883c9106f651744"
|
||||
dependencies = [
|
||||
"js_int",
|
||||
"ruma-identifiers",
|
||||
|
@ -1630,7 +1631,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "ruma-events"
|
||||
version = "0.22.0-alpha.1"
|
||||
source = "git+https://github.com/ruma/ruma?rev=987d48666cf166cf12100b5dbc61b5e3385c4014#987d48666cf166cf12100b5dbc61b5e3385c4014"
|
||||
source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#c2adc9ecb85538505ff351dbd883c9106f651744"
|
||||
dependencies = [
|
||||
"js_int",
|
||||
"ruma-common",
|
||||
|
@ -1645,7 +1646,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "ruma-events-macros"
|
||||
version = "0.22.0-alpha.1"
|
||||
source = "git+https://github.com/ruma/ruma?rev=987d48666cf166cf12100b5dbc61b5e3385c4014#987d48666cf166cf12100b5dbc61b5e3385c4014"
|
||||
source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#c2adc9ecb85538505ff351dbd883c9106f651744"
|
||||
dependencies = [
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
|
@ -1656,7 +1657,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "ruma-federation-api"
|
||||
version = "0.0.3"
|
||||
source = "git+https://github.com/ruma/ruma?rev=987d48666cf166cf12100b5dbc61b5e3385c4014#987d48666cf166cf12100b5dbc61b5e3385c4014"
|
||||
source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#c2adc9ecb85538505ff351dbd883c9106f651744"
|
||||
dependencies = [
|
||||
"js_int",
|
||||
"ruma-api",
|
||||
|
@ -1671,7 +1672,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "ruma-identifiers"
|
||||
version = "0.17.4"
|
||||
source = "git+https://github.com/ruma/ruma?rev=987d48666cf166cf12100b5dbc61b5e3385c4014#987d48666cf166cf12100b5dbc61b5e3385c4014"
|
||||
source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#c2adc9ecb85538505ff351dbd883c9106f651744"
|
||||
dependencies = [
|
||||
"rand",
|
||||
"ruma-identifiers-macros",
|
||||
|
@ -1683,7 +1684,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "ruma-identifiers-macros"
|
||||
version = "0.17.4"
|
||||
source = "git+https://github.com/ruma/ruma?rev=987d48666cf166cf12100b5dbc61b5e3385c4014#987d48666cf166cf12100b5dbc61b5e3385c4014"
|
||||
source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#c2adc9ecb85538505ff351dbd883c9106f651744"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -1694,7 +1695,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "ruma-identifiers-validation"
|
||||
version = "0.1.1"
|
||||
source = "git+https://github.com/ruma/ruma?rev=987d48666cf166cf12100b5dbc61b5e3385c4014#987d48666cf166cf12100b5dbc61b5e3385c4014"
|
||||
source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#c2adc9ecb85538505ff351dbd883c9106f651744"
|
||||
dependencies = [
|
||||
"ruma-serde",
|
||||
"serde",
|
||||
|
@ -1705,7 +1706,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "ruma-serde"
|
||||
version = "0.2.3"
|
||||
source = "git+https://github.com/ruma/ruma?rev=987d48666cf166cf12100b5dbc61b5e3385c4014#987d48666cf166cf12100b5dbc61b5e3385c4014"
|
||||
source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#c2adc9ecb85538505ff351dbd883c9106f651744"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"itoa",
|
||||
|
@ -1717,7 +1718,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "ruma-signatures"
|
||||
version = "0.6.0-dev.1"
|
||||
source = "git+https://github.com/ruma/ruma?rev=987d48666cf166cf12100b5dbc61b5e3385c4014#987d48666cf166cf12100b5dbc61b5e3385c4014"
|
||||
source = "git+https://github.com/timokoesters/ruma?branch=timo-fixes#c2adc9ecb85538505ff351dbd883c9106f651744"
|
||||
dependencies = [
|
||||
"base64 0.12.3",
|
||||
"ring",
|
||||
|
|
|
@ -16,9 +16,10 @@ edition = "2018"
|
|||
#rocket = { git = "https://github.com/SergioBenitez/Rocket.git", rev = "8d779caa22c63b15a6c3ceb75d8f6d4971b2eb67", features = ["tls"] } # Used to handle requests
|
||||
rocket = { git = "https://github.com/timokoesters/Rocket.git", branch = "empty_parameters", features = ["tls"] }
|
||||
|
||||
tokio = "0.2.22" # Used for long polling
|
||||
ruma = { git = "https://github.com/ruma/ruma", features = ["rand", "client-api", "federation-api", "unstable-pre-spec", "unstable-synapse-quirks"], rev = "987d48666cf166cf12100b5dbc61b5e3385c4014" } # Used for matrix spec type definitions and helpers
|
||||
#ruma = { git = "https://github.com/ruma/ruma", features = ["rand", "client-api", "federation-api", "unstable-pre-spec", "unstable-synapse-quirks"], rev = "987d48666cf166cf12100b5dbc61b5e3385c4014" } # Used for matrix spec type definitions and helpers
|
||||
ruma = { git = "https://github.com/timokoesters/ruma", features = ["rand", "client-api", "federation-api", "unstable-pre-spec", "unstable-synapse-quirks"], branch = "timo-fixes" } # Used for matrix spec type definitions and helpers
|
||||
#ruma = { path = "../ruma/ruma", features = ["rand", "client-api", "federation-api", "unstable-pre-spec", "unstable-synapse-quirks"] }
|
||||
tokio = "0.2.22" # Used for long polling
|
||||
sled = "0.32.0" # Used for storing data permanently
|
||||
log = "0.4.8" # Used for emitting log entries
|
||||
http = "0.2.1" # Used for rocket<->ruma conversions
|
||||
|
@ -31,6 +32,7 @@ rust-argon2 = "0.8.2" # Used to hash passwords
|
|||
reqwest = "0.10.6" # Used to send requests
|
||||
thiserror = "1.0.19" # Used for conduit::Error type
|
||||
image = { version = "0.23.4", default-features = false, features = ["jpeg", "png", "gif"] } # Used to generate thumbnails for images
|
||||
base64 = "0.12.3" # Used to encode server public key
|
||||
|
||||
[features]
|
||||
default = ["conduit_bin"]
|
||||
|
|
|
@ -15,11 +15,18 @@ use ruma::{
|
|||
UserId,
|
||||
};
|
||||
|
||||
use register::RegistrationKind;
|
||||
#[cfg(feature = "conduit_bin")]
|
||||
use rocket::{get, post};
|
||||
|
||||
const GUEST_NAME_LENGTH: usize = 10;
|
||||
|
||||
/// # `GET /_matrix/client/r0/register/available`
|
||||
///
|
||||
/// Checks if a username is valid and available on this server.
|
||||
///
|
||||
/// - Returns true if no user or appservice on this server claimed this username
|
||||
/// - This will not reserve the username, so the username might become invalid when trying to register
|
||||
#[cfg_attr(
|
||||
feature = "conduit_bin",
|
||||
get("/_matrix/client/r0/register/available", data = "<body>")
|
||||
|
@ -53,6 +60,15 @@ pub fn get_register_available_route(
|
|||
Ok(get_username_availability::Response { available: true }.into())
|
||||
}
|
||||
|
||||
/// # `POST /_matrix/client/r0/register`
|
||||
///
|
||||
/// Register an account on this homeserver.
|
||||
///
|
||||
/// - Returns the device id and access_token unless `inhibit_login` is true
|
||||
/// - When registering a guest account, all parameters except initial_device_display_name will be
|
||||
/// ignored
|
||||
/// - Creates a new account and a device for it
|
||||
/// - The account will be populated with default account data
|
||||
#[cfg_attr(
|
||||
feature = "conduit_bin",
|
||||
post("/_matrix/client/r0/register", data = "<body>")
|
||||
|
@ -68,12 +84,24 @@ pub fn register_route(
|
|||
));
|
||||
}
|
||||
|
||||
let is_guest = matches!(body.kind, Some(RegistrationKind::Guest));
|
||||
|
||||
let mut missing_username = false;
|
||||
|
||||
// Validate user id
|
||||
let user_id = UserId::parse_with_server_name(
|
||||
body.username
|
||||
.clone()
|
||||
.unwrap_or_else(|| utils::random_string(GUEST_NAME_LENGTH))
|
||||
.to_lowercase(),
|
||||
if is_guest {
|
||||
utils::random_string(GUEST_NAME_LENGTH)
|
||||
} else {
|
||||
body.username.clone().unwrap_or_else(|| {
|
||||
// If the user didn't send a username field, that means the client is just trying
|
||||
// the get an UIAA error to see available flows
|
||||
missing_username = true;
|
||||
// Just give the user a random name. He won't be able to register with it anyway.
|
||||
utils::random_string(GUEST_NAME_LENGTH)
|
||||
})
|
||||
}
|
||||
.to_lowercase(),
|
||||
db.globals.server_name(),
|
||||
)
|
||||
.ok()
|
||||
|
@ -84,7 +112,7 @@ pub fn register_route(
|
|||
))?;
|
||||
|
||||
// Check if username is creative enough
|
||||
if db.users.exists(&user_id)? {
|
||||
if !missing_username && db.users.exists(&user_id)? {
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::UserInUse,
|
||||
"Desired user ID is already taken.",
|
||||
|
@ -116,7 +144,19 @@ pub fn register_route(
|
|||
return Err(Error::Uiaa(uiaainfo));
|
||||
}
|
||||
|
||||
let password = body.password.clone().unwrap_or_default();
|
||||
if missing_username {
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::MissingParam,
|
||||
"Missing username field.",
|
||||
));
|
||||
}
|
||||
|
||||
let password = if is_guest {
|
||||
None
|
||||
} else {
|
||||
body.password.clone()
|
||||
}
|
||||
.unwrap_or_default();
|
||||
|
||||
// Create user
|
||||
db.users.create(&user_id, &password)?;
|
||||
|
@ -134,7 +174,7 @@ pub fn register_route(
|
|||
&db.globals,
|
||||
)?;
|
||||
|
||||
if body.inhibit_login {
|
||||
if !is_guest && body.inhibit_login {
|
||||
return Ok(register::Response {
|
||||
access_token: None,
|
||||
user_id,
|
||||
|
@ -144,10 +184,12 @@ pub fn register_route(
|
|||
}
|
||||
|
||||
// Generate new device id if the user didn't specify one
|
||||
let device_id = body
|
||||
.device_id
|
||||
.clone()
|
||||
.unwrap_or_else(|| utils::random_string(DEVICE_ID_LENGTH).into());
|
||||
let device_id = if is_guest {
|
||||
None
|
||||
} else {
|
||||
body.device_id.clone()
|
||||
}
|
||||
.unwrap_or_else(|| utils::random_string(DEVICE_ID_LENGTH).into());
|
||||
|
||||
// Generate new token for the device
|
||||
let token = utils::random_string(TOKEN_LENGTH);
|
||||
|
@ -168,6 +210,13 @@ pub fn register_route(
|
|||
.into())
|
||||
}
|
||||
|
||||
/// # `POST /_matrix/client/r0/account/password`
|
||||
///
|
||||
/// Changes the password of this account.
|
||||
///
|
||||
/// - Invalidates all other access tokens if logout_devices is true
|
||||
/// - Deletes all other devices and most of their data (to-device events, last seen, etc.) if
|
||||
/// logout_devices is true
|
||||
#[cfg_attr(
|
||||
feature = "conduit_bin",
|
||||
post("/_matrix/client/r0/account/password", data = "<body>")
|
||||
|
@ -225,6 +274,11 @@ pub fn change_password_route(
|
|||
Ok(change_password::Response.into())
|
||||
}
|
||||
|
||||
/// # `GET _matrix/client/r0/account/whoami`
|
||||
///
|
||||
/// Get user_id of this account.
|
||||
///
|
||||
/// - Also works for Application Services
|
||||
#[cfg_attr(
|
||||
feature = "conduit_bin",
|
||||
get("/_matrix/client/r0/account/whoami", data = "<body>")
|
||||
|
@ -237,6 +291,14 @@ pub fn whoami_route(body: Ruma<whoami::Request>) -> ConduitResult<whoami::Respon
|
|||
.into())
|
||||
}
|
||||
|
||||
/// # `POST /_matrix/client/r0/account/deactivate`
|
||||
///
|
||||
/// Deactivate this user's account
|
||||
///
|
||||
/// - Leaves all rooms and rejects all invitations
|
||||
/// - Invalidates all access tokens
|
||||
/// - Deletes all devices
|
||||
/// - Removes ability to log in again
|
||||
#[cfg_attr(
|
||||
feature = "conduit_bin",
|
||||
post("/_matrix/client/r0/account/deactivate", data = "<body>")
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
use super::State;
|
||||
use crate::{ConduitResult, Database, Error, Ruma};
|
||||
use ruma::api::client::{
|
||||
error::ErrorKind,
|
||||
r0::alias::{create_alias, delete_alias, get_alias},
|
||||
use crate::{server_server, ConduitResult, Database, Error, Ruma};
|
||||
use ruma::api::{
|
||||
client::{
|
||||
error::ErrorKind,
|
||||
r0::alias::{create_alias, delete_alias, get_alias},
|
||||
},
|
||||
federation,
|
||||
};
|
||||
|
||||
#[cfg(feature = "conduit_bin")]
|
||||
|
@ -43,12 +46,25 @@ pub fn delete_alias_route(
|
|||
feature = "conduit_bin",
|
||||
get("/_matrix/client/r0/directory/room/<_>", data = "<body>")
|
||||
)]
|
||||
pub fn get_alias_route(
|
||||
pub async fn get_alias_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<get_alias::IncomingRequest>,
|
||||
) -> ConduitResult<get_alias::Response> {
|
||||
if body.room_alias.server_name() != db.globals.server_name() {
|
||||
todo!("ask remote server");
|
||||
let response = server_server::send_request(
|
||||
&db,
|
||||
body.room_alias.server_name().to_string(),
|
||||
federation::query::get_room_information::v1::Request {
|
||||
room_alias: body.room_alias.to_string(),
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
return Ok(get_alias::Response {
|
||||
room_id: response.room_id,
|
||||
servers: response.servers,
|
||||
}
|
||||
.into());
|
||||
}
|
||||
|
||||
let room_id = db
|
||||
|
|
|
@ -5,6 +5,9 @@ use std::collections::BTreeMap;
|
|||
#[cfg(feature = "conduit_bin")]
|
||||
use rocket::get;
|
||||
|
||||
/// # `GET /_matrix/client/r0/capabilities`
|
||||
///
|
||||
/// Get information on this server's supported feature set and other relevent capabilities.
|
||||
#[cfg_attr(feature = "conduit_bin", get("/_matrix/client/r0/capabilities"))]
|
||||
pub fn get_capabilities_route() -> ConduitResult<get_capabilities::Response> {
|
||||
let mut available = BTreeMap::new();
|
||||
|
|
|
@ -1,15 +1,18 @@
|
|||
use super::State;
|
||||
use crate::{ConduitResult, Database, Error, Result, Ruma};
|
||||
use crate::{server_server, ConduitResult, Database, Error, Result, Ruma};
|
||||
use ruma::{
|
||||
api::client::{
|
||||
error::ErrorKind,
|
||||
r0::{
|
||||
directory::{
|
||||
self, get_public_rooms, get_public_rooms_filtered, get_room_visibility,
|
||||
set_room_visibility,
|
||||
api::{
|
||||
client::{
|
||||
error::ErrorKind,
|
||||
r0::{
|
||||
directory::{
|
||||
self, get_public_rooms, get_public_rooms_filtered, get_room_visibility,
|
||||
set_room_visibility,
|
||||
},
|
||||
room,
|
||||
},
|
||||
room,
|
||||
},
|
||||
federation,
|
||||
},
|
||||
events::{
|
||||
room::{avatar, canonical_alias, guest_access, history_visibility, name, topic},
|
||||
|
@ -29,6 +32,46 @@ pub async fn get_public_rooms_filtered_route(
|
|||
db: State<'_, Database>,
|
||||
body: Ruma<get_public_rooms_filtered::IncomingRequest>,
|
||||
) -> ConduitResult<get_public_rooms_filtered::Response> {
|
||||
if let Some(other_server) = body
|
||||
.server
|
||||
.clone()
|
||||
.filter(|server| server != &db.globals.server_name().as_str())
|
||||
{
|
||||
let response = server_server::send_request(
|
||||
&db,
|
||||
other_server,
|
||||
federation::directory::get_public_rooms::v1::Request {
|
||||
limit: body.limit,
|
||||
since: body.since.clone(),
|
||||
room_network: federation::directory::get_public_rooms::v1::RoomNetwork::Matrix,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
return Ok(get_public_rooms_filtered::Response {
|
||||
chunk: response
|
||||
.chunk
|
||||
.into_iter()
|
||||
.map(|c| {
|
||||
// Convert ruma::api::federation::directory::get_public_rooms::v1::PublicRoomsChunk
|
||||
// to ruma::api::client::r0::directory::PublicRoomsChunk
|
||||
Ok::<_, Error>(
|
||||
serde_json::from_str(
|
||||
&serde_json::to_string(&c)
|
||||
.expect("PublicRoomsChunk::to_string always works"),
|
||||
)
|
||||
.expect("federation and client-server PublicRoomsChunk are the same type"),
|
||||
)
|
||||
})
|
||||
.filter_map(|r| r.ok())
|
||||
.collect(),
|
||||
prev_batch: response.prev_batch,
|
||||
next_batch: response.next_batch,
|
||||
total_room_count_estimate: response.total_room_count_estimate,
|
||||
}
|
||||
.into());
|
||||
}
|
||||
|
||||
let limit = body.limit.map_or(10, u64::from);
|
||||
let mut since = 0_u64;
|
||||
|
||||
|
@ -169,26 +212,6 @@ pub async fn get_public_rooms_filtered_route(
|
|||
|
||||
all_rooms.sort_by(|l, r| r.num_joined_members.cmp(&l.num_joined_members));
|
||||
|
||||
/*
|
||||
all_rooms.extend_from_slice(
|
||||
&server_server::send_request(
|
||||
&db,
|
||||
"privacytools.io".to_owned(),
|
||||
ruma::api::federation::v1::get_public_rooms::Request {
|
||||
limit: Some(20_u32.into()),
|
||||
since: None,
|
||||
room_network: ruma::api::federation::v1::get_public_rooms::RoomNetwork::Matrix,
|
||||
},
|
||||
)
|
||||
.await
|
||||
?
|
||||
.chunk
|
||||
.into_iter()
|
||||
.map(|c| serde_json::from_str(&serde_json::to_string(&c)?)?)
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
*/
|
||||
|
||||
let total_room_count_estimate = (all_rooms.len() as u32).into();
|
||||
|
||||
let chunk = all_rooms
|
||||
|
|
|
@ -1,16 +1,24 @@
|
|||
use super::State;
|
||||
use crate::{pdu::PduBuilder, ConduitResult, Database, Error, Ruma};
|
||||
use crate::{
|
||||
client_server, pdu::PduBuilder, server_server, utils, ConduitResult, Database, Error, Ruma,
|
||||
};
|
||||
use ruma::{
|
||||
api::client::{
|
||||
error::ErrorKind,
|
||||
r0::membership::{
|
||||
ban_user, forget_room, get_member_events, invite_user, join_room_by_id,
|
||||
join_room_by_id_or_alias, joined_members, joined_rooms, kick_user, leave_room,
|
||||
unban_user,
|
||||
api::{
|
||||
client::{
|
||||
error::ErrorKind,
|
||||
r0::{
|
||||
alias,
|
||||
membership::{
|
||||
ban_user, forget_room, get_member_events, invite_user, join_room_by_id,
|
||||
join_room_by_id_or_alias, joined_members, joined_rooms, kick_user, leave_room,
|
||||
unban_user,
|
||||
},
|
||||
},
|
||||
},
|
||||
federation,
|
||||
},
|
||||
events::{room::member, EventType},
|
||||
Raw, RoomId,
|
||||
EventId, Raw, RoomId, RoomVersionId,
|
||||
};
|
||||
use std::{collections::BTreeMap, convert::TryFrom};
|
||||
|
||||
|
@ -21,13 +29,81 @@ use rocket::{get, post};
|
|||
feature = "conduit_bin",
|
||||
post("/_matrix/client/r0/rooms/<_>/join", data = "<body>")
|
||||
)]
|
||||
pub fn join_room_by_id_route(
|
||||
pub async fn join_room_by_id_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<join_room_by_id::IncomingRequest>,
|
||||
) -> ConduitResult<join_room_by_id::Response> {
|
||||
let sender_id = body.sender_id.as_ref().expect("user is authenticated");
|
||||
|
||||
// TODO: Ask a remote server if we don't have this room
|
||||
// Ask a remote server if we don't have this room
|
||||
if !db.rooms.exists(&body.room_id)? && body.room_id.server_name() != db.globals.server_name() {
|
||||
let make_join_response = server_server::send_request(
|
||||
&db,
|
||||
body.room_id.server_name().to_string(),
|
||||
federation::membership::create_join_event_template::v1::Request {
|
||||
room_id: body.room_id.clone(),
|
||||
user_id: sender_id.clone(),
|
||||
ver: vec![RoomVersionId::Version5, RoomVersionId::Version6],
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut join_event_stub_value =
|
||||
serde_json::from_str::<serde_json::Value>(make_join_response.event.json().get())
|
||||
.map_err(|_| {
|
||||
Error::BadServerResponse("Invalid make_join event json received from server.")
|
||||
})?;
|
||||
|
||||
let join_event_stub =
|
||||
join_event_stub_value
|
||||
.as_object_mut()
|
||||
.ok_or(Error::BadServerResponse(
|
||||
"Invalid make join event object received from server.",
|
||||
))?;
|
||||
|
||||
join_event_stub.insert(
|
||||
"origin".to_owned(),
|
||||
db.globals.server_name().to_owned().to_string().into(),
|
||||
);
|
||||
join_event_stub.insert(
|
||||
"origin_server_ts".to_owned(),
|
||||
utils::millis_since_unix_epoch().into(),
|
||||
);
|
||||
|
||||
// Generate event id
|
||||
let event_id = EventId::try_from(&*format!(
|
||||
"${}",
|
||||
ruma::signatures::reference_hash(&join_event_stub_value)
|
||||
.expect("ruma can calculate reference hashes")
|
||||
))
|
||||
.expect("ruma's reference hashes are valid event ids");
|
||||
|
||||
// We don't leave the event id into the pdu because that's only allowed in v1 or v2 rooms
|
||||
let join_event_stub = join_event_stub_value.as_object_mut().unwrap();
|
||||
join_event_stub.remove("event_id");
|
||||
|
||||
ruma::signatures::hash_and_sign_event(
|
||||
db.globals.server_name().as_str(),
|
||||
db.globals.keypair(),
|
||||
&mut join_event_stub_value,
|
||||
)
|
||||
.expect("event is valid, we just created it");
|
||||
|
||||
let send_join_response = server_server::send_request(
|
||||
&db,
|
||||
body.room_id.server_name().to_string(),
|
||||
federation::membership::create_join_event::v2::Request {
|
||||
room_id: body.room_id.clone(),
|
||||
event_id,
|
||||
pdu_stub: serde_json::from_value::<Raw<_>>(join_event_stub_value)
|
||||
.expect("Raw::from_value always works"),
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
dbg!(send_join_response);
|
||||
todo!("Take send_join_response and 'create' the room using that data");
|
||||
}
|
||||
|
||||
let event = member::MemberEventContent {
|
||||
membership: member::MembershipState::Join,
|
||||
|
@ -61,16 +137,28 @@ pub fn join_room_by_id_route(
|
|||
feature = "conduit_bin",
|
||||
post("/_matrix/client/r0/join/<_>", data = "<body>")
|
||||
)]
|
||||
pub fn join_room_by_id_or_alias_route(
|
||||
pub async fn join_room_by_id_or_alias_route(
|
||||
db: State<'_, Database>,
|
||||
db2: State<'_, Database>,
|
||||
body: Ruma<join_room_by_id_or_alias::Request>,
|
||||
) -> ConduitResult<join_room_by_id_or_alias::Response> {
|
||||
let room_id = RoomId::try_from(body.room_id_or_alias.clone()).or_else(|alias| {
|
||||
Ok::<_, Error>(db.rooms.id_from_alias(&alias)?.ok_or(Error::BadRequest(
|
||||
ErrorKind::NotFound,
|
||||
"Room not found (TODO: Federation).",
|
||||
))?)
|
||||
})?;
|
||||
let room_id = match RoomId::try_from(body.room_id_or_alias.clone()) {
|
||||
Ok(room_id) => room_id,
|
||||
Err(room_alias) => {
|
||||
client_server::get_alias_route(
|
||||
db,
|
||||
Ruma {
|
||||
body: alias::get_alias::IncomingRequest { room_alias },
|
||||
sender_id: body.sender_id.clone(),
|
||||
device_id: body.device_id.clone(),
|
||||
json_body: None,
|
||||
},
|
||||
)
|
||||
.await?
|
||||
.0
|
||||
.room_id
|
||||
}
|
||||
};
|
||||
|
||||
let body = Ruma {
|
||||
sender_id: body.sender_id.clone(),
|
||||
|
@ -83,7 +171,7 @@ pub fn join_room_by_id_or_alias_route(
|
|||
};
|
||||
|
||||
Ok(join_room_by_id_or_alias::Response {
|
||||
room_id: join_room_by_id_route(db, body)?.0.room_id,
|
||||
room_id: join_room_by_id_route(db2, body).await?.0.room_id,
|
||||
}
|
||||
.into())
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ mod push;
|
|||
mod read_marker;
|
||||
mod redact;
|
||||
mod room;
|
||||
mod search;
|
||||
mod session;
|
||||
mod state;
|
||||
mod sync;
|
||||
|
@ -47,6 +48,7 @@ pub use push::*;
|
|||
pub use read_marker::*;
|
||||
pub use redact::*;
|
||||
pub use room::*;
|
||||
pub use search::*;
|
||||
pub use session::*;
|
||||
pub use state::*;
|
||||
pub use sync::*;
|
||||
|
|
|
@ -92,13 +92,6 @@ pub fn create_room_route(
|
|||
&db.account_data,
|
||||
)?;
|
||||
|
||||
// Figure out preset. We need it for power levels and preset specific events
|
||||
let visibility = body.visibility.unwrap_or(room::Visibility::Private);
|
||||
let preset = body.preset.unwrap_or_else(|| match visibility {
|
||||
room::Visibility::Private => create_room::RoomPreset::PrivateChat,
|
||||
room::Visibility::Public => create_room::RoomPreset::PublicChat,
|
||||
});
|
||||
|
||||
// 3. Power levels
|
||||
let mut users = BTreeMap::new();
|
||||
users.insert(sender_id.clone(), 100.into());
|
||||
|
@ -142,6 +135,14 @@ pub fn create_room_route(
|
|||
)?;
|
||||
|
||||
// 4. Events set by preset
|
||||
|
||||
// Figure out preset. We need it for preset specific events
|
||||
let visibility = body.visibility.unwrap_or(room::Visibility::Private);
|
||||
let preset = body.preset.unwrap_or_else(|| match visibility {
|
||||
room::Visibility::Private => create_room::RoomPreset::PrivateChat,
|
||||
room::Visibility::Public => create_room::RoomPreset::PublicChat,
|
||||
});
|
||||
|
||||
// 4.1 Join Rules
|
||||
db.rooms.append_pdu(
|
||||
PduBuilder {
|
||||
|
|
86
src/client_server/search.rs
Normal file
86
src/client_server/search.rs
Normal file
|
@ -0,0 +1,86 @@
|
|||
use super::State;
|
||||
use crate::{ConduitResult, Database, Error, Ruma};
|
||||
use js_int::uint;
|
||||
use ruma::api::client::{error::ErrorKind, r0::search::search_events};
|
||||
|
||||
#[cfg(feature = "conduit_bin")]
|
||||
use rocket::post;
|
||||
use search_events::{ResultCategories, ResultRoomEvents, SearchResult};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "conduit_bin",
|
||||
post("/_matrix/client/r0/search", data = "<body>")
|
||||
)]
|
||||
pub fn search_events_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<search_events::Request>,
|
||||
) -> ConduitResult<search_events::Response> {
|
||||
let sender_id = body.sender_id.as_ref().expect("user is authenticated");
|
||||
|
||||
let search_criteria = body.search_categories.room_events.as_ref().unwrap();
|
||||
let filter = search_criteria.filter.as_ref().unwrap();
|
||||
|
||||
let room_id = filter.rooms.as_ref().unwrap().first().unwrap();
|
||||
|
||||
let limit = filter.limit.map_or(10, |l| u64::from(l) as usize);
|
||||
|
||||
if !db.rooms.is_joined(sender_id, &room_id)? {
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::Forbidden,
|
||||
"You don't have permission to view this room.",
|
||||
));
|
||||
}
|
||||
|
||||
let skip = match body.next_batch.as_ref().map(|s| s.parse()) {
|
||||
Some(Ok(s)) => s,
|
||||
Some(Err(_)) => {
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"Invalid next_batch token.",
|
||||
))
|
||||
}
|
||||
None => 0, // Default to the start
|
||||
};
|
||||
|
||||
let search = db
|
||||
.rooms
|
||||
.search_pdus(&room_id, &search_criteria.search_term)?;
|
||||
|
||||
let results = search
|
||||
.0
|
||||
.map(|result| {
|
||||
Ok::<_, Error>(SearchResult {
|
||||
context: None,
|
||||
rank: None,
|
||||
result: db
|
||||
.rooms
|
||||
.get_pdu_from_id(&result)?
|
||||
.map(|pdu| pdu.to_room_event()),
|
||||
})
|
||||
})
|
||||
.filter_map(|r| r.ok())
|
||||
.skip(skip)
|
||||
.take(limit)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let next_batch = if results.len() < limit as usize {
|
||||
None
|
||||
} else {
|
||||
Some((skip + limit).to_string())
|
||||
};
|
||||
|
||||
Ok(search_events::Response {
|
||||
search_categories: ResultCategories {
|
||||
room_events: Some(ResultRoomEvents {
|
||||
count: uint!(0), // TODO
|
||||
groups: BTreeMap::new(), // TODO
|
||||
next_batch,
|
||||
results,
|
||||
state: BTreeMap::new(), // TODO
|
||||
highlights: search.1,
|
||||
}),
|
||||
},
|
||||
}
|
||||
.into())
|
||||
}
|
|
@ -12,14 +12,28 @@ use ruma::{
|
|||
#[cfg(feature = "conduit_bin")]
|
||||
use rocket::{get, post};
|
||||
|
||||
/// # `GET /_matrix/client/r0/login`
|
||||
///
|
||||
/// Get the homeserver's supported login types. One of these should be used as the `type` field
|
||||
/// when logging in.
|
||||
#[cfg_attr(feature = "conduit_bin", get("/_matrix/client/r0/login"))]
|
||||
pub fn get_login_route() -> ConduitResult<get_login_types::Response> {
|
||||
pub fn get_login_types_route() -> ConduitResult<get_login_types::Response> {
|
||||
Ok(get_login_types::Response {
|
||||
flows: vec![get_login_types::LoginType::Password],
|
||||
}
|
||||
.into())
|
||||
}
|
||||
|
||||
/// # `POST /_matrix/client/r0/login`
|
||||
///
|
||||
/// Authenticates the user and returns an access token it can use in subsequent requests.
|
||||
///
|
||||
/// - The returned access token is associated with the user and device
|
||||
/// - Old access tokens of that device should be invalidated
|
||||
/// - If `device_id` is unknown, a new device will be created
|
||||
///
|
||||
/// Note: You can use [`GET /_matrix/client/r0/login`](fn.get_supported_versions_route.html) to see
|
||||
/// supported login types.
|
||||
#[cfg_attr(
|
||||
feature = "conduit_bin",
|
||||
post("/_matrix/client/r0/login", data = "<body>")
|
||||
|
@ -74,6 +88,7 @@ pub fn login_route(
|
|||
// Generate a new token for the device
|
||||
let token = utils::random_string(TOKEN_LENGTH);
|
||||
|
||||
// TODO: Don't always create a new device
|
||||
// Add device
|
||||
db.users.create_device(
|
||||
&user_id,
|
||||
|
@ -92,6 +107,12 @@ pub fn login_route(
|
|||
.into())
|
||||
}
|
||||
|
||||
/// # `POST /_matrix/client/r0/logout`
|
||||
///
|
||||
/// Log out the current device.
|
||||
///
|
||||
/// - Invalidates the access token
|
||||
/// - Deletes the device and most of it's data (to-device events, last seen, etc.)
|
||||
#[cfg_attr(
|
||||
feature = "conduit_bin",
|
||||
post("/_matrix/client/r0/logout", data = "<body>")
|
||||
|
@ -108,6 +129,15 @@ pub fn logout_route(
|
|||
Ok(logout::Response.into())
|
||||
}
|
||||
|
||||
/// # `POST /_matrix/client/r0/logout/all`
|
||||
///
|
||||
/// Log out all devices of this user.
|
||||
///
|
||||
/// - Invalidates all access tokens
|
||||
/// - Deletes devices and most of their data (to-device events, last seen, etc.)
|
||||
///
|
||||
/// Note: This is equivalent to calling [`GET /_matrix/client/r0/logout`](fn.logout_route.html)
|
||||
/// from each device of this user.
|
||||
#[cfg_attr(
|
||||
feature = "conduit_bin",
|
||||
post("/_matrix/client/r0/logout/all", data = "<body>")
|
||||
|
|
|
@ -2,17 +2,29 @@ use super::State;
|
|||
use crate::{ConduitResult, Database, Error, Ruma};
|
||||
use ruma::{
|
||||
api::client::r0::sync::sync_events,
|
||||
events::{AnySyncEphemeralRoomEvent, EventType},
|
||||
Raw,
|
||||
events::{room::member::MembershipState, AnySyncEphemeralRoomEvent, EventType},
|
||||
Raw, RoomId, UserId,
|
||||
};
|
||||
|
||||
#[cfg(feature = "conduit_bin")]
|
||||
use rocket::{get, tokio};
|
||||
use std::{
|
||||
collections::{hash_map, BTreeMap, HashMap, HashSet},
|
||||
convert::TryFrom,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
/// # `GET /_matrix/client/r0/sync`
|
||||
///
|
||||
/// Synchronize the client's state with the latest state on the server.
|
||||
///
|
||||
/// - This endpoint takes a `since` parameter which should be the `next_batch` value from a
|
||||
/// previous request.
|
||||
/// - Calling this endpoint without a `since` parameter will return all recent events, the state
|
||||
/// of all rooms and more data. This should only be called on the initial login of the device.
|
||||
/// - To get incremental updates, you can call this endpoint with a `since` parameter. This will
|
||||
/// return all recent events, state updates and more data that happened since the last /sync
|
||||
/// request.
|
||||
#[cfg_attr(
|
||||
feature = "conduit_bin",
|
||||
get("/_matrix/client/r0/sync", data = "<body>")
|
||||
|
@ -40,7 +52,9 @@ pub async fn sync_events_route(
|
|||
.unwrap_or(0);
|
||||
|
||||
let mut presence_updates = HashMap::new();
|
||||
let mut left_encrypted_users = HashSet::new(); // Users that have left any encrypted rooms the sender was in
|
||||
let mut device_list_updates = HashSet::new();
|
||||
let mut device_list_left = HashSet::new();
|
||||
|
||||
// Look for device list updates of this account
|
||||
device_list_updates.extend(
|
||||
|
@ -67,46 +81,100 @@ pub async fn sync_events_route(
|
|||
.rev()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let send_notification_counts = !timeline_pdus.is_empty();
|
||||
|
||||
// They /sync response doesn't always return all messages, so we say the output is
|
||||
// limited unless there are events in non_timeline_pdus
|
||||
//let mut limited = false;
|
||||
let mut limited = false;
|
||||
|
||||
let mut state_pdus = Vec::new();
|
||||
for pdu in non_timeline_pdus {
|
||||
if pdu.state_key.is_some() {
|
||||
state_pdus.push(pdu);
|
||||
}
|
||||
limited = true;
|
||||
}
|
||||
|
||||
let encrypted_room = db
|
||||
.rooms
|
||||
.room_state_get(&room_id, &EventType::RoomEncryption, "")?
|
||||
.is_some();
|
||||
|
||||
// TODO: optimize this?
|
||||
let mut send_member_count = false;
|
||||
let mut joined_since_last_sync = false;
|
||||
let mut send_notification_counts = false;
|
||||
for pdu in db
|
||||
let mut new_encrypted_room = false;
|
||||
for (state_key, pdu) in db
|
||||
.rooms
|
||||
.pdus_since(&sender_id, &room_id, since)?
|
||||
.filter_map(|r| r.ok())
|
||||
.filter_map(|pdu| Some((pdu.state_key.clone()?, pdu)))
|
||||
{
|
||||
let pdu = pdu?;
|
||||
send_notification_counts = true;
|
||||
if pdu.kind == EventType::RoomMember {
|
||||
send_member_count = true;
|
||||
if !joined_since_last_sync && pdu.state_key == Some(sender_id.to_string()) {
|
||||
let content = serde_json::from_value::<
|
||||
Raw<ruma::events::room::member::MemberEventContent>,
|
||||
>(pdu.content.clone())
|
||||
.expect("Raw::from_value always works")
|
||||
.deserialize()
|
||||
.map_err(|_| Error::bad_database("Invalid PDU in database."))?;
|
||||
if content.membership == ruma::events::room::member::MembershipState::Join {
|
||||
joined_since_last_sync = true;
|
||||
// Both send_member_count and joined_since_last_sync are set. There's
|
||||
// nothing more to do
|
||||
break;
|
||||
|
||||
let content = serde_json::from_value::<
|
||||
Raw<ruma::events::room::member::MemberEventContent>,
|
||||
>(pdu.content.clone())
|
||||
.expect("Raw::from_value always works")
|
||||
.deserialize()
|
||||
.map_err(|_| Error::bad_database("Invalid PDU in database."))?;
|
||||
|
||||
if pdu.state_key == Some(sender_id.to_string())
|
||||
&& content.membership == MembershipState::Join
|
||||
{
|
||||
joined_since_last_sync = true;
|
||||
} else if encrypted_room && content.membership == MembershipState::Join {
|
||||
// A new user joined an encrypted room
|
||||
let user_id = UserId::try_from(state_key)
|
||||
.map_err(|_| Error::bad_database("Invalid UserId in member PDU."))?;
|
||||
// Add encryption update if we didn't share an encrypted room already
|
||||
if !share_encrypted_room(&db, &sender_id, &user_id, &room_id) {
|
||||
device_list_updates.insert(user_id);
|
||||
}
|
||||
} else if encrypted_room && content.membership == MembershipState::Leave {
|
||||
// Write down users that have left encrypted rooms we are in
|
||||
left_encrypted_users.insert(
|
||||
UserId::try_from(state_key)
|
||||
.map_err(|_| Error::bad_database("Invalid UserId in member PDU."))?,
|
||||
);
|
||||
}
|
||||
} else if pdu.kind == EventType::RoomEncryption {
|
||||
new_encrypted_room = true;
|
||||
}
|
||||
}
|
||||
|
||||
let members = db.rooms.room_state_type(&room_id, &EventType::RoomMember)?;
|
||||
if joined_since_last_sync && encrypted_room || new_encrypted_room {
|
||||
// If the user is in a new encrypted room, give them all joined users
|
||||
device_list_updates.extend(
|
||||
db.rooms
|
||||
.room_members(&room_id)
|
||||
.filter_map(|user_id| {
|
||||
Some(
|
||||
UserId::try_from(user_id.ok()?.clone())
|
||||
.map_err(|_| {
|
||||
Error::bad_database("Invalid member event state key in db.")
|
||||
})
|
||||
.ok()?,
|
||||
)
|
||||
})
|
||||
.filter(|user_id| {
|
||||
// Don't send key updates from the sender to the sender
|
||||
sender_id != user_id
|
||||
})
|
||||
.filter(|user_id| {
|
||||
// Only send keys if the sender doesn't share an encrypted room with the target already
|
||||
!share_encrypted_room(&db, sender_id, user_id, &room_id)
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
// Look for device list updates in this room
|
||||
device_list_updates.extend(
|
||||
db.users
|
||||
.keys_changed(&room_id.to_string(), since, None)
|
||||
.filter_map(|r| r.ok()),
|
||||
);
|
||||
|
||||
let (joined_member_count, invited_member_count, heroes) = if send_member_count {
|
||||
let joined_member_count = db.rooms.room_members(&room_id).count();
|
||||
|
@ -133,35 +201,17 @@ pub async fn sync_events_route(
|
|||
.map_err(|_| Error::bad_database("Invalid member event in database."))?;
|
||||
|
||||
if let Some(state_key) = &pdu.state_key {
|
||||
let current_content = serde_json::from_value::<
|
||||
Raw<ruma::events::room::member::MemberEventContent>,
|
||||
>(
|
||||
members
|
||||
.get(state_key)
|
||||
.ok_or_else(|| {
|
||||
Error::bad_database(
|
||||
"A user that joined once has no member event anymore.",
|
||||
)
|
||||
})?
|
||||
.content
|
||||
.clone(),
|
||||
)
|
||||
.expect("Raw::from_value always works")
|
||||
.deserialize()
|
||||
.map_err(|_| {
|
||||
Error::bad_database("Invalid member event in database.")
|
||||
let user_id = UserId::try_from(state_key.clone()).map_err(|_| {
|
||||
Error::bad_database("Invalid UserId in member PDU.")
|
||||
})?;
|
||||
|
||||
// The membership was and still is invite or join
|
||||
if matches!(
|
||||
content.membership,
|
||||
ruma::events::room::member::MembershipState::Join
|
||||
| ruma::events::room::member::MembershipState::Invite
|
||||
) && matches!(
|
||||
current_content.membership,
|
||||
ruma::events::room::member::MembershipState::Join
|
||||
| ruma::events::room::member::MembershipState::Invite
|
||||
) {
|
||||
MembershipState::Join | MembershipState::Invite
|
||||
) && (db.rooms.is_joined(&user_id, &room_id)?
|
||||
|| db.rooms.is_invited(&user_id, &room_id)?)
|
||||
{
|
||||
Ok::<_, Error>(Some(state_key.clone()))
|
||||
} else {
|
||||
Ok(None)
|
||||
|
@ -274,7 +324,7 @@ pub async fn sync_events_route(
|
|||
notification_count,
|
||||
},
|
||||
timeline: sync_events::Timeline {
|
||||
limited: joined_since_last_sync,
|
||||
limited: limited || joined_since_last_sync,
|
||||
prev_batch,
|
||||
events: room_events,
|
||||
},
|
||||
|
@ -297,13 +347,6 @@ pub async fn sync_events_route(
|
|||
joined_rooms.insert(room_id.clone(), joined_room);
|
||||
}
|
||||
|
||||
// Look for device list updates in this room
|
||||
device_list_updates.extend(
|
||||
db.users
|
||||
.keys_changed(&room_id.to_string(), since, None)
|
||||
.filter_map(|r| r.ok()),
|
||||
);
|
||||
|
||||
// Take presence updates from this room
|
||||
for (user_id, presence) in
|
||||
db.rooms
|
||||
|
@ -348,31 +391,6 @@ pub async fn sync_events_route(
|
|||
.map(|pdu| pdu.to_sync_room_event())
|
||||
.collect();
|
||||
|
||||
// TODO: Only until leave point
|
||||
let mut edus = db
|
||||
.rooms
|
||||
.edus
|
||||
.roomlatests_since(&room_id, since)?
|
||||
.filter_map(|r| r.ok()) // Filter out buggy events
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if db
|
||||
.rooms
|
||||
.edus
|
||||
.last_roomactive_update(&room_id, &db.globals)?
|
||||
> since
|
||||
{
|
||||
edus.push(
|
||||
serde_json::from_str(
|
||||
&serde_json::to_string(&AnySyncEphemeralRoomEvent::Typing(
|
||||
db.rooms.edus.roomactives_all(&room_id)?,
|
||||
))
|
||||
.expect("event is valid, we just created it"),
|
||||
)
|
||||
.expect("event is valid, we just created it"),
|
||||
);
|
||||
}
|
||||
|
||||
let left_room = sync_events::LeftRoom {
|
||||
account_data: sync_events::AccountData { events: Vec::new() },
|
||||
timeline: sync_events::Timeline {
|
||||
|
@ -383,6 +401,49 @@ pub async fn sync_events_route(
|
|||
state: sync_events::State { events: Vec::new() },
|
||||
};
|
||||
|
||||
let mut left_since_last_sync = false;
|
||||
for pdu in db.rooms.pdus_since(&sender_id, &room_id, since)? {
|
||||
let pdu = pdu?;
|
||||
if pdu.kind == EventType::RoomMember && pdu.state_key == Some(sender_id.to_string()) {
|
||||
let content = serde_json::from_value::<
|
||||
Raw<ruma::events::room::member::MemberEventContent>,
|
||||
>(pdu.content.clone())
|
||||
.expect("Raw::from_value always works")
|
||||
.deserialize()
|
||||
.map_err(|_| Error::bad_database("Invalid PDU in database."))?;
|
||||
|
||||
if content.membership == MembershipState::Leave {
|
||||
left_since_last_sync = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if left_since_last_sync {
|
||||
device_list_left.extend(
|
||||
db.rooms
|
||||
.room_members(&room_id)
|
||||
.filter_map(|user_id| {
|
||||
Some(
|
||||
UserId::try_from(user_id.ok()?.clone())
|
||||
.map_err(|_| {
|
||||
Error::bad_database("Invalid member event state key in db.")
|
||||
})
|
||||
.ok()?,
|
||||
)
|
||||
})
|
||||
.filter(|user_id| {
|
||||
// Don't send key updates from the sender to the sender
|
||||
sender_id != user_id
|
||||
})
|
||||
.filter(|user_id| {
|
||||
// Only send if the sender doesn't share any encrypted room with the target
|
||||
// anymore
|
||||
!share_encrypted_room(&db, sender_id, user_id, &room_id)
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
if !left_room.is_empty() {
|
||||
left_rooms.insert(room_id.clone(), left_room);
|
||||
}
|
||||
|
@ -392,23 +453,19 @@ pub async fn sync_events_route(
|
|||
for room_id in db.rooms.rooms_invited(&sender_id) {
|
||||
let room_id = room_id?;
|
||||
let mut invited_since_last_sync = false;
|
||||
for pdu in db
|
||||
.rooms
|
||||
.pdus_since(&sender_id, &room_id, since)?
|
||||
{
|
||||
for pdu in db.rooms.pdus_since(&sender_id, &room_id, since)? {
|
||||
let pdu = pdu?;
|
||||
if pdu.kind == EventType::RoomMember {
|
||||
if pdu.state_key == Some(sender_id.to_string()) {
|
||||
let content = serde_json::from_value::<
|
||||
Raw<ruma::events::room::member::MemberEventContent>,
|
||||
>(pdu.content.clone())
|
||||
.expect("Raw::from_value always works")
|
||||
.deserialize()
|
||||
.map_err(|_| Error::bad_database("Invalid PDU in database."))?;
|
||||
if content.membership == ruma::events::room::member::MembershipState::Invite {
|
||||
invited_since_last_sync = true;
|
||||
break;
|
||||
}
|
||||
if pdu.kind == EventType::RoomMember && pdu.state_key == Some(sender_id.to_string()) {
|
||||
let content = serde_json::from_value::<
|
||||
Raw<ruma::events::room::member::MemberEventContent>,
|
||||
>(pdu.content.clone())
|
||||
.expect("Raw::from_value always works")
|
||||
.deserialize()
|
||||
.map_err(|_| Error::bad_database("Invalid PDU in database."))?;
|
||||
|
||||
if content.membership == MembershipState::Invite {
|
||||
invited_since_last_sync = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -433,6 +490,27 @@ pub async fn sync_events_route(
|
|||
}
|
||||
}
|
||||
|
||||
for user_id in left_encrypted_users {
|
||||
// If the user doesn't share an encrypted room with the target anymore, we need to tell
|
||||
// them
|
||||
if db
|
||||
.rooms
|
||||
.get_shared_rooms(vec![sender_id.clone(), user_id.clone()])
|
||||
.filter_map(|r| r.ok())
|
||||
.filter_map(|other_room_id| {
|
||||
Some(
|
||||
db.rooms
|
||||
.room_state_get(&other_room_id, &EventType::RoomEncryption, "")
|
||||
.ok()?
|
||||
.is_some(),
|
||||
)
|
||||
})
|
||||
.all(|encrypted| !encrypted)
|
||||
{
|
||||
device_list_left.insert(user_id);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove all to-device events the device received *last time*
|
||||
db.users
|
||||
.remove_to_device_events(sender_id, device_id, since)?;
|
||||
|
@ -464,7 +542,7 @@ pub async fn sync_events_route(
|
|||
},
|
||||
device_lists: sync_events::DeviceLists {
|
||||
changed: device_list_updates.into_iter().collect(),
|
||||
left: Vec::new(), // TODO
|
||||
left: device_list_left.into_iter().collect(),
|
||||
},
|
||||
device_one_time_keys_count: if db.users.last_one_time_keys_update(sender_id)? > since {
|
||||
db.users.count_one_time_keys(sender_id, device_id)?
|
||||
|
@ -500,3 +578,24 @@ pub async fn sync_events_route(
|
|||
|
||||
Ok(response.into())
|
||||
}
|
||||
|
||||
fn share_encrypted_room(
|
||||
db: &Database,
|
||||
sender_id: &UserId,
|
||||
user_id: &UserId,
|
||||
ignore_room: &RoomId,
|
||||
) -> bool {
|
||||
db.rooms
|
||||
.get_shared_rooms(vec![sender_id.clone(), user_id.clone()])
|
||||
.filter_map(|r| r.ok())
|
||||
.filter(|room_id| room_id != ignore_room)
|
||||
.filter_map(|other_room_id| {
|
||||
Some(
|
||||
db.rooms
|
||||
.room_state_get(&other_room_id, &EventType::RoomEncryption, "")
|
||||
.ok()?
|
||||
.is_some(),
|
||||
)
|
||||
})
|
||||
.any(|encrypted| encrypted)
|
||||
}
|
||||
|
|
|
@ -5,6 +5,16 @@ use std::collections::BTreeMap;
|
|||
#[cfg(feature = "conduit_bin")]
|
||||
use rocket::get;
|
||||
|
||||
/// # `GET /_matrix/client/versions`
|
||||
///
|
||||
/// Get the versions of the specification and unstable features supported by this server.
|
||||
///
|
||||
/// - Versions take the form MAJOR.MINOR.PATCH
|
||||
/// - Only the latest PATCH release will be reported for each MAJOR.MINOR value
|
||||
/// - Unstable features should be namespaced and may include version information in their name
|
||||
///
|
||||
/// Note: Unstable features are used while developing new features. Clients should avoid using
|
||||
/// unstable features in their stable releases
|
||||
#[cfg_attr(feature = "conduit_bin", get("/_matrix/client/versions"))]
|
||||
pub fn get_supported_versions_route() -> ConduitResult<get_supported_versions::Response> {
|
||||
let mut unstable_features = BTreeMap::new();
|
||||
|
|
|
@ -104,6 +104,8 @@ impl Database {
|
|||
aliasid_alias: db.open_tree("alias_roomid")?,
|
||||
publicroomids: db.open_tree("publicroomids")?,
|
||||
|
||||
tokenids: db.open_tree("tokenids")?,
|
||||
|
||||
userroomid_joined: db.open_tree("userroomid_joined")?,
|
||||
roomuserid_joined: db.open_tree("roomuserid_joined")?,
|
||||
userroomid_invited: db.open_tree("userroomid_invited")?,
|
||||
|
@ -127,7 +129,6 @@ impl Database {
|
|||
|
||||
pub async fn watch(&self, user_id: &UserId, device_id: &DeviceId) {
|
||||
let userid_bytes = user_id.to_string().as_bytes().to_vec();
|
||||
|
||||
let mut userid_prefix = userid_bytes.clone();
|
||||
userid_prefix.push(0xff);
|
||||
|
||||
|
@ -151,7 +152,8 @@ impl Database {
|
|||
|
||||
// Events for rooms we are in
|
||||
for room_id in self.rooms.rooms_joined(user_id).filter_map(|r| r.ok()) {
|
||||
let mut roomid_prefix = room_id.to_string().as_bytes().to_vec();
|
||||
let roomid_bytes = room_id.to_string().as_bytes().to_vec();
|
||||
let mut roomid_prefix = roomid_bytes.clone();
|
||||
roomid_prefix.push(0xff);
|
||||
|
||||
// PDUs
|
||||
|
@ -162,7 +164,7 @@ impl Database {
|
|||
self.rooms
|
||||
.edus
|
||||
.roomid_lastroomactiveupdate
|
||||
.watch_prefix(&roomid_prefix),
|
||||
.watch_prefix(&roomid_bytes),
|
||||
);
|
||||
|
||||
futures.push(
|
||||
|
|
|
@ -35,6 +35,8 @@ pub struct Rooms {
|
|||
pub(super) aliasid_alias: sled::Tree, // AliasId = RoomId + Count
|
||||
pub(super) publicroomids: sled::Tree,
|
||||
|
||||
pub(super) tokenids: sled::Tree, // TokenId = RoomId + Token + PduId
|
||||
|
||||
pub(super) userroomid_joined: sled::Tree,
|
||||
pub(super) roomuserid_joined: sled::Tree,
|
||||
pub(super) userroomid_invited: sled::Tree,
|
||||
|
@ -562,7 +564,7 @@ impl Rooms {
|
|||
self.pduid_pdu.insert(&pdu_id, &*pdu_json.to_string())?;
|
||||
|
||||
self.eventid_pduid
|
||||
.insert(pdu.event_id.to_string(), pdu_id)?;
|
||||
.insert(pdu.event_id.to_string(), pdu_id.clone())?;
|
||||
|
||||
if let Some(state_key) = pdu.state_key {
|
||||
let mut key = room_id.to_string().as_bytes().to_vec();
|
||||
|
@ -616,6 +618,21 @@ impl Rooms {
|
|||
)?;
|
||||
}
|
||||
}
|
||||
EventType::RoomMessage => {
|
||||
if let Some(body) = content.get("body").and_then(|b| b.as_str()) {
|
||||
for word in body
|
||||
.split_terminator(|c: char| !c.is_alphanumeric())
|
||||
.map(str::to_lowercase)
|
||||
{
|
||||
let mut key = room_id.to_string().as_bytes().to_vec();
|
||||
key.push(0xff);
|
||||
key.extend_from_slice(word.as_bytes());
|
||||
key.push(0xff);
|
||||
key.extend_from_slice(&pdu_id);
|
||||
self.tokenids.insert(key, &[])?;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
self.edus.room_read_set(&room_id, &sender, index)?;
|
||||
|
@ -928,6 +945,95 @@ impl Rooms {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn search_pdus<'a>(
|
||||
&'a self,
|
||||
room_id: &RoomId,
|
||||
search_string: &str,
|
||||
) -> Result<(impl Iterator<Item = IVec> + 'a, Vec<String>)> {
|
||||
let mut prefix = room_id.to_string().as_bytes().to_vec();
|
||||
prefix.push(0xff);
|
||||
|
||||
let words = search_string
|
||||
.split_terminator(|c: char| !c.is_alphanumeric())
|
||||
.map(str::to_lowercase)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let iterators = words.clone().into_iter().map(move |word| {
|
||||
let mut prefix2 = prefix.clone();
|
||||
prefix2.extend_from_slice(word.as_bytes());
|
||||
prefix2.push(0xff);
|
||||
self.tokenids
|
||||
.scan_prefix(&prefix2)
|
||||
.keys()
|
||||
.rev() // Newest pdus first
|
||||
.filter_map(|r| r.ok())
|
||||
.map(|key| {
|
||||
let pduid_index = key
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, &b)| b == 0xff)
|
||||
.nth(1)
|
||||
.ok_or_else(|| Error::bad_database("Invalid tokenid in db."))?
|
||||
.0
|
||||
+ 1; // +1 because the pdu id starts AFTER the separator
|
||||
|
||||
let pdu_id = key.subslice(pduid_index, key.len() - pduid_index);
|
||||
|
||||
Ok::<_, Error>(pdu_id)
|
||||
})
|
||||
.filter_map(|r| r.ok())
|
||||
});
|
||||
|
||||
Ok((
|
||||
utils::common_elements(iterators, |a, b| {
|
||||
// We compare b with a because we reversed the iterator earlier
|
||||
b.cmp(a)
|
||||
})
|
||||
.unwrap(),
|
||||
words,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn get_shared_rooms<'a>(
|
||||
&'a self,
|
||||
users: Vec<UserId>,
|
||||
) -> impl Iterator<Item = Result<RoomId>> + 'a {
|
||||
let iterators = users.into_iter().map(move |user_id| {
|
||||
let mut prefix = user_id.as_bytes().to_vec();
|
||||
prefix.push(0xff);
|
||||
|
||||
self.userroomid_joined
|
||||
.scan_prefix(&prefix)
|
||||
.keys()
|
||||
.filter_map(|r| r.ok())
|
||||
.map(|key| {
|
||||
let roomid_index = key
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, &b)| b == 0xff)
|
||||
.nth(0)
|
||||
.ok_or_else(|| Error::bad_database("Invalid userroomid_joined in db."))?
|
||||
.0
|
||||
+ 1; // +1 because the room id starts AFTER the separator
|
||||
|
||||
let room_id = key.subslice(roomid_index, key.len() - roomid_index);
|
||||
|
||||
Ok::<_, Error>(room_id)
|
||||
})
|
||||
.filter_map(|r| r.ok())
|
||||
});
|
||||
|
||||
// We use the default compare function because keys are sorted correctly (not reversed)
|
||||
utils::common_elements(iterators, Ord::cmp)
|
||||
.expect("users is not empty")
|
||||
.map(|bytes| {
|
||||
RoomId::try_from(utils::string_from_bytes(&*bytes).map_err(|_| {
|
||||
Error::bad_database("Invalid RoomId bytes in userroomid_joined")
|
||||
})?)
|
||||
.map_err(|_| Error::bad_database("Invalid RoomId in userroomid_joined."))
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns an iterator over all joined members of a room.
|
||||
pub fn room_members(&self, room_id: &RoomId) -> impl Iterator<Item = Result<UserId>> {
|
||||
self.roomuserid_joined
|
||||
|
|
|
@ -408,19 +408,7 @@ impl Users {
|
|||
&*serde_json::to_string(&device_keys).expect("DeviceKeys::to_string always works"),
|
||||
)?;
|
||||
|
||||
let count = globals.next_count()?.to_be_bytes();
|
||||
for room_id in rooms.rooms_joined(&user_id) {
|
||||
let mut key = room_id?.to_string().as_bytes().to_vec();
|
||||
key.push(0xff);
|
||||
key.extend_from_slice(&count);
|
||||
|
||||
self.keychangeid_userid.insert(key, &*user_id.to_string())?;
|
||||
}
|
||||
|
||||
let mut key = user_id.to_string().as_bytes().to_vec();
|
||||
key.push(0xff);
|
||||
key.extend_from_slice(&count);
|
||||
self.keychangeid_userid.insert(key, &*user_id.to_string())?;
|
||||
self.mark_device_key_update(user_id, rooms, globals)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -520,19 +508,7 @@ impl Users {
|
|||
.insert(&*user_id.to_string(), user_signing_key_key)?;
|
||||
}
|
||||
|
||||
let count = globals.next_count()?.to_be_bytes();
|
||||
for room_id in rooms.rooms_joined(&user_id) {
|
||||
let mut key = room_id?.to_string().as_bytes().to_vec();
|
||||
key.push(0xff);
|
||||
key.extend_from_slice(&count);
|
||||
|
||||
self.keychangeid_userid.insert(key, &*user_id.to_string())?;
|
||||
}
|
||||
|
||||
let mut key = user_id.to_string().as_bytes().to_vec();
|
||||
key.push(0xff);
|
||||
key.extend_from_slice(&count);
|
||||
self.keychangeid_userid.insert(key, &*user_id.to_string())?;
|
||||
self.mark_device_key_update(user_id, rooms, globals)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -576,21 +552,7 @@ impl Users {
|
|||
)?;
|
||||
|
||||
// TODO: Should we notify about this change?
|
||||
let count = globals.next_count()?.to_be_bytes();
|
||||
for room_id in rooms.rooms_joined(&target_id) {
|
||||
let mut key = room_id?.to_string().as_bytes().to_vec();
|
||||
key.push(0xff);
|
||||
key.extend_from_slice(&count);
|
||||
|
||||
self.keychangeid_userid
|
||||
.insert(key, &*target_id.to_string())?;
|
||||
}
|
||||
|
||||
let mut key = target_id.to_string().as_bytes().to_vec();
|
||||
key.push(0xff);
|
||||
key.extend_from_slice(&count);
|
||||
self.keychangeid_userid
|
||||
.insert(key, &*target_id.to_string())?;
|
||||
self.mark_device_key_update(target_id, rooms, globals)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -628,6 +590,37 @@ impl Users {
|
|||
})
|
||||
}
|
||||
|
||||
fn mark_device_key_update(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
rooms: &super::rooms::Rooms,
|
||||
globals: &super::globals::Globals,
|
||||
) -> Result<()> {
|
||||
let count = globals.next_count()?.to_be_bytes();
|
||||
for room_id in rooms.rooms_joined(&user_id).filter_map(|r| r.ok()) {
|
||||
// Don't send key updates to unencrypted rooms
|
||||
if rooms
|
||||
.room_state_get(&room_id, &EventType::RoomEncryption, "")?
|
||||
.is_none()
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut key = room_id.to_string().as_bytes().to_vec();
|
||||
key.push(0xff);
|
||||
key.extend_from_slice(&count);
|
||||
|
||||
self.keychangeid_userid.insert(key, &*user_id.to_string())?;
|
||||
}
|
||||
|
||||
let mut key = user_id.to_string().as_bytes().to_vec();
|
||||
key.push(0xff);
|
||||
key.extend_from_slice(&count);
|
||||
self.keychangeid_userid.insert(key, &*user_id.to_string())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_device_keys(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
|
@ -859,7 +852,9 @@ impl Users {
|
|||
self.remove_device(&user_id, &device_id?)?;
|
||||
}
|
||||
|
||||
// Set the password to "" to indicate a deactivated account
|
||||
// Set the password to "" to indicate a deactivated account. Hashes will never result in an
|
||||
// empty string, so the user will not be able to log in again. Systems like changing the
|
||||
// password without logging in should check if the account is deactivated.
|
||||
self.userid_password.insert(user_id.to_string(), "")?;
|
||||
|
||||
// TODO: Unhook 3PID
|
||||
|
|
|
@ -27,6 +27,13 @@ pub enum Error {
|
|||
#[from]
|
||||
source: image::error::ImageError,
|
||||
},
|
||||
#[error("Could not connect to server.")]
|
||||
ReqwestError {
|
||||
#[from]
|
||||
source: reqwest::Error,
|
||||
},
|
||||
#[error("{0}")]
|
||||
BadServerResponse(&'static str),
|
||||
#[error("{0}")]
|
||||
BadConfig(&'static str),
|
||||
#[error("{0}")]
|
||||
|
|
|
@ -4,6 +4,7 @@ mod error;
|
|||
mod pdu;
|
||||
mod push_rules;
|
||||
mod ruma_wrapper;
|
||||
pub mod server_server;
|
||||
mod utils;
|
||||
|
||||
pub use database::Database;
|
||||
|
|
19
src/main.rs
19
src/main.rs
|
@ -1,13 +1,13 @@
|
|||
#![warn(rust_2018_idioms)]
|
||||
|
||||
pub mod push_rules;
|
||||
pub mod client_server;
|
||||
pub mod server_server;
|
||||
|
||||
mod client_server;
|
||||
mod database;
|
||||
mod error;
|
||||
mod pdu;
|
||||
mod push_rules;
|
||||
mod ruma_wrapper;
|
||||
//mod server_server;
|
||||
mod utils;
|
||||
|
||||
pub use database::Database;
|
||||
|
@ -26,7 +26,7 @@ fn setup_rocket() -> rocket::Rocket {
|
|||
client_server::get_supported_versions_route,
|
||||
client_server::get_register_available_route,
|
||||
client_server::register_route,
|
||||
client_server::get_login_route,
|
||||
client_server::get_login_types_route,
|
||||
client_server::login_route,
|
||||
client_server::whoami_route,
|
||||
client_server::logout_route,
|
||||
|
@ -90,6 +90,7 @@ fn setup_rocket() -> rocket::Rocket {
|
|||
client_server::sync_events_route,
|
||||
client_server::get_context_route,
|
||||
client_server::get_message_events_route,
|
||||
client_server::search_events_route,
|
||||
client_server::turn_server_route,
|
||||
client_server::send_event_to_device_route,
|
||||
client_server::get_media_config_route,
|
||||
|
@ -110,10 +111,12 @@ fn setup_rocket() -> rocket::Rocket {
|
|||
client_server::get_key_changes_route,
|
||||
client_server::get_pushers_route,
|
||||
client_server::set_pushers_route,
|
||||
//server_server::well_known_server,
|
||||
//server_server::get_server_version,
|
||||
//server_server::get_server_keys,
|
||||
//server_server::get_server_keys_deprecated,
|
||||
server_server::well_known_server,
|
||||
server_server::get_server_version,
|
||||
server_server::get_server_keys,
|
||||
server_server::get_server_keys_deprecated,
|
||||
server_server::get_public_rooms_route,
|
||||
server_server::send_transaction_message_route,
|
||||
],
|
||||
)
|
||||
.attach(AdHoc::on_attach("Config", |mut rocket| async {
|
||||
|
|
19
src/pdu.rs
19
src/pdu.rs
|
@ -37,11 +37,12 @@ pub struct PduEvent {
|
|||
impl PduEvent {
|
||||
pub fn redact(&mut self) -> Result<()> {
|
||||
self.unsigned.clear();
|
||||
let allowed = match self.kind {
|
||||
EventType::RoomMember => vec!["membership"],
|
||||
EventType::RoomCreate => vec!["creator"],
|
||||
EventType::RoomJoinRules => vec!["join_rule"],
|
||||
EventType::RoomPowerLevels => vec![
|
||||
|
||||
let allowed: &[&str] = match self.kind {
|
||||
EventType::RoomMember => &["membership"],
|
||||
EventType::RoomCreate => &["creator"],
|
||||
EventType::RoomJoinRules => &["join_rule"],
|
||||
EventType::RoomPowerLevels => &[
|
||||
"ban",
|
||||
"events",
|
||||
"events_default",
|
||||
|
@ -51,8 +52,8 @@ impl PduEvent {
|
|||
"users",
|
||||
"users_default",
|
||||
],
|
||||
EventType::RoomHistoryVisibility => vec!["history_visibility"],
|
||||
_ => vec![],
|
||||
EventType::RoomHistoryVisibility => &["history_visibility"],
|
||||
_ => &[],
|
||||
};
|
||||
|
||||
let old_content = self
|
||||
|
@ -63,8 +64,8 @@ impl PduEvent {
|
|||
let mut new_content = serde_json::Map::new();
|
||||
|
||||
for key in allowed {
|
||||
if let Some(value) = old_content.remove(key) {
|
||||
new_content.insert(key.to_owned(), value);
|
||||
if let Some(value) = old_content.remove(*key) {
|
||||
new_content.insert((*key).to_owned(), value);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,16 +1,19 @@
|
|||
use crate::{Database, MatrixResult};
|
||||
use crate::{client_server, ConduitResult, Database, Error, Result, Ruma};
|
||||
use http::header::{HeaderValue, AUTHORIZATION};
|
||||
use log::error;
|
||||
use rocket::{get, response::content::Json, State};
|
||||
use ruma::api::Endpoint;
|
||||
use ruma::api::client::error::Error;
|
||||
use ruma::api::federation::discovery::{
|
||||
get_server_keys::v2 as get_server_keys, get_server_version::v1 as get_server_version,
|
||||
use rocket::{get, post, put, response::content::Json, State};
|
||||
use ruma::api::federation::{
|
||||
directory::get_public_rooms,
|
||||
discovery::{
|
||||
get_server_keys, get_server_version::v1 as get_server_version, ServerKey, VerifyKey,
|
||||
},
|
||||
transactions::send_transaction_message,
|
||||
};
|
||||
use ruma::api::{client, OutgoingRequest};
|
||||
use serde_json::json;
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
convert::TryFrom,
|
||||
fmt::Debug,
|
||||
time::{Duration, SystemTime},
|
||||
};
|
||||
|
||||
|
@ -33,36 +36,51 @@ pub async fn request_well_known(db: &crate::Database, destination: &str) -> Opti
|
|||
Some(body.get("m.server")?.as_str()?.to_owned())
|
||||
}
|
||||
|
||||
pub async fn send_request<T: Endpoint>(
|
||||
pub async fn send_request<T: OutgoingRequest>(
|
||||
db: &crate::Database,
|
||||
destination: String,
|
||||
request: T,
|
||||
) -> Option<T::Response> {
|
||||
let mut http_request: http::Request<_> = request.try_into().unwrap();
|
||||
|
||||
) -> Result<T::IncomingResponse>
|
||||
where
|
||||
T: Debug,
|
||||
{
|
||||
let actual_destination = "https://".to_owned()
|
||||
+ &request_well_known(db, &destination)
|
||||
.await
|
||||
.unwrap_or(destination.clone() + ":8448");
|
||||
*http_request.uri_mut() = (actual_destination + T::METADATA.path).parse().unwrap();
|
||||
|
||||
let mut http_request = request
|
||||
.try_into_http_request(&actual_destination, Some(""))
|
||||
.unwrap();
|
||||
|
||||
let mut request_map = serde_json::Map::new();
|
||||
|
||||
if !http_request.body().is_empty() {
|
||||
request_map.insert(
|
||||
"content".to_owned(),
|
||||
serde_json::to_value(http_request.body()).unwrap(),
|
||||
serde_json::from_slice(http_request.body()).unwrap(),
|
||||
);
|
||||
};
|
||||
|
||||
request_map.insert("method".to_owned(), T::METADATA.method.to_string().into());
|
||||
request_map.insert("uri".to_owned(), T::METADATA.path.into());
|
||||
request_map.insert("origin".to_owned(), db.globals.server_name().into());
|
||||
request_map.insert(
|
||||
"uri".to_owned(),
|
||||
http_request
|
||||
.uri()
|
||||
.path_and_query()
|
||||
.expect("all requests have a path")
|
||||
.to_string()
|
||||
.into(),
|
||||
);
|
||||
request_map.insert(
|
||||
"origin".to_owned(),
|
||||
db.globals.server_name().as_str().into(),
|
||||
);
|
||||
request_map.insert("destination".to_owned(), destination.into());
|
||||
|
||||
let mut request_json = request_map.into();
|
||||
ruma::signatures::sign_json(
|
||||
db.globals.server_name(),
|
||||
db.globals.server_name().as_str(),
|
||||
db.globals.keypair(),
|
||||
&mut request_json,
|
||||
)
|
||||
|
@ -72,31 +90,32 @@ pub async fn send_request<T: Endpoint>(
|
|||
.as_object()
|
||||
.unwrap()
|
||||
.values()
|
||||
.next()
|
||||
.unwrap()
|
||||
.as_object()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|(k, v)| (k, v.as_str().unwrap()));
|
||||
.map(|v| {
|
||||
v.as_object()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|(k, v)| (k, v.as_str().unwrap()))
|
||||
});
|
||||
|
||||
for s in signatures {
|
||||
http_request.headers_mut().insert(
|
||||
AUTHORIZATION,
|
||||
HeaderValue::from_str(&format!(
|
||||
"X-Matrix origin={},key=\"{}\",sig=\"{}\"",
|
||||
db.globals.server_name(),
|
||||
s.0,
|
||||
s.1
|
||||
))
|
||||
.unwrap(),
|
||||
);
|
||||
for signature_server in signatures {
|
||||
for s in signature_server {
|
||||
http_request.headers_mut().insert(
|
||||
AUTHORIZATION,
|
||||
HeaderValue::from_str(&format!(
|
||||
"X-Matrix origin={},key=\"{}\",sig=\"{}\"",
|
||||
db.globals.server_name(),
|
||||
s.0,
|
||||
s.1
|
||||
))
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let reqwest_response = db
|
||||
.globals
|
||||
.reqwest_client()
|
||||
.execute(http_request.into())
|
||||
.await;
|
||||
let reqwest_request = reqwest::Request::try_from(http_request)
|
||||
.expect("all http requests are valid reqwest requests");
|
||||
|
||||
let reqwest_response = db.globals.reqwest_client().execute(reqwest_request).await;
|
||||
|
||||
// Because reqwest::Response -> http::Response is complicated:
|
||||
match reqwest_response {
|
||||
|
@ -117,59 +136,56 @@ pub async fn send_request<T: Endpoint>(
|
|||
.unwrap()
|
||||
.into_iter()
|
||||
.collect();
|
||||
Some(
|
||||
<T::Response>::try_from(http_response.body(body).unwrap())
|
||||
.ok()
|
||||
.unwrap(),
|
||||
Ok(
|
||||
T::IncomingResponse::try_from(http_response.body(body).unwrap())
|
||||
.expect("TODO: error handle other server errors"),
|
||||
)
|
||||
}
|
||||
Err(e) => {
|
||||
error!("{}", e);
|
||||
None
|
||||
}
|
||||
Err(e) => Err(e.into()),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "conduit_bin",get("/.well-known/matrix/server"))]
|
||||
#[cfg_attr(feature = "conduit_bin", get("/.well-known/matrix/server"))]
|
||||
pub fn well_known_server() -> Json<String> {
|
||||
rocket::response::content::Json(
|
||||
json!({ "m.server": "matrixtesting.koesters.xyz:14004"}).to_string(),
|
||||
)
|
||||
rocket::response::content::Json(json!({ "m.server": "pc.koesters.xyz:59003"}).to_string())
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "conduit_bin",get("/_matrix/federation/v1/version"))]
|
||||
pub fn get_server_version() -> MatrixResult<get_server_version::Response, Error> {
|
||||
MatrixResult(Ok(get_server_version::Response {
|
||||
#[cfg_attr(feature = "conduit_bin", get("/_matrix/federation/v1/version"))]
|
||||
pub fn get_server_version() -> ConduitResult<get_server_version::Response> {
|
||||
Ok(get_server_version::Response {
|
||||
server: Some(get_server_version::Server {
|
||||
name: Some("Conduit".to_owned()),
|
||||
version: Some(env!("CARGO_PKG_VERSION").to_owned()),
|
||||
}),
|
||||
}))
|
||||
}
|
||||
.into())
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "conduit_bin",get("/_matrix/key/v2/server"))]
|
||||
#[cfg_attr(feature = "conduit_bin", get("/_matrix/key/v2/server"))]
|
||||
pub fn get_server_keys(db: State<'_, Database>) -> Json<String> {
|
||||
let mut verify_keys = BTreeMap::new();
|
||||
verify_keys.insert(
|
||||
format!("ed25519:{}", db.globals.keypair().version()),
|
||||
get_server_keys::VerifyKey {
|
||||
VerifyKey {
|
||||
key: base64::encode_config(db.globals.keypair().public_key(), base64::STANDARD_NO_PAD),
|
||||
},
|
||||
);
|
||||
let mut response = serde_json::from_slice(
|
||||
http::Response::try_from(get_server_keys::Response {
|
||||
server_name: db.globals.server_name().to_owned(),
|
||||
verify_keys,
|
||||
old_verify_keys: BTreeMap::new(),
|
||||
signatures: BTreeMap::new(),
|
||||
valid_until_ts: SystemTime::now() + Duration::from_secs(60 * 2),
|
||||
http::Response::try_from(get_server_keys::v2::Response {
|
||||
server_key: ServerKey {
|
||||
server_name: db.globals.server_name().to_owned(),
|
||||
verify_keys,
|
||||
old_verify_keys: BTreeMap::new(),
|
||||
signatures: BTreeMap::new(),
|
||||
valid_until_ts: SystemTime::now() + Duration::from_secs(60 * 2),
|
||||
},
|
||||
})
|
||||
.unwrap()
|
||||
.body(),
|
||||
)
|
||||
.unwrap();
|
||||
ruma::signatures::sign_json(
|
||||
db.globals.server_name(),
|
||||
db.globals.server_name().as_str(),
|
||||
db.globals.keypair(),
|
||||
&mut response,
|
||||
)
|
||||
|
@ -177,7 +193,88 @@ pub fn get_server_keys(db: State<'_, Database>) -> Json<String> {
|
|||
Json(response.to_string())
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "conduit_bin",get("/_matrix/key/v2/server/<_key_id>"))]
|
||||
pub fn get_server_keys_deprecated(db: State<'_, Database>, _key_id: String) -> Json<String> {
|
||||
#[cfg_attr(feature = "conduit_bin", get("/_matrix/key/v2/server/<_>"))]
|
||||
pub fn get_server_keys_deprecated(db: State<'_, Database>) -> Json<String> {
|
||||
get_server_keys(db)
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "conduit_bin",
|
||||
post("/_matrix/federation/v1/publicRooms", data = "<body>")
|
||||
)]
|
||||
pub async fn get_public_rooms_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<get_public_rooms::v1::Request>,
|
||||
) -> ConduitResult<get_public_rooms::v1::Response> {
|
||||
let Ruma {
|
||||
body:
|
||||
get_public_rooms::v1::Request {
|
||||
room_network: _room_network, // TODO
|
||||
limit,
|
||||
since,
|
||||
},
|
||||
sender_id,
|
||||
device_id,
|
||||
json_body,
|
||||
} = body;
|
||||
|
||||
let client::r0::directory::get_public_rooms_filtered::Response {
|
||||
chunk,
|
||||
prev_batch,
|
||||
next_batch,
|
||||
total_room_count_estimate,
|
||||
} = client_server::get_public_rooms_filtered_route(
|
||||
db,
|
||||
Ruma {
|
||||
body: client::r0::directory::get_public_rooms_filtered::IncomingRequest {
|
||||
filter: None,
|
||||
limit,
|
||||
room_network: client::r0::directory::get_public_rooms_filtered::RoomNetwork::Matrix,
|
||||
server: None,
|
||||
since,
|
||||
},
|
||||
sender_id,
|
||||
device_id,
|
||||
json_body,
|
||||
},
|
||||
)
|
||||
.await?
|
||||
.0;
|
||||
|
||||
Ok(get_public_rooms::v1::Response {
|
||||
chunk: chunk
|
||||
.into_iter()
|
||||
.map(|c| {
|
||||
// Convert ruma::api::federation::directory::get_public_rooms::v1::PublicRoomsChunk
|
||||
// to ruma::api::client::r0::directory::PublicRoomsChunk
|
||||
Ok::<_, Error>(
|
||||
serde_json::from_str(
|
||||
&serde_json::to_string(&c)
|
||||
.expect("PublicRoomsChunk::to_string always works"),
|
||||
)
|
||||
.expect("federation and client-server PublicRoomsChunk are the same type"),
|
||||
)
|
||||
})
|
||||
.filter_map(|r| r.ok())
|
||||
.collect(),
|
||||
prev_batch,
|
||||
next_batch,
|
||||
total_room_count_estimate,
|
||||
}
|
||||
.into())
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "conduit_bin",
|
||||
put("/_matrix/federation/v1/send/<_>", data = "<body>")
|
||||
)]
|
||||
pub fn send_transaction_message_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<send_transaction_message::v1::Request>,
|
||||
) -> ConduitResult<send_transaction_message::v1::Response> {
|
||||
dbg!(&*body);
|
||||
Ok(send_transaction_message::v1::Response {
|
||||
pdus: BTreeMap::new(),
|
||||
}
|
||||
.into())
|
||||
}
|
||||
|
|
31
src/utils.rs
31
src/utils.rs
|
@ -1,6 +1,9 @@
|
|||
use argon2::{Config, Variant};
|
||||
use cmp::Ordering;
|
||||
use rand::prelude::*;
|
||||
use sled::IVec;
|
||||
use std::{
|
||||
cmp,
|
||||
convert::TryInto,
|
||||
time::{SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
|
@ -59,3 +62,31 @@ pub fn calculate_hash(password: &str) -> Result<String, argon2::Error> {
|
|||
let salt = random_string(32);
|
||||
argon2::hash_encoded(password.as_bytes(), salt.as_bytes(), &hashing_config)
|
||||
}
|
||||
|
||||
pub fn common_elements(
|
||||
mut iterators: impl Iterator<Item = impl Iterator<Item = IVec>>,
|
||||
check_order: impl Fn(&IVec, &IVec) -> Ordering,
|
||||
) -> Option<impl Iterator<Item = IVec>> {
|
||||
let first_iterator = iterators.next()?;
|
||||
let mut other_iterators = iterators.map(|i| i.peekable()).collect::<Vec<_>>();
|
||||
|
||||
Some(first_iterator.filter(move |target| {
|
||||
other_iterators
|
||||
.iter_mut()
|
||||
.map(|it| {
|
||||
while let Some(element) = it.peek() {
|
||||
match check_order(element, target) {
|
||||
Ordering::Greater => return false, // We went too far
|
||||
Ordering::Equal => return true, // Element is in both iters
|
||||
Ordering::Less => {
|
||||
// Keep searching
|
||||
it.next();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
})
|
||||
.all(|b| b)
|
||||
}))
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue