1#![allow(clippy::exhaustive_structs)]
5
6use as_variant::as_variant;
7use http::{HeaderMap, header};
8use serde::Deserialize;
9
10pub trait AuthScheme: Sized {
12 type Input<'a>;
14
15 type AddAuthenticationError: Into<Box<dyn std::error::Error + Send + Sync + 'static>>;
17
18 type Output;
20
21 type ExtractAuthenticationError: Into<Box<dyn std::error::Error + Send + Sync + 'static>>;
23
24 fn add_authentication<T: AsRef<[u8]>>(
29 request: &mut http::Request<T>,
30 input: Self::Input<'_>,
31 ) -> Result<(), Self::AddAuthenticationError>;
32
33 fn extract_authentication<T>(
38 request: &http::Request<T>,
39 ) -> Result<Self::Output, Self::ExtractAuthenticationError>;
40}
41
42pub trait ClientScopedAuthScheme: AuthScheme {}
49
50#[derive(Debug, Clone, Copy, Default)]
52pub struct NoAuthentication;
53
54impl AuthScheme for NoAuthentication {
55 type Input<'a> = ();
56 type AddAuthenticationError = std::convert::Infallible;
57 type Output = ();
58 type ExtractAuthenticationError = std::convert::Infallible;
59
60 fn add_authentication<T: AsRef<[u8]>>(
61 _request: &mut http::Request<T>,
62 _input: (),
63 ) -> Result<(), Self::AddAuthenticationError> {
64 Ok(())
65 }
66
67 fn extract_authentication<T>(
69 _request: &http::Request<T>,
70 ) -> Result<(), Self::ExtractAuthenticationError> {
71 Ok(())
72 }
73}
74
75#[derive(Debug, Clone, Copy, Default)]
80pub struct NoAccessToken;
81
82impl AuthScheme for NoAccessToken {
83 type Input<'a> = SendAccessToken<'a>;
84 type AddAuthenticationError = header::InvalidHeaderValue;
85 type Output = ();
86 type ExtractAuthenticationError = std::convert::Infallible;
87
88 fn add_authentication<T: AsRef<[u8]>>(
89 request: &mut http::Request<T>,
90 access_token: SendAccessToken<'_>,
91 ) -> Result<(), Self::AddAuthenticationError> {
92 if let Some(access_token) = access_token.get_not_required_for_endpoint() {
93 add_access_token_as_authorization_header(request.headers_mut(), access_token)?;
94 }
95
96 Ok(())
97 }
98
99 fn extract_authentication<T>(
101 _request: &http::Request<T>,
102 ) -> Result<(), Self::ExtractAuthenticationError> {
103 Ok(())
104 }
105}
106
107#[derive(Debug, Clone, Copy, Default)]
112pub struct AccessToken;
113
114impl AuthScheme for AccessToken {
115 type Input<'a> = SendAccessToken<'a>;
116 type AddAuthenticationError = AddRequiredTokenError;
117 type Output = String;
119 type ExtractAuthenticationError = ExtractTokenError;
120
121 fn add_authentication<T>(
122 request: &mut http::Request<T>,
123 access_token: SendAccessToken<'_>,
124 ) -> Result<(), Self::AddAuthenticationError> {
125 let token = access_token
126 .get_required_for_endpoint()
127 .ok_or(AddRequiredTokenError::MissingAccessToken)?;
128 Ok(add_access_token_as_authorization_header(request.headers_mut(), token)?)
129 }
130
131 fn extract_authentication<T>(
132 request: &http::Request<T>,
133 ) -> Result<String, Self::ExtractAuthenticationError> {
134 extract_bearer_or_query_token(request)?.ok_or(ExtractTokenError::MissingAccessToken)
135 }
136}
137
138impl ClientScopedAuthScheme for AccessToken {}
139
140#[derive(Debug, Clone, Copy, Default)]
145pub struct AccessTokenOptional;
146
147impl AuthScheme for AccessTokenOptional {
148 type Input<'a> = SendAccessToken<'a>;
149 type AddAuthenticationError = header::InvalidHeaderValue;
150 type Output = Option<String>;
152 type ExtractAuthenticationError = ExtractTokenError;
153
154 fn add_authentication<T: AsRef<[u8]>>(
155 request: &mut http::Request<T>,
156 access_token: SendAccessToken<'_>,
157 ) -> Result<(), Self::AddAuthenticationError> {
158 if let Some(access_token) = access_token.get_required_for_endpoint() {
159 add_access_token_as_authorization_header(request.headers_mut(), access_token)?;
160 }
161
162 Ok(())
163 }
164
165 fn extract_authentication<T>(
166 request: &http::Request<T>,
167 ) -> Result<Option<String>, Self::ExtractAuthenticationError> {
168 extract_bearer_or_query_token(request)
169 }
170}
171
172impl ClientScopedAuthScheme for AccessTokenOptional {}
173
174#[derive(Debug, Clone, Copy, Default)]
180pub struct AppserviceToken;
181
182impl AuthScheme for AppserviceToken {
183 type Input<'a> = SendAccessToken<'a>;
184 type AddAuthenticationError = AddRequiredTokenError;
185 type Output = String;
187 type ExtractAuthenticationError = ExtractTokenError;
188
189 fn add_authentication<T: AsRef<[u8]>>(
190 request: &mut http::Request<T>,
191 access_token: SendAccessToken<'_>,
192 ) -> Result<(), Self::AddAuthenticationError> {
193 let token = access_token
194 .get_required_for_appservice()
195 .ok_or(AddRequiredTokenError::MissingAccessToken)?;
196 Ok(add_access_token_as_authorization_header(request.headers_mut(), token)?)
197 }
198
199 fn extract_authentication<T>(
200 request: &http::Request<T>,
201 ) -> Result<String, Self::ExtractAuthenticationError> {
202 extract_bearer_or_query_token(request)?.ok_or(ExtractTokenError::MissingAccessToken)
203 }
204}
205
206#[derive(Debug, Clone, Copy, Default)]
212pub struct AppserviceTokenOptional;
213
214impl AuthScheme for AppserviceTokenOptional {
215 type Input<'a> = SendAccessToken<'a>;
216 type AddAuthenticationError = header::InvalidHeaderValue;
217 type Output = Option<String>;
219 type ExtractAuthenticationError = ExtractTokenError;
220
221 fn add_authentication<T: AsRef<[u8]>>(
222 request: &mut http::Request<T>,
223 access_token: SendAccessToken<'_>,
224 ) -> Result<(), Self::AddAuthenticationError> {
225 if let Some(access_token) = access_token.get_required_for_appservice() {
226 add_access_token_as_authorization_header(request.headers_mut(), access_token)?;
227 }
228
229 Ok(())
230 }
231
232 fn extract_authentication<T>(
233 request: &http::Request<T>,
234 ) -> Result<Option<String>, Self::ExtractAuthenticationError> {
235 extract_bearer_or_query_token(request)
236 }
237}
238
239pub fn add_access_token_as_authorization_header(
241 headers: &mut HeaderMap,
242 token: &str,
243) -> Result<(), header::InvalidHeaderValue> {
244 headers.insert(header::AUTHORIZATION, format!("Bearer {token}").try_into()?);
245 Ok(())
246}
247
248pub fn extract_bearer_or_query_token<T>(
251 request: &http::Request<T>,
252) -> Result<Option<String>, ExtractTokenError> {
253 if let Some(token) = extract_bearer_token_from_authorization_header(request.headers())? {
254 return Ok(Some(token));
255 }
256
257 if let Some(query) = request.uri().query() {
258 Ok(extract_access_token_from_query(query)?)
259 } else {
260 Ok(None)
261 }
262}
263
264fn extract_bearer_token_from_authorization_header(
266 headers: &HeaderMap,
267) -> Result<Option<String>, ExtractTokenError> {
268 const EXPECTED_START: &str = "bearer ";
269
270 let Some(value) = headers.get(header::AUTHORIZATION) else {
271 return Ok(None);
272 };
273
274 let value = value.to_str()?;
275
276 if value.len() < EXPECTED_START.len()
277 || !value[..EXPECTED_START.len()].eq_ignore_ascii_case(EXPECTED_START)
278 {
279 return Err(ExtractTokenError::InvalidAuthorizationScheme);
280 }
281
282 Ok(Some(value[EXPECTED_START.len()..].to_owned()))
283}
284
285fn extract_access_token_from_query(
287 query: &str,
288) -> Result<Option<String>, serde_html_form::de::Error> {
289 #[derive(Deserialize)]
290 struct AccessTokenDeHelper {
291 access_token: Option<String>,
292 }
293
294 serde_html_form::from_str::<AccessTokenDeHelper>(query).map(|helper| helper.access_token)
295}
296
297#[derive(Clone, Copy, Debug)]
299#[allow(clippy::exhaustive_enums)]
300pub enum SendAccessToken<'a> {
301 IfRequired(&'a str),
304
305 Always(&'a str),
307
308 Appservice(&'a str),
311
312 None,
316}
317
318impl<'a> SendAccessToken<'a> {
319 pub fn get_required_for_endpoint(self) -> Option<&'a str> {
323 as_variant!(self, Self::IfRequired | Self::Appservice | Self::Always)
324 }
325
326 pub fn get_not_required_for_endpoint(self) -> Option<&'a str> {
330 as_variant!(self, Self::Always)
331 }
332
333 pub fn get_required_for_appservice(self) -> Option<&'a str> {
338 as_variant!(self, Self::Appservice | Self::Always)
339 }
340}
341
342#[derive(Debug, thiserror::Error)]
344#[non_exhaustive]
345pub enum AddRequiredTokenError {
346 #[error("no access token provided, but this endpoint requires one")]
348 MissingAccessToken,
349
350 #[error(transparent)]
352 IntoHeader(#[from] header::InvalidHeaderValue),
353}
354
355#[derive(Debug, thiserror::Error)]
357#[non_exhaustive]
358pub enum ExtractTokenError {
359 #[error("no access token found, but this endpoint requires one")]
361 MissingAccessToken,
362
363 #[error(transparent)]
365 FromHeader(#[from] header::ToStrError),
366
367 #[error("invalid authorization header scheme")]
369 InvalidAuthorizationScheme,
370
371 #[error("failed to deserialize query string: {0}")]
373 FromQuery(#[from] serde_html_form::de::Error),
374}