refactor: Fix errors in api/router/

This commit is contained in:
Ginger
2026-04-13 16:20:47 -04:00
parent a7041854ca
commit be4bb3e0e4
8 changed files with 275 additions and 482 deletions

View File

@@ -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.

View File

@@ -1,7 +1,6 @@
mod args;
mod auth;
mod handler;
mod request;
mod response;
use std::str::FromStr;

View File

@@ -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<String>,
pub(super) user_id: Option<String>,
/// 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<String>,
}
/// Extractor for Ruma request structs
pub(crate) struct Args<T> {
@@ -77,9 +91,9 @@ impl<T> Deref for Args<T>
fn deref(&self) -> &Self::Target { &self.body }
}
impl<T> FromRequest<State, Body> for Args<T>
impl<R> FromRequest<State, Body> for Args<R>
where
T: IncomingRequest + Send + Sync + 'static,
R: IncomingRequest<Authentication: CheckAuth> + Send + Sync + 'static,
{
type Rejection = Error;
@@ -87,27 +101,53 @@ async fn from_request(
request: hyper::Request<Body>,
services: &State,
) -> Result<Self, Self::Rejection> {
let mut request = request::from(services, request).await?;
let mut json_body = serde_json::from_slice::<CanonicalJsonValue>(&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::<usize>().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::<CanonicalJsonValue>(&body).ok();
// Extract the query parameters and path
let Path(path): Path<Vec<String>> = parts.extract().await?;
let Query(auth_query): Query<AuthQueryParams> = 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::<R, bytes::Bytes>(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::<T>(&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<T>(request: &mut Request, json_body: Option<&mut CanonicalJsonValue>) -> Result<T>
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<Bytes> {
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()
}

View File

@@ -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<RegistrationInfo>),
@@ -42,6 +26,7 @@ enum Token {
None,
}
#[derive(Default)]
pub(super) struct Auth {
pub(super) origin: Option<OwnedServerName>,
pub(super) sender_user: Option<OwnedUserId>,
@@ -49,350 +34,227 @@ pub(super) struct Auth {
pub(super) appservice_info: Option<RegistrationInfo>,
}
pub(super) async fn auth(
services: &Services,
request: &mut Request,
json_body: Option<&CanonicalJsonValue>,
metadata: &Metadata,
) -> Result<Auth> {
let bearer: Option<TypedHeader<Authorization<Bearer>>> =
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<R: IncomingRequest + Any, B: AsRef<[u8]> + Sync>(
services: &Services,
incoming_request: &hyper::Request<B>,
query: AuthQueryParams,
) -> impl Future<Output = Result<Auth>> + Send {
async move {
let route = TypeId::of::<R>();
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<B: AsRef<[u8]> + Sync>(
services: &Services,
output: Self::Output,
request: &hyper::Request<B>,
query: AuthQueryParams,
route: TypeId,
) -> impl Future<Output = Result<Auth>> + Send;
}
impl CheckAuth for ServerSignatures {
async fn verify<B: AsRef<[u8]> + Sync>(
services: &Services,
output: Self::Output,
request: &hyper::Request<B>,
_query: AuthQueryParams,
_route: TypeId,
) -> Result<Auth> {
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<RegistrationInfo>,
) -> Result<Auth> {
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<B: AsRef<[u8]> + Sync>(
services: &Services,
output: Self::Output,
_request: &hyper::Request<B>,
_query: AuthQueryParams,
route: TypeId,
) -> Result<Auth> {
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::<ruma::api::client::session::logout::v3::Request>()
|| route == TypeId::of::<ruma::api::client::session::logout_all::v3::Request>())
{
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<Auth> {
type Member = (String, CanonicalJsonValue);
type Object = CanonicalJsonObject;
type Value = CanonicalJsonValue;
impl CheckAuth for AccessTokenOptional {
async fn verify<B: AsRef<[u8]> + Sync>(
services: &Services,
output: Self::Output,
request: &hyper::Request<B>,
query: AuthQueryParams,
route: TypeId,
) -> Result<Auth> {
match output {
| Some(token) =>
<AccessToken as CheckAuth>::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<B: AsRef<[u8]> + Sync>(
services: &Services,
output: Self::Output,
_request: &hyper::Request<B>,
query: AuthQueryParams,
_route: TypeId,
) -> Result<Auth> {
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<B: AsRef<[u8]> + Sync>(
services: &Services,
output: Self::Output,
request: &hyper::Request<B>,
query: AuthQueryParams,
route: TypeId,
) -> Result<Auth> {
match output {
| Some(token) =>
<AppserviceToken as CheckAuth>::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<XMatrix> {
let TypedHeader(Authorization(x_matrix)) = request
.parts
.extract::<TypedHeader<Authorization<XMatrix>>>()
.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<B: AsRef<[u8]> + Sync>(
_services: &Services,
_output: Self::Output,
_request: &hyper::Request<B>,
_query: AuthQueryParams,
_route: TypeId,
) -> Result<Auth> {
Ok(Auth::default())
}
}
err!(Request(Forbidden(warn!("{msg}: {e}"))))
impl CheckAuth for NoAccessToken {
async fn verify<B: AsRef<[u8]> + Sync>(
services: &Services,
_output: Self::Output,
request: &hyper::Request<B>,
query: AuthQueryParams,
route: TypeId,
) -> Result<Auth> {
// 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<Token> {
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),
<AccessTokenOptional as CheckAuth>::verify(services, token, request, query, route).await
}
}

View File

@@ -38,7 +38,7 @@ impl<Err, Req, Fut, Fun, $($tx,)*> RumaHandler<($($tx,)* Ruma<Req>,)> for Fun
where
Fun: Fn($($tx,)* Ruma<Req>,) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<Req::OutgoingResponse, Err>> + Send,
Req: IncomingRequest + Send + Sync + 'static,
Req: IncomingRequest<Authentication: $crate::router::auth::CheckAuth> + Send + Sync + 'static,
Err: IntoResponse + Send,
<Req as IncomingRequest>::OutgoingResponse: Send,
$( $tx: FromRequestParts<State> + Send + Sync + 'static, )*

View File

@@ -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<String>,
pub(super) user_id: Option<String>,
/// 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<String>,
}
pub(super) struct Request {
pub(super) path: Path<Vec<String>>,
pub(super) query: QueryParams,
pub(super) body: Bytes,
pub(super) parts: Parts,
}
pub(super) async fn from(
services: &Services,
request: hyper::Request<axum::body::Body>,
) -> Result<Request> {
let limited = request.with_limited_body();
let (mut parts, body) = limited.into_parts();
let path: Path<Vec<String>> = 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::<usize>().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 })
}

View File

@@ -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.

View File

@@ -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}")]