Skip to main content

arminius_server_client/
client_request.rs

1// SPDX-FileCopyrightText: 2025 BWI GmbH
2//
3// SPDX-License-Identifier: Apache-2.0
4
5//! Wrappers around ruma requests that verify authentication
6
7use arminius_lib_authentication::AuthenticatedClaims;
8use arminius_lib_component::{
9	ServerState,
10	error::MatrixApiError,
11	ruma_requests::{ParseRequestError, extract_ruma_request, to_vector_request},
12};
13use axum::extract::{FromRequestParts as _, Request};
14use ruma::api::{
15	Metadata,
16	auth_scheme::{AccessToken, AccessTokenOptional, AppserviceTokenOptional, NoAccessToken},
17};
18
19/// Error type which arises when parsing authenticated requests
20#[derive(MatrixApiError, thiserror::Error, Debug)]
21pub enum AuthenticatedRequestError {
22	/// Error validating authentication
23	#[error(transparent)]
24	AuthError(#[from] arminius_lib_authentication::AuthError),
25	/// Failed parsing the ruma request
26	#[error(transparent)]
27	ParseRequestError(#[from] ParseRequestError),
28}
29
30/// Wrapper around requests to the client API that require authentication
31///
32/// Provides an axum extractor, but only if the associated `Authentication` type
33/// is `AccessToken`. If you're getting strange trait errors you don't
34/// understand, that's probably what's wrong
35#[derive(Debug)]
36pub struct AuthenticatedClientRequest<T: ruma::api::IncomingRequest>(
37	pub arminius_lib_authentication::SessionInfo,
38	pub T,
39);
40
41impl<T: ruma::api::IncomingRequest> axum::extract::FromRequest<ServerState>
42	for AuthenticatedClientRequest<T>
43where
44	// Hack around equality not being supported in trait bounds
45	<T as Metadata>::Authentication: From<AccessToken> + Into<AccessToken>,
46{
47	type Rejection = AuthenticatedRequestError;
48	async fn from_request(
49		request: Request,
50		state: &ServerState,
51	) -> Result<Self, AuthenticatedRequestError> {
52		let (mut parts, body) = request.into_parts();
53		let AuthenticatedClaims(session_info) =
54			AuthenticatedClaims::from_request_parts(&mut parts, state).await?;
55		let request = Request::from_parts(parts, body);
56		let request = to_vector_request(request).await?;
57		Ok(Self(session_info, extract_ruma_request(request).await?))
58	}
59}
60
61/// Wrapper around requests to the client API that are optionally authenticated
62///
63/// Provides an axum extractor, but only if the associated `Authentication` type
64/// is `AccessTokenOptional`. If you're getting strange trait errors you don't
65/// understand, that's probably what's wrong
66#[expect(dead_code, reason = "This might be used in the future")]
67#[derive(Debug)]
68pub struct OptionallyAuthenticatedClientRequest<T: ruma::api::IncomingRequest>(
69	pub Option<arminius_lib_authentication::SessionInfo>,
70	pub T,
71);
72
73impl<T: ruma::api::IncomingRequest> axum::extract::FromRequest<ServerState>
74	for OptionallyAuthenticatedClientRequest<T>
75where
76	// Hack around equality not being supported in trait bounds
77	<T as Metadata>::Authentication: From<AccessTokenOptional> + Into<AccessTokenOptional>,
78{
79	type Rejection = AuthenticatedRequestError;
80	async fn from_request(
81		request: Request,
82		state: &ServerState,
83	) -> Result<Self, AuthenticatedRequestError> {
84		let (mut parts, body) = request.into_parts();
85		let claims = if parts.headers.contains_key("Authorization") {
86			Some(AuthenticatedClaims::from_request_parts(&mut parts, state).await?.0)
87		} else {
88			None
89		};
90		let request = Request::from_parts(parts, body);
91		let request = to_vector_request(request).await?;
92		Ok(Self(claims, extract_ruma_request(request).await?))
93	}
94}
95
96/// Wrapper around requests to the client API that are not authenticated
97///
98/// Provides an axum extractor, but only if the associated `Authentication` type
99/// is `NoAccessToken`. If you're getting strange trait errors you don't
100/// understand, that's probably what's wrong
101#[derive(Debug)]
102pub struct UnauthenticatedClientRequest<T: ruma::api::IncomingRequest>(pub T);
103
104impl<T: ruma::api::IncomingRequest> axum::extract::FromRequest<ServerState>
105	for UnauthenticatedClientRequest<T>
106where
107	// Hack around equality not being supported in trait bounds
108	<T as Metadata>::Authentication: From<NoAccessToken> + Into<NoAccessToken>,
109{
110	type Rejection = AuthenticatedRequestError;
111	async fn from_request(
112		request: Request,
113		_: &ServerState,
114	) -> Result<Self, AuthenticatedRequestError> {
115		let request = to_vector_request(request).await?;
116		Ok(Self(extract_ruma_request(request).await?))
117	}
118}
119
120/// Wrapper around requests to the client API that are not authenticated
121///
122/// Provides an axum extractor, but only if the associated `Authentication` type
123/// is `NoAccessToken`. If you're getting strange trait errors you don't
124/// understand, that's probably what's wrong
125#[derive(Debug)]
126pub struct AppserviceTokenOptionalClientRequest<T: ruma::api::IncomingRequest>(pub T);
127
128impl<T: ruma::api::IncomingRequest> axum::extract::FromRequest<ServerState>
129	for AppserviceTokenOptionalClientRequest<T>
130where
131	// Hack around equality not being supported in trait bounds
132	<T as Metadata>::Authentication: From<AppserviceTokenOptional> + Into<AppserviceTokenOptional>,
133{
134	type Rejection = AuthenticatedRequestError;
135	async fn from_request(
136		request: Request,
137		_: &ServerState,
138	) -> Result<Self, AuthenticatedRequestError> {
139		let request = to_vector_request(request).await?;
140		Ok(Self(extract_ruma_request(request).await?))
141	}
142}