mirror of
https://forgejo.ellis.link/continuwuation/continuwuity.git
synced 2026-11-12 19:56:48 +00:00
feat: Add support for MSC4388
This commit is contained in:
10 files changed
+289
-11
No files matched your search
@@ -392,6 +392,7 @@ features = [
|
||||
"unstable-msc4406",
|
||||
"unstable-msc4439",
|
||||
"unstable-msc4354",
|
||||
"unstable-msc4388",
|
||||
"unstable-msc4480",
|
||||
"unstable-msc4466",
|
||||
"unstable-msc4494",
|
||||
|
||||
+19
-4
@@ -812,15 +812,30 @@
|
||||
# env: CONTINUWUITY_FORGET_FORCED_UPON_LEAVE
|
||||
#forget_forced_upon_leave = false
|
||||
|
||||
# Set this to true to require authentication on the normally
|
||||
# unauthenticated profile retrieval endpoints (GET)
|
||||
# "/_matrix/client/v3/profile/{userId}".
|
||||
# Set this to true to require authentication when looking up user profiles
|
||||
# through this server.
|
||||
#
|
||||
# This can prevent profile scraping.
|
||||
# By default this is false, allowing tools such as matrix.to to access
|
||||
# profiles of users on this server and on other servers.
|
||||
# Setting this to true will block those tools, and may block automated
|
||||
# crawlers from scraping users' profiles, although it will not block
|
||||
# other homeservers from accessing users' profiles.
|
||||
#
|
||||
# env: CONTINUWUITY_REQUIRE_AUTH_FOR_PROFILE_REQUESTS
|
||||
#require_auth_for_profile_requests = false
|
||||
|
||||
# Set this to false to allow rendezvous sessions to be created without
|
||||
# authentication. Rendezvous sessions are used for QR code sign-in.
|
||||
#
|
||||
# By default this is true, meaning that a user must use their signed-in
|
||||
# device to generate a QR code when signing in. Setting this to false will
|
||||
# allow signed-out devices to generate QR codes using this homeserver,
|
||||
# which may be desirable for public-registration homeservers.
|
||||
#a user
|
||||
#
|
||||
# env: CONTINUWUITY_REQUIRE_AUTH_FOR_RENDEZVOUS
|
||||
#require_auth_for_rendezvous = true
|
||||
|
||||
# Set this to true to allow your server's public room directory to be
|
||||
# federated. Set this to false to protect against /publicRooms spiders,
|
||||
# but will forbid external users from viewing your server's public room
|
||||
|
||||
@@ -24,6 +24,7 @@ pub(super) mod push;
|
||||
pub(super) mod read_marker;
|
||||
pub(super) mod redact;
|
||||
pub(super) mod relations;
|
||||
pub(super) mod rendezvous;
|
||||
pub(super) mod report;
|
||||
pub(super) mod room;
|
||||
pub(super) mod search;
|
||||
@@ -71,6 +72,7 @@ pub(super) use push::*;
|
||||
pub(super) use read_marker::*;
|
||||
pub(super) use redact::*;
|
||||
pub(super) use relations::*;
|
||||
pub(super) use rendezvous::*;
|
||||
pub(super) use report::*;
|
||||
pub(super) use room::*;
|
||||
pub(super) use search::*;
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
use axum::extract::State;
|
||||
use conduwuit::{Err, Result};
|
||||
use http::HeaderMap;
|
||||
use ruma::api::client::rendezvous::{
|
||||
create_rendezvous_session, delete_rendezvous_session, discover_rendezvous,
|
||||
get_rendezvous_session, update_rendezvous_session,
|
||||
};
|
||||
use service::Services;
|
||||
|
||||
use crate::{Ruma, router::ClientIdentity};
|
||||
|
||||
/// `GET /_matrix/client/msc4388/io.element.msc4388/rendezvous`
|
||||
pub(crate) async fn discover_rendezvous_route(
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<discover_rendezvous::msc4388::Request>,
|
||||
) -> Result<discover_rendezvous::msc4388::Response> {
|
||||
Ok(discover_rendezvous::msc4388::Response::new(rendezvous_available(
|
||||
&services,
|
||||
body.identity.as_ref(),
|
||||
)))
|
||||
}
|
||||
|
||||
/// `POST /_matrix/client/msc4388/io.element.msc4388/rendezvous/`
|
||||
pub(crate) async fn create_rendezvous_session_route(
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<create_rendezvous_session::msc4388::Request>,
|
||||
) -> Result<create_rendezvous_session::msc4388::Response> {
|
||||
if !rendezvous_available(&services, body.identity.as_ref()) {
|
||||
return Err!(Request(Forbidden("You are not authorized to create a rendezvous session")));
|
||||
}
|
||||
|
||||
services.rendezvous.create_session(body.data.clone())
|
||||
}
|
||||
|
||||
/// `PUT /_matrix/client/msc4388/io.element.msc4388/rendezvous/{id}/{txn_id}`
|
||||
pub(crate) async fn update_rendezvous_session_route(
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<update_rendezvous_session::msc4388::Request>,
|
||||
) -> Result<update_rendezvous_session::msc4388::Response> {
|
||||
services.rendezvous.update_session(
|
||||
&body.id,
|
||||
body.txn_id.clone(),
|
||||
body.sequence_token.clone(),
|
||||
body.data.clone(),
|
||||
)
|
||||
}
|
||||
|
||||
/// `GET /_matrix/client/msc4388/io.element.msc4388/rendezvous/{id}`
|
||||
pub(crate) async fn get_rendezvous_session_route(
|
||||
State(services): State<crate::State>,
|
||||
headers: HeaderMap,
|
||||
body: Ruma<get_rendezvous_session::msc4388::Request>,
|
||||
) -> Result<get_rendezvous_session::msc4388::Response> {
|
||||
if let Some(mode) = headers.get("Sec-Fetch-Mode")
|
||||
&& let Ok(mode) = mode.to_str()
|
||||
&& mode == "navigate"
|
||||
{
|
||||
return Err!(Request(Forbidden(
|
||||
"Rendezvous sessions do not accept browser navigation requests."
|
||||
)));
|
||||
}
|
||||
|
||||
services.rendezvous.get_session(&body.id)
|
||||
}
|
||||
|
||||
/// `DELETE /_matrix/client/msc4388/io.element.msc4388/rendezvous/{id}`
|
||||
pub(crate) async fn delete_rendezvous_session_route(
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<delete_rendezvous_session::msc4388::Request>,
|
||||
) -> Result<delete_rendezvous_session::msc4388::Response> {
|
||||
services.rendezvous.delete_session(&body.id)
|
||||
}
|
||||
|
||||
fn rendezvous_available(services: &Services, identity: Option<&ClientIdentity>) -> bool {
|
||||
!services.config.require_auth_for_rendezvous || identity.is_some()
|
||||
}
|
||||
@@ -194,6 +194,11 @@ pub fn build(router: Router<State>, state: State) -> Router<State> {
|
||||
.ruma_route(&client::well_known_policy_server)
|
||||
.ruma_route(&client::get_rtc_transports)
|
||||
.ruma_route(&client::room_initial_sync_route)
|
||||
.ruma_route(&client::discover_rendezvous_route)
|
||||
.ruma_route(&client::create_rendezvous_session_route)
|
||||
.ruma_route(&client::update_rendezvous_session_route)
|
||||
.ruma_route(&client::get_rendezvous_session_route)
|
||||
.ruma_route(&client::delete_rendezvous_session_route)
|
||||
.ruma_route(&client::get_authorization_server_metadata_route)
|
||||
.merge(client::oauth::router(state))
|
||||
.route("/_continuwuity/server_version", get(client::continuwuity_server_version))
|
||||
|
||||
+21
-4
@@ -918,14 +918,31 @@ pub struct Config {
|
||||
#[serde(default)]
|
||||
pub forget_forced_upon_leave: bool,
|
||||
|
||||
/// Set this to true to require authentication on the normally
|
||||
/// unauthenticated profile retrieval endpoints (GET)
|
||||
/// "/_matrix/client/v3/profile/{userId}".
|
||||
/// Set this to true to require authentication when looking up user profiles
|
||||
/// through this server.
|
||||
///
|
||||
/// This can prevent profile scraping.
|
||||
/// By default this is false, allowing tools such as matrix.to to access
|
||||
/// profiles of users on this server and on other servers.
|
||||
/// Setting this to true will block those tools, and may block automated
|
||||
/// crawlers from scraping users' profiles, although it will not block
|
||||
/// other homeservers from accessing users' profiles.
|
||||
///
|
||||
/// default: false
|
||||
#[serde(default)]
|
||||
pub require_auth_for_profile_requests: bool,
|
||||
|
||||
/// Set this to false to allow rendezvous sessions to be created without
|
||||
/// authentication. Rendezvous sessions are used for QR code sign-in.
|
||||
///
|
||||
/// By default this is true, meaning that a user must use their signed-in
|
||||
/// device to generate a QR code when signing in. Setting this to false will
|
||||
/// allow signed-out devices to generate QR codes using this homeserver,
|
||||
/// which may be desirable for public-registration homeservers.
|
||||
///
|
||||
/// default: true
|
||||
#[serde(default = "true_fn")]
|
||||
pub require_auth_for_rendezvous: bool,
|
||||
|
||||
/// Set this to true to allow your server's public room directory to be
|
||||
/// federated. Set this to false to protect against /publicRooms spiders,
|
||||
/// but will forbid external users from viewing your server's public room
|
||||
|
||||
+2
-1
@@ -37,7 +37,8 @@ pub use error::Error;
|
||||
pub use info::version::*;
|
||||
pub use matrix::{Event, EventTypeExt, Pdu, PduCount, PduEvent, PduId, pdu, state_res};
|
||||
pub use parking_lot::{
|
||||
Mutex as SyncMutex, RwLock as SyncRwLock, RwLockReadGuard as SyncRwLockReadGuard,
|
||||
MappedMutexGuard as SyncMappedMutexGuard, Mutex as SyncMutex, MutexGuard as SyncMutexGuard,
|
||||
RwLock as SyncRwLock, RwLockReadGuard as SyncRwLockReadGuard,
|
||||
RwLockWriteGuard as SyncRwLockWriteGuard,
|
||||
};
|
||||
pub use server::Server;
|
||||
|
||||
@@ -33,6 +33,7 @@ pub mod oidc;
|
||||
pub mod presence;
|
||||
pub mod pusher;
|
||||
pub mod registration_tokens;
|
||||
pub mod rendezvous;
|
||||
pub mod rooms;
|
||||
pub mod sending;
|
||||
pub mod server_keys;
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
use std::{
|
||||
sync::Arc,
|
||||
time::{Duration, SystemTime},
|
||||
};
|
||||
|
||||
use conduwuit::{Err, Result, SyncMappedMutexGuard, SyncMutex, SyncMutexGuard, at, err, utils};
|
||||
use lru_cache::LruCache;
|
||||
use ruma::{
|
||||
OwnedTransactionId,
|
||||
api::client::rendezvous::{
|
||||
create_rendezvous_session, delete_rendezvous_session, get_rendezvous_session,
|
||||
update_rendezvous_session,
|
||||
},
|
||||
};
|
||||
|
||||
pub struct Service {
|
||||
sessions: SyncMutex<LruCache<String, Session>>,
|
||||
}
|
||||
|
||||
struct Session {
|
||||
data: String,
|
||||
created_at: SystemTime,
|
||||
last_sequence_token: String,
|
||||
transaction_sequences: LruCache<OwnedTransactionId, String>,
|
||||
}
|
||||
|
||||
impl Session {
|
||||
const SAVED_TRANSACTION_SEQUENCES: usize = 256;
|
||||
|
||||
fn new(data: String, sequence_token: String) -> Self {
|
||||
Self {
|
||||
data,
|
||||
created_at: SystemTime::now(),
|
||||
last_sequence_token: sequence_token,
|
||||
transaction_sequences: LruCache::new(Self::SAVED_TRANSACTION_SEQUENCES),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::Service for Service {
|
||||
fn build(_: crate::Args<'_>) -> Result<Arc<Self>> {
|
||||
Ok(Arc::new(Self {
|
||||
sessions: SyncMutex::new(LruCache::new(Self::MAX_SESSIONS)),
|
||||
}))
|
||||
}
|
||||
|
||||
fn name(&self) -> &str { crate::service::make_name(std::module_path!()) }
|
||||
}
|
||||
|
||||
impl Service {
|
||||
const MAX_DATA_SIZE_BYTES: usize = 4096;
|
||||
const MAX_SESSIONS: usize = 256;
|
||||
const SESSION_MAX_AGE: Duration = Duration::from_secs(300);
|
||||
const TOKEN_LENGTH: usize = 32;
|
||||
|
||||
fn create_token() -> String { utils::random_string(Self::TOKEN_LENGTH) }
|
||||
|
||||
fn lock_session(&self, id: &str) -> Result<SyncMappedMutexGuard<'_, Session>> {
|
||||
SyncMutexGuard::try_map_or_err(self.sessions.lock(), |sessions| {
|
||||
sessions
|
||||
.get_mut(id)
|
||||
.filter(|session| {
|
||||
SystemTime::now()
|
||||
.duration_since(session.created_at)
|
||||
.is_ok_and(|duration| duration < Self::SESSION_MAX_AGE)
|
||||
})
|
||||
.ok_or_else(|| err!(Request(NotFound("Unknown session"))))
|
||||
})
|
||||
.map_err(at!(1))
|
||||
}
|
||||
|
||||
pub fn create_session(
|
||||
&self,
|
||||
data: String,
|
||||
) -> Result<create_rendezvous_session::msc4388::Response> {
|
||||
if data.len() > Self::MAX_DATA_SIZE_BYTES {
|
||||
return Err!(Request(TooLarge("Data too large")));
|
||||
}
|
||||
|
||||
let id = Self::create_token();
|
||||
let sequence_token = Self::create_token();
|
||||
|
||||
self.sessions
|
||||
.lock()
|
||||
.insert(id.clone(), Session::new(data, sequence_token.clone()));
|
||||
|
||||
Ok(create_rendezvous_session::msc4388::Response::new(
|
||||
id,
|
||||
sequence_token,
|
||||
Self::SESSION_MAX_AGE,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn update_session(
|
||||
&self,
|
||||
id: &str,
|
||||
transaction: OwnedTransactionId,
|
||||
sequence_token: String,
|
||||
data: String,
|
||||
) -> Result<update_rendezvous_session::msc4388::Response> {
|
||||
let mut session = self.lock_session(id)?;
|
||||
|
||||
if data.len() > Self::MAX_DATA_SIZE_BYTES {
|
||||
return Err!(Request(TooLarge("Data too large")));
|
||||
}
|
||||
|
||||
let mut session = self.lock_session(id)?;
|
||||
|
||||
if let Some(sequence_token) = session.transaction_sequences.get_mut(&transaction) {
|
||||
return Ok(update_rendezvous_session::msc4388::Response::new(
|
||||
sequence_token.to_owned(),
|
||||
));
|
||||
}
|
||||
|
||||
if session.last_sequence_token != sequence_token {
|
||||
return Err!(Request(ConcurrentWrite("Sequence token mismatch")));
|
||||
}
|
||||
|
||||
let new_sequence_token = Self::create_token();
|
||||
|
||||
session.data = data;
|
||||
session.last_sequence_token.clone_from(&new_sequence_token);
|
||||
session
|
||||
.transaction_sequences
|
||||
.insert(transaction, sequence_token);
|
||||
|
||||
Ok(update_rendezvous_session::msc4388::Response::new(new_sequence_token))
|
||||
}
|
||||
|
||||
pub fn get_session(&self, id: &str) -> Result<get_rendezvous_session::unstable::Response> {
|
||||
let session = self.lock_session(id)?;
|
||||
|
||||
let session_age = SystemTime::now()
|
||||
.duration_since(session.created_at)
|
||||
.unwrap_or(Duration::MAX);
|
||||
|
||||
let session_time_remaining = Self::SESSION_MAX_AGE.saturating_sub(session_age);
|
||||
|
||||
Ok(get_rendezvous_session::msc4388::Response::new(
|
||||
session.last_sequence_token.clone(),
|
||||
session.data.clone(),
|
||||
session_time_remaining,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn delete_session(
|
||||
&self,
|
||||
id: &str,
|
||||
) -> Result<delete_rendezvous_session::msc4388::Response> {
|
||||
let mut sessions = self.sessions.lock();
|
||||
|
||||
if sessions.remove(id).is_some() {
|
||||
Ok(delete_rendezvous_session::msc4388::Response::new())
|
||||
} else {
|
||||
Err!(Request(NotFound("Unknown session")))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,8 +11,8 @@ use crate::{
|
||||
account_data, admin, announcements, antispam, appservice, client, config, emergency,
|
||||
federation, firstrun, globals, key_backups, mailer,
|
||||
manager::Manager,
|
||||
media, moderation, oauth, oidc, presence, pusher, registration_tokens, rooms, sending,
|
||||
server_keys,
|
||||
media, moderation, oauth, oidc, presence, pusher, registration_tokens, rendezvous, rooms,
|
||||
sending, server_keys,
|
||||
service::{self, Args, Map, Service},
|
||||
sync, threepid, transactions, uiaa, users,
|
||||
};
|
||||
@@ -33,6 +33,7 @@ pub struct Services {
|
||||
pub presence: Arc<presence::Service>,
|
||||
pub pusher: Arc<pusher::Service>,
|
||||
pub registration_tokens: Arc<registration_tokens::Service>,
|
||||
pub rendezvous: Arc<rendezvous::Service>,
|
||||
pub rooms: rooms::Service,
|
||||
pub federation: Arc<federation::Service>,
|
||||
pub firstrun: Arc<firstrun::Service>,
|
||||
@@ -89,6 +90,7 @@ impl Services {
|
||||
presence: build!(presence::Service),
|
||||
pusher: build!(pusher::Service),
|
||||
registration_tokens: build!(registration_tokens::Service),
|
||||
rendezvous: build!(rendezvous::Service),
|
||||
rooms: rooms::Service {
|
||||
alias: build!(rooms::alias::Service),
|
||||
auth_chain: build!(rooms::auth_chain::Service),
|
||||
|
||||
Reference in new issue
Block a user