Files
continuwuity/src/api/router/auth.rs
T

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

326 lines
8.5 KiB
Rust
Raw Normal View History

2026-04-13 16:20:47 -04:00
use std::any::{Any, TypeId};
use conduwuit::{Err, Result, err};
2024-05-23 18:44:19 +00:00
use ruma::{
DeviceId, OwnedDeviceId, OwnedServerName, OwnedUserId, UserId,
api::{
2026-04-13 16:20:47 -04:00
IncomingRequest,
auth_scheme::{
AccessToken, AccessTokenOptional, AppserviceToken, AppserviceTokenOptional,
AuthScheme, NoAccessToken, NoAuthentication,
},
client,
2026-04-13 16:20:47 -04:00
federation::authentication::ServerSignatures,
},
};
use service::{
Services,
server_keys::{PubKeyMap, PubKeys},
2024-05-23 18:44:19 +00:00
};
2026-04-13 16:20:47 -04:00
use crate::{router::args::AuthQueryParams, service::appservice::RegistrationInfo};
2024-05-23 18:44:19 +00:00
pub(crate) enum ClientIdentity {
User {
sender_user: OwnedUserId,
sender_device: OwnedDeviceId,
},
Appservice {
sender_user: OwnedUserId,
sender_device: Option<OwnedDeviceId>,
appservice_info: RegistrationInfo,
},
2024-05-23 18:44:19 +00:00
}
impl ClientIdentity {
pub(crate) fn sender_user(&self) -> &UserId {
match self {
| Self::User { sender_user, .. } | Self::Appservice { sender_user, .. } =>
sender_user,
}
}
pub(crate) fn sender_device(&self) -> Option<&DeviceId> {
match self {
| Self::User { sender_device, .. } => Some(sender_device),
| Self::Appservice { sender_device, .. } => sender_device.as_deref(),
}
}
pub(crate) fn expect_sender_device(&self) -> Result<&DeviceId> {
self.sender_device().ok_or_else(|| {
err!(Request(Forbidden("Appservices must masquerade to use this endpoint")))
})
}
pub(crate) fn appservice_info(&self) -> Option<&RegistrationInfo> {
match self {
| Self::User { .. } => None,
| Self::Appservice { appservice_info, .. } => Some(appservice_info),
}
}
pub(crate) fn is_appservice(&self) -> bool { matches!(self, Self::Appservice { .. }) }
}
pub(crate) trait CheckAuth: AuthScheme {
type Identity: Send;
2026-04-13 16:20:47 -04:00
fn authenticate<R: IncomingRequest + Any, B: AsRef<[u8]> + Sync>(
services: &Services,
incoming_request: &hyper::Request<B>,
query: AuthQueryParams,
) -> impl Future<Output = Result<Self::Identity>> + Send {
2026-04-13 16:20:47 -04:00
async move {
let route = TypeId::of::<R>();
2026-04-13 18:31:16 -04:00
let output = Self::extract_authentication(incoming_request).map_err(|err| {
2026-04-13 16:20:47 -04:00
err!(Request(Unauthorized(warn!(
"Failed to extract authorization: {}",
err.into()
))))
})?;
2024-05-23 18:44:19 +00:00
2026-04-13 16:20:47 -04:00
Self::verify(services, output, incoming_request, query, route).await
2025-02-25 18:38:12 +00:00
}
2024-05-23 18:44:19 +00:00
}
2026-04-13 16:20:47 -04:00
fn verify<B: AsRef<[u8]> + Sync>(
services: &Services,
output: Self::Output,
request: &hyper::Request<B>,
query: AuthQueryParams,
route: TypeId,
) -> impl Future<Output = Result<Self::Identity>> + Send;
2024-05-23 18:44:19 +00:00
}
2026-04-13 16:20:47 -04:00
impl CheckAuth for ServerSignatures {
type Identity = OwnedServerName;
2026-04-13 16:20:47 -04:00
async fn verify<B: AsRef<[u8]> + Sync>(
services: &Services,
output: Self::Output,
request: &hyper::Request<B>,
_query: AuthQueryParams,
_route: TypeId,
) -> Result<Self::Identity> {
2026-04-13 16:20:47 -04:00
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}"))))
})?;
2026-04-13 16:20:47 -04:00
let keys: PubKeys = [(output.key.to_string(), key.key)].into();
let keys: PubKeyMap = [(output.origin.as_str().into(), keys)].into();
2024-05-23 18:44:19 +00:00
2026-04-13 16:20:47 -04:00
match output.verify_request(request, destination, &keys) {
| Ok(()) => {
if services
.moderation
.is_remote_server_forbidden(&output.origin)
{
2026-05-20 12:57:46 -04:00
return Err!(Request(Forbidden(
"You are blocked from federating with this server."
)));
}
Ok(output.origin)
},
2026-04-13 16:20:47 -04:00
| Err(err) =>
Err!(Request(Unauthorized(warn!("Failed to verify X-Matrix header: {err}")))),
}
2024-05-23 18:44:19 +00:00
}
2026-04-13 16:20:47 -04:00
}
2024-05-23 18:44:19 +00:00
2026-04-13 16:20:47 -04:00
impl CheckAuth for AccessToken {
type Identity = ClientIdentity;
2026-04-13 16:20:47 -04:00
async fn verify<B: AsRef<[u8]> + Sync>(
services: &Services,
output: Self::Output,
_request: &hyper::Request<B>,
2026-04-29 09:09:09 -04:00
query: AuthQueryParams,
2026-04-13 16:20:47 -04:00
route: TypeId,
) -> Result<Self::Identity> {
if let Ok((sender_user, sender_device)) = services.users.find_from_token(&output).await {
// Locked users can only use /logout and /logout/all
if services
.users
.is_locked(&sender_user)
.await
.is_ok_and(std::convert::identity)
2026-04-13 16:20:47 -04:00
{
if !(route == TypeId::of::<client::session::logout::v3::Request>()
|| route == TypeId::of::<client::session::logout_all::v3::Request>())
2026-04-29 09:09:09 -04:00
{
return Err!(Request(Unauthorized("Your account is locked.")));
2026-04-29 09:09:09 -04:00
}
}
2026-04-29 09:09:09 -04:00
Ok(ClientIdentity::User { sender_user, sender_device })
} else if let Ok(appservice_info) = services.appservice.find_from_token(&output).await {
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.")));
};
2026-04-29 09:09:09 -04:00
if !appservice_info.is_user_match(&sender_user) {
return Err!(Request(Exclusive("User is not in namespace.")));
2026-04-13 16:20:47 -04:00
}
// 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."
)));
}
Some(device_id.to_owned())
} else {
None
};
Ok(ClientIdentity::Appservice {
sender_user,
sender_device,
appservice_info,
})
} else {
return Err!(Request(Unauthorized("Invalid access token.")));
}
2026-04-13 16:20:47 -04:00
}
2024-05-23 18:44:19 +00:00
}
2026-04-13 16:20:47 -04:00
impl CheckAuth for AccessTokenOptional {
type Identity = Option<ClientIdentity>;
2026-04-13 16:20:47 -04:00
async fn verify<B: AsRef<[u8]> + Sync>(
services: &Services,
output: Self::Output,
request: &hyper::Request<B>,
query: AuthQueryParams,
route: TypeId,
) -> Result<Self::Identity> {
2026-04-13 16:20:47 -04:00
match output {
| Some(token) =>
<AccessToken as CheckAuth>::verify(services, token, request, query, route)
.await
.map(Some),
| None => Ok(None),
2026-04-13 16:20:47 -04:00
}
}
}
2026-04-13 16:20:47 -04:00
impl CheckAuth for AppserviceToken {
type Identity = RegistrationInfo;
2026-04-13 16:20:47 -04:00
async fn verify<B: AsRef<[u8]> + Sync>(
services: &Services,
output: Self::Output,
_request: &hyper::Request<B>,
2026-04-29 09:09:09 -04:00
_query: AuthQueryParams,
2026-04-13 16:20:47 -04:00
_route: TypeId,
) -> Result<Self::Identity> {
2026-04-13 16:20:47 -04:00
let Ok(appservice_info) = services.appservice.find_from_token(&output).await else {
return Err!(Request(Unauthorized("Invalid appservice token.")));
};
Ok(appservice_info)
}
2026-04-13 16:20:47 -04:00
}
2026-04-13 16:20:47 -04:00
impl CheckAuth for AppserviceTokenOptional {
type Identity = Option<RegistrationInfo>;
2026-04-13 16:20:47 -04:00
async fn verify<B: AsRef<[u8]> + Sync>(
services: &Services,
output: Self::Output,
request: &hyper::Request<B>,
query: AuthQueryParams,
route: TypeId,
) -> Result<Self::Identity> {
2026-04-13 16:20:47 -04:00
match output {
| Some(token) =>
<AppserviceToken as CheckAuth>::verify(services, token, request, query, route)
.await
.map(Some),
| None => Ok(None),
2026-04-13 16:20:47 -04:00
}
}
2026-04-13 16:20:47 -04:00
}
2026-04-13 16:20:47 -04:00
impl CheckAuth for NoAuthentication {
type Identity = ();
2026-04-13 16:20:47 -04:00
async fn verify<B: AsRef<[u8]> + Sync>(
_services: &Services,
_output: Self::Output,
_request: &hyper::Request<B>,
_query: AuthQueryParams,
_route: TypeId,
) -> Result<Self::Identity> {
Ok(())
}
}
2024-05-23 18:44:19 +00:00
2026-04-13 16:20:47 -04:00
impl CheckAuth for NoAccessToken {
type Identity = Option<ClientIdentity>;
2026-04-13 16:20:47 -04:00
async fn verify<B: AsRef<[u8]> + Sync>(
services: &Services,
_output: Self::Output,
request: &hyper::Request<B>,
query: AuthQueryParams,
route: TypeId,
) -> Result<Self::Identity> {
2026-04-13 16:20:47 -04:00
// 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))))
})?;
2024-05-23 18:44:19 +00:00
// Check special access restrictions
if (route == TypeId::of::<client::profile::get_avatar_url::v3::Request>()
|| route == TypeId::of::<client::profile::get_display_name::v3::Request>()
|| route == TypeId::of::<client::profile::get_profile_field::v3::Request>()
|| route == TypeId::of::<client::profile::get_profile::v3::Request>())
&& services.config.require_auth_for_profile_requests
&& token.is_none()
{
return Err!(Request(Unauthorized(
"This server requires authentication to access user profiles."
)));
}
2026-04-13 16:20:47 -04:00
<AccessTokenOptional as CheckAuth>::verify(services, token, request, query, route).await
}
}