From be4bb3e0e46ce63eb9ab891dbfc03436105dfcf7 Mon Sep 17 00:00:00 2001 From: Ginger Date: Mon, 13 Apr 2026 16:20:47 -0400 Subject: [PATCH] refactor: Fix errors in `api/router/` --- src/api/client/membership/join.rs | 3 +- src/api/router.rs | 1 - src/api/router/args.rs | 138 ++++---- src/api/router/auth.rs | 540 +++++++++++------------------- src/api/router/handler.rs | 2 +- src/api/router/request.rs | 59 ---- src/core/config/mod.rs | 12 - src/core/error/mod.rs | 2 + 8 files changed, 275 insertions(+), 482 deletions(-) delete mode 100644 src/api/router/request.rs diff --git a/src/api/client/membership/join.rs b/src/api/client/membership/join.rs index 91075c693..ea0fdf862 100644 --- a/src/api/client/membership/join.rs +++ b/src/api/client/membership/join.rs @@ -735,8 +735,7 @@ async fn join_room_by_id_helper_local( // This is a restricted room, check if we can complete the join requirements // locally. let needs_auth_user = - user_can_perform_restricted_join(services, sender_user, room_id, &room_version) - .await; + user_can_perform_restricted_join(services, sender_user, room_id).await; if needs_auth_user.is_ok_and(is_true!()) { // If there was an error or the value is false, we'll try joining over // federation. Since it's Ok(true), we can authorise this locally. diff --git a/src/api/router.rs b/src/api/router.rs index d8fad39b4..6aaea7679 100644 --- a/src/api/router.rs +++ b/src/api/router.rs @@ -1,7 +1,6 @@ mod args; mod auth; mod handler; -mod request; mod response; use std::str::FromStr; diff --git a/src/api/router/args.rs b/src/api/router/args.rs index 48ae3add9..a2510bd16 100644 --- a/src/api/router/args.rs +++ b/src/api/router/args.rs @@ -1,15 +1,29 @@ -use std::{mem, ops::Deref}; +use std::ops::Deref; -use axum::{body::Body, extract::FromRequest}; -use bytes::{BufMut, Bytes, BytesMut}; -use conduwuit::{Error, Result, debug, debug_warn, err, trace}; -use ruma::{ - CanonicalJsonObject, CanonicalJsonValue, DeviceId, OwnedDeviceId, OwnedServerName, - OwnedUserId, ServerName, UserId, api::IncomingRequest, +use axum::{ + RequestExt, RequestPartsExt, + body::Body, + extract::{FromRequest, Path, Query}, }; +use conduwuit::{Error, Result, err}; +use ruma::{ + CanonicalJsonValue, DeviceId, OwnedDeviceId, OwnedServerName, OwnedUserId, ServerName, + UserId, api::IncomingRequest, +}; +use serde::Deserialize; -use super::{auth, request, request::Request}; -use crate::{State, service::appservice::RegistrationInfo}; +use crate::{State, router::auth::CheckAuth, service::appservice::RegistrationInfo}; + +/// Query parameters needed to authenticate requests +#[derive(Deserialize)] +pub(super) struct AuthQueryParams { + pub(super) access_token: Option, + pub(super) user_id: Option, + /// Device ID for appservice device masquerading (MSC3202/MSC4190). + /// Can be provided as `device_id` or `org.matrix.msc3202.device_id`. + #[serde(alias = "org.matrix.msc3202.device_id")] + pub(super) device_id: Option, +} /// Extractor for Ruma request structs pub(crate) struct Args { @@ -77,9 +91,9 @@ impl Deref for Args fn deref(&self) -> &Self::Target { &self.body } } -impl FromRequest for Args +impl FromRequest for Args where - T: IncomingRequest + Send + Sync + 'static, + R: IncomingRequest + Send + Sync + 'static, { type Rejection = Error; @@ -87,27 +101,53 @@ async fn from_request( request: hyper::Request, services: &State, ) -> Result { - let mut request = request::from(services, request).await?; - let mut json_body = serde_json::from_slice::(&request.body).ok(); + let limited = request.with_limited_body(); + + let (mut parts, body) = limited.into_parts(); + + // Read the body + let body = { + let max_body_size = services.server.config.max_request_size; + + // Check if the Content-Length header is present and valid, saves us streaming + // the response into memory + if let Some(content_length) = parts.headers.get(http::header::CONTENT_LENGTH) { + if let Ok(content_length) = content_length + .to_str() + .map(|s| s.parse::().unwrap_or_default()) + { + if content_length > max_body_size { + return Err(err!(Request(TooLarge("Request body too large")))); + } + } + } + + axum::body::to_bytes(body, max_body_size) + .await + .map_err(|e| err!(Request(TooLarge("Request body too large: {e}"))))? + }; + + // Make a JSON copy of the body for use in handlers + let json_body = serde_json::from_slice::(&body).ok(); + + // Extract the query parameters and path + let Path(path): Path> = parts.extract().await?; + let Query(auth_query): Query = parts.extract().await?; + + // Assemble a new request from the read body and parts + let request = hyper::Request::from_parts(parts, body); + + // Check authentication + let auth = + R::Authentication::authenticate::(services, &request, auth_query) + .await?; + + // Deserialize the body + let body = R::try_from_http_request(request, &path) + .map_err(|e| err!(Request(BadJson(debug_warn!("{e}")))))?; - // while very unusual and really shouldn't be recommended, Synapse accepts POST - // requests with a completely empty body. very old clients, libraries, and some - // appservices still call APIs like /join like this. so let's just default to - // empty object `{}` to copy synapse's behaviour - if json_body.is_none() - && request.parts.method == http::Method::POST - && !request.parts.uri.path().contains("/media/") - { - trace!("json_body from_request: {:?}", json_body.clone()); - debug_warn!( - "received a POST request with an empty body, defaulting/assuming to {{}} like \ - Synapse does" - ); - json_body = Some(CanonicalJsonValue::Object(CanonicalJsonObject::new())); - } - let auth = auth::auth(services, &mut request, json_body.as_ref(), &T::METADATA).await?; Ok(Self { - body: make_body::(&mut request, json_body.as_mut())?, + body, origin: auth.origin, sender_user: auth.sender_user, sender_device: auth.sender_device, @@ -116,41 +156,3 @@ async fn from_request( }) } } - -fn make_body(request: &mut Request, json_body: Option<&mut CanonicalJsonValue>) -> Result -where - T: IncomingRequest, -{ - let body = take_body(request, json_body); - let http_request = into_http_request(request, body); - T::try_from_http_request(http_request, &request.path) - .map_err(|e| err!(Request(BadJson(debug_warn!("{e}"))))) -} - -fn into_http_request(request: &Request, body: Bytes) -> hyper::Request { - let mut http_request = hyper::Request::builder() - .uri(request.parts.uri.clone()) - .method(request.parts.method.clone()); - - *http_request.headers_mut().expect("mutable http headers") = request.parts.headers.clone(); - - let http_request = http_request.body(body).expect("http request body"); - - let headers = http_request.headers(); - let method = http_request.method(); - let uri = http_request.uri(); - debug!("{method:?} {uri:?} {headers:?}"); - - http_request -} - -#[allow(clippy::needless_pass_by_value)] -fn take_body(request: &mut Request, json_body: Option<&mut CanonicalJsonValue>) -> Bytes { - let Some(CanonicalJsonValue::Object(json_body)) = json_body else { - return mem::take(&mut request.body); - }; - - let mut buf = BytesMut::new().writer(); - serde_json::to_writer(&mut buf, &json_body).expect("value serialization can't fail"); - buf.into_inner().freeze() -} diff --git a/src/api/router/auth.rs b/src/api/router/auth.rs index 8f48166df..dbfe58f03 100644 --- a/src/api/router/auth.rs +++ b/src/api/router/auth.rs @@ -1,30 +1,15 @@ -use axum::RequestPartsExt; -use axum_extra::{ - TypedHeader, - headers::{Authorization, authorization::Bearer}, - typed_header::TypedHeaderRejectionReason, -}; -use conduwuit::{Err, Error, Result, debug_error, err, warn}; -use futures::{ - TryFutureExt, - future::{ - Either::{Left, Right}, - select_ok, - }, - pin_mut, -}; +use std::any::{Any, TypeId}; + +use conduwuit::{Err, Result, err}; use ruma::{ - CanonicalJsonObject, CanonicalJsonValue, DeviceId, OwnedDeviceId, OwnedServerName, - OwnedUserId, UserId, + OwnedDeviceId, OwnedServerName, OwnedUserId, UserId, api::{ - AuthScheme, IncomingRequest, Metadata, - client::{ - directory::get_public_rooms, - error::ErrorKind, - profile::{get_avatar_url, get_display_name, get_profile, get_profile_key}, - voip::get_turn_server_info, + IncomingRequest, + auth_scheme::{ + AccessToken, AccessTokenOptional, AppserviceToken, AppserviceTokenOptional, + AuthScheme, NoAccessToken, NoAuthentication, }, - federation::{authentication::XMatrix, openid::get_openid_userinfo}, + federation::authentication::ServerSignatures, }, }; use service::{ @@ -32,8 +17,7 @@ server_keys::{PubKeyMap, PubKeys}, }; -use super::request::Request; -use crate::service::appservice::RegistrationInfo; +use crate::{router::args::AuthQueryParams, service::appservice::RegistrationInfo}; enum Token { Appservice(Box), @@ -42,6 +26,7 @@ enum Token { None, } +#[derive(Default)] pub(super) struct Auth { pub(super) origin: Option, pub(super) sender_user: Option, @@ -49,350 +34,227 @@ pub(super) struct Auth { pub(super) appservice_info: Option, } -pub(super) async fn auth( - services: &Services, - request: &mut Request, - json_body: Option<&CanonicalJsonValue>, - metadata: &Metadata, -) -> Result { - let bearer: Option>> = - request.parts.extract().await.unwrap_or(None); - let token = match &bearer { - | Some(TypedHeader(Authorization(bearer))) => Some(bearer.token()), - | None => request.query.access_token.as_deref(), - }; +pub(crate) trait CheckAuth: AuthScheme { + fn authenticate + Sync>( + services: &Services, + incoming_request: &hyper::Request, + query: AuthQueryParams, + ) -> impl Future> + Send { + async move { + let route = TypeId::of::(); - let token = find_token(services, token).await?; + let output = Self::extract_authentication(&incoming_request).map_err(|err| { + err!(Request(Unauthorized(warn!( + "Failed to extract authorization: {}", + err.into() + )))) + })?; - if metadata.authentication == AuthScheme::None { - match metadata { - | &get_public_rooms::v3::Request::METADATA => { - match token { - | Token::Appservice(_) | Token::User(_) => { - // we should have validated the token above - // already - }, - | Token::None | Token::Invalid => { - return Err(Error::BadRequest( - ErrorKind::MissingToken, - "Missing or invalid access token.", - )); - }, - } - }, - | &get_profile::v3::Request::METADATA - | &get_profile_key::unstable::Request::METADATA - | &get_display_name::v3::Request::METADATA - | &get_avatar_url::v3::Request::METADATA => { - if services.server.config.require_auth_for_profile_requests { - match token { - | Token::Appservice(_) | Token::User(_) => { - // we should have validated the token above - // already - }, - | Token::None | Token::Invalid => { - return Err(Error::BadRequest( - ErrorKind::MissingToken, - "Missing or invalid access token.", - )); - }, - } - } - }, - | _ => {}, + Self::verify(services, output, incoming_request, query, route).await } } - match (metadata.authentication, token) { - | (AuthScheme::AccessToken, Token::Appservice(info)) => - Ok(auth_appservice(services, request, info).await?), - | ( - AuthScheme::None | AuthScheme::AccessTokenOptional | AuthScheme::AppserviceToken, - Token::Appservice(info), - ) => Ok(Auth { - origin: None, - sender_user: None, - sender_device: None, - appservice_info: Some(*info), - }), - | (AuthScheme::AccessToken, Token::None) => match metadata { - | &get_turn_server_info::v3::Request::METADATA => { - if services.server.config.turn_allow_guests { - Ok(Auth { - origin: None, - sender_user: None, - sender_device: None, - appservice_info: None, - }) - } else { - Err(Error::BadRequest(ErrorKind::MissingToken, "Missing access token.")) - } - }, - | _ => Err(Error::BadRequest(ErrorKind::MissingToken, "Missing access token.")), - }, - | ( - AuthScheme::AccessToken | AuthScheme::AccessTokenOptional | AuthScheme::None, - Token::User((user_id, device_id)), - ) => { - let is_locked = services.users.is_locked(&user_id).await.map_err(|e| { - err!(Request(Forbidden(warn!("Failed to check user lock status: {e}")))) + fn verify + Sync>( + services: &Services, + output: Self::Output, + request: &hyper::Request, + query: AuthQueryParams, + route: TypeId, + ) -> impl Future> + Send; +} + +impl CheckAuth for ServerSignatures { + async fn verify + Sync>( + services: &Services, + output: Self::Output, + request: &hyper::Request, + _query: AuthQueryParams, + _route: TypeId, + ) -> Result { + let destination = services.globals.server_name(); + if output + .destination + .as_ref() + .is_some_and(|supplied_destination| supplied_destination != destination) + { + return Err!(Request(Unauthorized("Destination mismatch."))); + } + + let key = services + .server_keys + .get_verify_key(&output.origin, &output.key) + .await + .map_err(|e| { + err!(Request(Unauthorized(warn!("Failed to fetch signing keys: {e}")))) })?; - if is_locked { - // Only /logout and /logout/all are allowed for locked users - if !matches!( - metadata, - &ruma::api::client::session::logout::v3::Request::METADATA - | &ruma::api::client::session::logout_all::v3::Request::METADATA - ) { - return Err(Error::BadRequest( - ErrorKind::UserLocked, - "This account has been locked.", - )); - } - } - Ok(Auth { - origin: None, - sender_user: Some(user_id), - sender_device: Some(device_id), - appservice_info: None, - }) - }, - | (AuthScheme::ServerSignatures, Token::None) => - Ok(auth_server(services, request, json_body).await?), - | ( - AuthScheme::None | AuthScheme::AppserviceToken | AuthScheme::AccessTokenOptional, - Token::None, - ) => Ok(Auth { - sender_user: None, - sender_device: None, - origin: None, - appservice_info: None, - }), - | (AuthScheme::ServerSignatures, Token::Appservice(_) | Token::User(_)) => - Err(Error::BadRequest( - ErrorKind::Unauthorized, - "Only server signatures should be used on this endpoint.", - )), - | (AuthScheme::AppserviceToken, Token::User(_)) => Err(Error::BadRequest( - ErrorKind::Unauthorized, - "Only appservice access tokens should be used on this endpoint.", - )), - | (AuthScheme::None, Token::Invalid) => { - // OpenID federation endpoint uses a query param with the same name, drop this - // once query params for user auth are removed from the spec. This is - // required to make integration manager work. - if request.query.access_token.is_some() - && metadata == &get_openid_userinfo::v1::Request::METADATA - { - Ok(Auth { - origin: None, - sender_user: None, - sender_device: None, - appservice_info: None, - }) - } else { - Err(Error::BadRequest( - ErrorKind::UnknownToken { soft_logout: false }, - "Unknown access token.", - )) - } - }, - | (_, Token::Invalid) => Err(Error::BadRequest( - ErrorKind::UnknownToken { soft_logout: false }, - "Unknown access token.", - )), + + let keys: PubKeys = [(output.key.to_string(), key.key)].into(); + let keys: PubKeyMap = [(output.origin.as_str().into(), keys)].into(); + + match output.verify_request(request, destination, &keys) { + | Ok(_) => Ok(Auth { + origin: Some(output.origin.to_owned()), + ..Default::default() + }), + | Err(err) => + Err!(Request(Unauthorized(warn!("Failed to verify X-Matrix header: {err}")))), + } } } -async fn auth_appservice( - services: &Services, - request: &Request, - info: Box, -) -> Result { - let user_id_default = || { - UserId::parse_with_server_name( - info.registration.sender_localpart.as_str(), - services.globals.server_name(), - ) - }; +impl CheckAuth for AccessToken { + async fn verify + Sync>( + services: &Services, + output: Self::Output, + _request: &hyper::Request, + _query: AuthQueryParams, + route: TypeId, + ) -> Result { + let Ok((sender_user, sender_device)) = services.users.find_from_token(&output).await + else { + return Err!(Request(Unauthorized("Invalid access token."))); + }; - let Ok(user_id) = request - .query - .user_id - .clone() - .map_or_else(user_id_default, OwnedUserId::parse) - else { - return Err!(Request(InvalidUsername("Username is invalid."))); - }; - - if !info.is_user_match(&user_id) { - return Err!(Request(Exclusive("User is not in namespace."))); - } - - // MSC3202/MSC4190: Handle device_id masquerading for appservices. - // The device_id can be provided via `device_id` or - // `org.matrix.msc3202.device_id` query parameter. - let sender_device = if let Some(ref device_id_str) = request.query.device_id { - let device_id: &DeviceId = device_id_str.as_str().into(); - - // Verify the device exists for this user if services .users - .get_device_metadata(&user_id, device_id) + .is_locked(&sender_user) .await - .is_err() + .is_ok_and(std::convert::identity) { - return Err!(Request(Forbidden( - "Device does not exist for user or appservice cannot masquerade as this device." - ))); + // Locked users can only use /logout and /logout/all + + if !(route == TypeId::of::() + || route == TypeId::of::()) + { + return Err!(Request(Unauthorized("Your account is locked."))); + } } - Some(device_id.to_owned()) - } else { - None - }; - - Ok(Auth { - origin: None, - sender_user: Some(user_id), - sender_device, - appservice_info: Some(*info), - }) + return Ok(Auth { + sender_user: Some(sender_user), + sender_device: Some(sender_device), + ..Default::default() + }); + } } -async fn auth_server( - services: &Services, - request: &mut Request, - body: Option<&CanonicalJsonValue>, -) -> Result { - type Member = (String, CanonicalJsonValue); - type Object = CanonicalJsonObject; - type Value = CanonicalJsonValue; +impl CheckAuth for AccessTokenOptional { + async fn verify + Sync>( + services: &Services, + output: Self::Output, + request: &hyper::Request, + query: AuthQueryParams, + route: TypeId, + ) -> Result { + match output { + | Some(token) => + ::verify(services, token, request, query, route).await, + | None => Ok(Auth::default()), + } + } +} - let x_matrix = parse_x_matrix(request).await?; - auth_server_checks(services, &x_matrix)?; +impl CheckAuth for AppserviceToken { + async fn verify + Sync>( + services: &Services, + output: Self::Output, + _request: &hyper::Request, + query: AuthQueryParams, + _route: TypeId, + ) -> Result { + let Ok(appservice_info) = services.appservice.find_from_token(&output).await else { + return Err!(Request(Unauthorized("Invalid appservice token."))); + }; - let destination = services.globals.server_name(); - let origin = &x_matrix.origin; - let signature_uri = request - .parts - .uri - .path_and_query() - .expect("all requests have a path") - .to_string(); + let Ok(sender_user) = query.user_id.clone().map_or_else( + || { + UserId::parse_with_server_name( + appservice_info.registration.sender_localpart.as_str(), + services.globals.server_name(), + ) + }, + UserId::parse, + ) else { + return Err!(Request(InvalidUsername("Username is invalid."))); + }; - let signature: [Member; 1] = - [(x_matrix.key.as_str().into(), Value::String(x_matrix.sig.to_string()))]; - - let signatures: [Member; 1] = [(origin.as_str().into(), Value::Object(signature.into()))]; - - let authorization: Object = if let Some(body) = body.cloned() { - let authorization: [Member; 6] = [ - ("content".into(), body), - ("destination".into(), Value::String(destination.into())), - ("method".into(), Value::String(request.parts.method.as_str().into())), - ("origin".into(), Value::String(origin.as_str().into())), - ("signatures".into(), Value::Object(signatures.into())), - ("uri".into(), Value::String(signature_uri)), - ]; - - authorization.into() - } else { - let authorization: [Member; 5] = [ - ("destination".into(), Value::String(destination.into())), - ("method".into(), Value::String(request.parts.method.as_str().into())), - ("origin".into(), Value::String(origin.as_str().into())), - ("signatures".into(), Value::Object(signatures.into())), - ("uri".into(), Value::String(signature_uri)), - ]; - - authorization.into() - }; - - let key = services - .server_keys - .get_verify_key(origin, &x_matrix.key) - .await - .map_err(|e| err!(Request(Forbidden(warn!("Failed to fetch signing keys: {e}")))))?; - - let keys: PubKeys = [(x_matrix.key.to_string(), key.key)].into(); - let keys: PubKeyMap = [(origin.as_str().into(), keys)].into(); - if let Err(e) = ruma::signatures::verify_json(&keys, authorization) { - debug_error!("Failed to verify federation request from {origin}: {e}"); - if request.parts.uri.to_string().contains('@') { - warn!( - "Request uri contained '@' character. Make sure your reverse proxy gives \ - conduwuit the raw uri (apache: use nocanon)" - ); + if !appservice_info.is_user_match(&sender_user) { + return Err!(Request(Exclusive("User is not in namespace."))); } - return Err!(Request(Forbidden("Failed to verify X-Matrix signatures."))); - } + // MSC3202/MSC4190: Handle device_id masquerading for appservices. + // The device_id can be provided via `device_id` or + // `org.matrix.msc3202.device_id` query parameter. + let sender_device = if let Some(device_id) = query.device_id.as_deref().map(Into::into) { + // Verify the device exists for this user + if services + .users + .get_device_metadata(&sender_user, device_id) + .await + .is_err() + { + return Err!(Request(Forbidden( + "Device does not exist for user or appservice cannot masquerade as this \ + device." + ))); + } - Ok(Auth { - origin: origin.to_owned().into(), - sender_user: None, - sender_device: None, - appservice_info: None, - }) + Some(device_id.to_owned()) + } else { + None + }; + + Ok(Auth { + appservice_info: Some(appservice_info), + sender_user: Some(sender_user), + sender_device, + ..Default::default() + }) + } } -fn auth_server_checks(services: &Services, x_matrix: &XMatrix) -> Result<()> { - if !services.config.allow_federation { - return Err!(Config("allow_federation", "Federation is disabled.")); +impl CheckAuth for AppserviceTokenOptional { + async fn verify + Sync>( + services: &Services, + output: Self::Output, + request: &hyper::Request, + query: AuthQueryParams, + route: TypeId, + ) -> Result { + match output { + | Some(token) => + ::verify(services, token, request, query, route) + .await, + | None => Ok(Auth::default()), + } } - - let destination = services.globals.server_name(); - if x_matrix.destination.as_deref() != Some(destination) { - return Err!(Request(Forbidden("Invalid destination."))); - } - - let origin = &x_matrix.origin; - if services.moderation.is_remote_server_forbidden(origin) { - return Err!(Request(Forbidden(debug_warn!( - "Federation requests from {origin} denied." - )))); - } - - Ok(()) } -async fn parse_x_matrix(request: &mut Request) -> Result { - let TypedHeader(Authorization(x_matrix)) = request - .parts - .extract::>>() - .await - .map_err(|e| { - let msg = match e.reason() { - | TypedHeaderRejectionReason::Missing => "Missing Authorization header.", - | TypedHeaderRejectionReason::Error(_) => "Invalid X-Matrix signatures.", - | _ => "Unknown header-related error", - }; +impl CheckAuth for NoAuthentication { + async fn verify + Sync>( + _services: &Services, + _output: Self::Output, + _request: &hyper::Request, + _query: AuthQueryParams, + _route: TypeId, + ) -> Result { + Ok(Auth::default()) + } +} - err!(Request(Forbidden(warn!("{msg}: {e}")))) +impl CheckAuth for NoAccessToken { + async fn verify + Sync>( + services: &Services, + _output: Self::Output, + request: &hyper::Request, + query: AuthQueryParams, + route: TypeId, + ) -> Result { + // We handle these the same as AccessTokenOptional + let token = AccessTokenOptional::extract_authentication(request).map_err(|err| { + err!(Request(Unauthorized(warn!("Failed to extract authorization: {}", err)))) })?; - Ok(x_matrix) -} - -async fn find_token(services: &Services, token: Option<&str>) -> Result { - let Some(token) = token else { - return Ok(Token::None); - }; - - let user_token = services.users.find_from_token(token).map_ok(Token::User); - - let appservice_token = services - .appservice - .find_from_token(token) - .map_ok(Box::new) - .map_ok(Token::Appservice); - - pin_mut!(user_token, appservice_token); - // Returns Ok if either token type succeeds, Err only if both fail - match select_ok([Left(user_token), Right(appservice_token)]).await { - | Err(e) if !e.is_not_found() => Err(e), - | Ok((token, _)) => Ok(token), - | _ => Ok(Token::Invalid), + ::verify(services, token, request, query, route).await } } diff --git a/src/api/router/handler.rs b/src/api/router/handler.rs index 5ffd9f500..9fd425eeb 100644 --- a/src/api/router/handler.rs +++ b/src/api/router/handler.rs @@ -38,7 +38,7 @@ impl RumaHandler<($($tx,)* Ruma,)> for Fun where Fun: Fn($($tx,)* Ruma,) -> Fut + Send + Sync + 'static, Fut: Future> + Send, - Req: IncomingRequest + Send + Sync + 'static, + Req: IncomingRequest + Send + Sync + 'static, Err: IntoResponse + Send, ::OutgoingResponse: Send, $( $tx: FromRequestParts + Send + Sync + 'static, )* diff --git a/src/api/router/request.rs b/src/api/router/request.rs deleted file mode 100644 index 96a78c0e8..000000000 --- a/src/api/router/request.rs +++ /dev/null @@ -1,59 +0,0 @@ -use std::str; - -use axum::{RequestExt, RequestPartsExt, extract::Path}; -use bytes::Bytes; -use conduwuit::{Result, err}; -use http::request::Parts; -use serde::Deserialize; -use service::Services; - -#[derive(Deserialize)] -pub(super) struct QueryParams { - pub(super) access_token: Option, - pub(super) user_id: Option, - /// Device ID for appservice device masquerading (MSC3202/MSC4190). - /// Can be provided as `device_id` or `org.matrix.msc3202.device_id`. - #[serde(alias = "org.matrix.msc3202.device_id")] - pub(super) device_id: Option, -} - -pub(super) struct Request { - pub(super) path: Path>, - pub(super) query: QueryParams, - pub(super) body: Bytes, - pub(super) parts: Parts, -} - -pub(super) async fn from( - services: &Services, - request: hyper::Request, -) -> Result { - let limited = request.with_limited_body(); - let (mut parts, body) = limited.into_parts(); - - let path: Path> = parts.extract().await?; - let query = parts.uri.query().unwrap_or_default(); - let query = serde_html_form::from_str(query) - .map_err(|e| err!(Request(Unknown("Failed to read query parameters: {e}"))))?; - - let max_body_size = services.server.config.max_request_size; - - // Check if the Content-Length header is present and valid, saves us streaming - // the response into memory - if let Some(content_length) = parts.headers.get(http::header::CONTENT_LENGTH) { - if let Ok(content_length) = content_length - .to_str() - .map(|s| s.parse::().unwrap_or_default()) - { - if content_length > max_body_size { - return Err(err!(Request(TooLarge("Request body too large")))); - } - } - } - - let body = axum::body::to_bytes(body, max_body_size) - .await - .map_err(|e| err!(Request(TooLarge("Request body too large: {e}"))))?; - - Ok(Request { path, query, body, parts }) -} diff --git a/src/core/config/mod.rs b/src/core/config/mod.rs index c419093f1..a1250acfe 100644 --- a/src/core/config/mod.rs +++ b/src/core/config/mod.rs @@ -710,18 +710,6 @@ pub struct Config { #[serde(default)] pub allow_public_room_directory_over_federation: bool, - /// Allow guests/unauthenticated users to access TURN credentials. - /// - /// This is the equivalent of Synapse's `turn_allow_guests` config option. - /// This allows any unauthenticated user to call the endpoint - /// `/_matrix/client/v3/voip/turnServer`. - /// - /// It is unlikely you need to enable this as all major clients support - /// authentication for this endpoint and prevents misuse of your TURN server - /// from potential bots. - #[serde(default)] - pub turn_allow_guests: bool, - /// Set this to true to lock down your server's public room directory and /// only allow admins to publish rooms to the room directory. Unpublishing /// is still allowed by all users with this enabled. diff --git a/src/core/error/mod.rs b/src/core/error/mod.rs index 122a8f8eb..54fa18394 100644 --- a/src/core/error/mod.rs +++ b/src/core/error/mod.rs @@ -60,6 +60,8 @@ pub enum Error { Path(#[from] axum::extract::rejection::PathRejection), #[error("Mutex poisoned: {0}")] Poison(Cow<'static, str>), + #[error(transparent)] + Query(#[from] axum::extract::rejection::QueryRejection), #[error("Regex error: {0}")] Regex(#[from] regex::Error), #[error("{0}")]