mas_handlers/compat/
mod.rs
1use axum::{
8 Json,
9 body::Bytes,
10 extract::{
11 Request,
12 rejection::{BytesRejection, FailedToBufferBody},
13 },
14 response::IntoResponse,
15};
16use hyper::{StatusCode, header};
17use mas_axum_utils::sentry::SentryEventID;
18use serde::{Serialize, de::DeserializeOwned};
19use thiserror::Error;
20
21pub(crate) mod login;
22pub(crate) mod login_sso_complete;
23pub(crate) mod login_sso_redirect;
24pub(crate) mod logout;
25pub(crate) mod refresh;
26
27#[derive(Debug, Serialize)]
28struct MatrixError {
29 errcode: &'static str,
30 error: &'static str,
31 #[serde(skip)]
32 status: StatusCode,
33}
34
35impl IntoResponse for MatrixError {
36 fn into_response(self) -> axum::response::Response {
37 (self.status, Json(self)).into_response()
38 }
39}
40
41#[derive(Debug, Clone, Copy, Default)]
42#[must_use]
43pub struct MatrixJsonBody<T>(pub T);
44
45#[derive(Debug, Error)]
46pub enum MatrixJsonBodyRejection {
47 #[error("Invalid Content-Type header: expected application/json")]
48 InvalidContentType,
49
50 #[error("Invalid Content-Type header: expected application/json, got {0}")]
51 ContentTypeNotJson(mime::Mime),
52
53 #[error("Failed to read request body")]
54 BytesRejection(#[from] BytesRejection),
55
56 #[error("Invalid JSON document")]
57 Json(#[from] serde_json::Error),
58}
59
60impl IntoResponse for MatrixJsonBodyRejection {
61 fn into_response(self) -> axum::response::Response {
62 let event_id = sentry::capture_error(&self);
63 let response = match self {
64 Self::InvalidContentType | Self::ContentTypeNotJson(_) => MatrixError {
65 errcode: "M_NOT_JSON",
66 error: "Invalid Content-Type header: expected application/json",
67 status: StatusCode::BAD_REQUEST,
68 },
69
70 Self::BytesRejection(BytesRejection::FailedToBufferBody(
71 FailedToBufferBody::LengthLimitError(_),
72 )) => MatrixError {
73 errcode: "M_TOO_LARGE",
74 error: "Request body too large",
75 status: StatusCode::PAYLOAD_TOO_LARGE,
76 },
77
78 Self::BytesRejection(BytesRejection::FailedToBufferBody(
79 FailedToBufferBody::UnknownBodyError(_),
80 )) => MatrixError {
81 errcode: "M_UNKNOWN",
82 error: "Failed to read request body",
83 status: StatusCode::BAD_REQUEST,
84 },
85
86 Self::BytesRejection(_) => MatrixError {
87 errcode: "M_UNKNOWN",
88 error: "Unknown error while reading request body",
89 status: StatusCode::BAD_REQUEST,
90 },
91
92 Self::Json(err) if err.is_data() => MatrixError {
93 errcode: "M_BAD_JSON",
94 error: "JSON fields are not valid",
95 status: StatusCode::BAD_REQUEST,
96 },
97
98 Self::Json(_) => MatrixError {
99 errcode: "M_NOT_JSON",
100 error: "Body is not a valid JSON document",
101 status: StatusCode::BAD_REQUEST,
102 },
103 };
104
105 (SentryEventID::from(event_id), response).into_response()
106 }
107}
108
109impl<T, S> axum::extract::FromRequest<S> for MatrixJsonBody<T>
110where
111 T: DeserializeOwned,
112 S: Send + Sync,
113{
114 type Rejection = MatrixJsonBodyRejection;
115
116 async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {
117 if let Some(content_type) = req.headers().get(header::CONTENT_TYPE) {
120 let Ok(content_type) = content_type.to_str() else {
121 return Err(MatrixJsonBodyRejection::InvalidContentType);
122 };
123
124 let Ok(mime) = content_type.parse::<mime::Mime>() else {
125 return Err(MatrixJsonBodyRejection::InvalidContentType);
126 };
127
128 let is_json_content_type = mime.type_() == "application"
129 && (mime.subtype() == "json" || mime.suffix().is_some_and(|name| name == "json"));
130
131 if !is_json_content_type {
132 return Err(MatrixJsonBodyRejection::ContentTypeNotJson(mime));
133 }
134 }
135
136 let bytes = Bytes::from_request(req, state).await?;
137
138 let value: T = serde_json::from_slice(&bytes)?;
139
140 Ok(Self(value))
141 }
142}