use std::any::{Any, TypeId}; use conduwuit::{Err, Result, err}; use ruma::{ OwnedDeviceId, OwnedServerName, OwnedUserId, UserId, api::{ IncomingRequest, auth_scheme::{ AccessToken, AccessTokenOptional, AppserviceToken, AppserviceTokenOptional, AuthScheme, NoAccessToken, NoAuthentication, }, federation::authentication::ServerSignatures, }, }; use service::{ Services, server_keys::{PubKeyMap, PubKeys}, }; use crate::{router::args::AuthQueryParams, service::appservice::RegistrationInfo}; enum Token { Appservice(Box), User((OwnedUserId, OwnedDeviceId)), Invalid, None, } #[derive(Default)] pub(super) struct Auth { pub(super) origin: Option, pub(super) sender_user: Option, pub(super) sender_device: Option, pub(super) appservice_info: Option, } 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 output = Self::extract_authentication(&incoming_request).map_err(|err| { err!(Request(Unauthorized(warn!( "Failed to extract authorization: {}", err.into() )))) })?; Self::verify(services, output, incoming_request, query, route).await } } 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}")))) })?; 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}")))), } } } 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."))); }; if services .users .is_locked(&sender_user) .await .is_ok_and(std::convert::identity) { // Locked users can only use /logout and /logout/all if !(route == TypeId::of::() || route == TypeId::of::()) { return Err!(Request(Unauthorized("Your account is locked."))); } } return Ok(Auth { sender_user: Some(sender_user), sender_device: Some(sender_device), ..Default::default() }); } } 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()), } } } 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 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."))); }; if !appservice_info.is_user_match(&sender_user) { 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(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(Auth { appservice_info: Some(appservice_info), sender_user: Some(sender_user), sender_device, ..Default::default() }) } } 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()), } } } impl CheckAuth for NoAuthentication { async fn verify + Sync>( _services: &Services, _output: Self::Output, _request: &hyper::Request, _query: AuthQueryParams, _route: TypeId, ) -> Result { Ok(Auth::default()) } } 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)))) })?; ::verify(services, token, request, query, route).await } }