mas_handlers/compat/
mod.rs1use 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::record_error;
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 logout_all;
26pub(crate) mod refresh;
27
28#[derive(Debug, Serialize)]
29struct MatrixError {
30 errcode: &'static str,
31 error: &'static str,
32 #[serde(skip)]
33 status: StatusCode,
34}
35
36impl IntoResponse for MatrixError {
37 fn into_response(self) -> axum::response::Response {
38 (self.status, Json(self)).into_response()
39 }
40}
41
42#[derive(Debug, Clone, Copy, Default)]
43#[must_use]
44pub struct MatrixJsonBody<T>(pub T);
45
46#[derive(Debug, Error)]
47pub enum MatrixJsonBodyRejection {
48 #[error("Invalid Content-Type header: expected application/json")]
49 InvalidContentType,
50
51 #[error("Invalid Content-Type header: expected application/json, got {0}")]
52 ContentTypeNotJson(mime::Mime),
53
54 #[error("Failed to read request body")]
55 BytesRejection(#[from] BytesRejection),
56
57 #[error("Invalid JSON document")]
58 Json(#[from] serde_json::Error),
59}
60
61impl IntoResponse for MatrixJsonBodyRejection {
62 fn into_response(self) -> axum::response::Response {
63 let sentry_event_id = record_error!(self, !);
64 let response = match self {
65 Self::InvalidContentType | Self::ContentTypeNotJson(_) => MatrixError {
66 errcode: "M_NOT_JSON",
67 error: "Invalid Content-Type header: expected application/json",
68 status: StatusCode::BAD_REQUEST,
69 },
70
71 Self::BytesRejection(BytesRejection::FailedToBufferBody(
72 FailedToBufferBody::LengthLimitError(_),
73 )) => MatrixError {
74 errcode: "M_TOO_LARGE",
75 error: "Request body too large",
76 status: StatusCode::PAYLOAD_TOO_LARGE,
77 },
78
79 Self::BytesRejection(BytesRejection::FailedToBufferBody(
80 FailedToBufferBody::UnknownBodyError(_),
81 )) => MatrixError {
82 errcode: "M_UNKNOWN",
83 error: "Failed to read request body",
84 status: StatusCode::BAD_REQUEST,
85 },
86
87 Self::BytesRejection(_) => MatrixError {
88 errcode: "M_UNKNOWN",
89 error: "Unknown error while reading request body",
90 status: StatusCode::BAD_REQUEST,
91 },
92
93 Self::Json(err) if err.is_data() => MatrixError {
94 errcode: "M_BAD_JSON",
95 error: "JSON fields are not valid",
96 status: StatusCode::BAD_REQUEST,
97 },
98
99 Self::Json(_) => MatrixError {
100 errcode: "M_NOT_JSON",
101 error: "Body is not a valid JSON document",
102 status: StatusCode::BAD_REQUEST,
103 },
104 };
105
106 (sentry_event_id, response).into_response()
107 }
108}
109
110impl<T, S> axum::extract::FromRequest<S> for MatrixJsonBody<T>
111where
112 T: DeserializeOwned,
113 S: Send + Sync,
114{
115 type Rejection = MatrixJsonBodyRejection;
116
117 async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {
118 if let Some(content_type) = req.headers().get(header::CONTENT_TYPE) {
121 let Ok(content_type) = content_type.to_str() else {
122 return Err(MatrixJsonBodyRejection::InvalidContentType);
123 };
124
125 let Ok(mime) = content_type.parse::<mime::Mime>() else {
126 return Err(MatrixJsonBodyRejection::InvalidContentType);
127 };
128
129 let is_json_content_type = mime.type_() == "application"
130 && (mime.subtype() == "json" || mime.suffix().is_some_and(|name| name == "json"));
131
132 if !is_json_content_type {
133 return Err(MatrixJsonBodyRejection::ContentTypeNotJson(mime));
134 }
135 }
136
137 let bytes = Bytes::from_request(req, state).await?;
138
139 let value: T = serde_json::from_slice(&bytes)?;
140
141 Ok(Self(value))
142 }
143}
144
145impl<T, S> axum::extract::OptionalFromRequest<S> for MatrixJsonBody<T>
146where
147 T: DeserializeOwned,
148 S: Send + Sync,
149{
150 type Rejection = MatrixJsonBodyRejection;
151
152 async fn from_request(req: Request, state: &S) -> Result<Option<Self>, Self::Rejection> {
153 if req.headers().contains_key(header::CONTENT_TYPE) {
154 let result = <Self as axum::extract::FromRequest<S>>::from_request(req, state).await?;
156 return Ok(Some(result));
157 }
158
159 let bytes = <Bytes as axum::extract::FromRequest<S>>::from_request(req, state).await?;
161 if bytes.is_empty() {
162 return Ok(None);
163 }
164
165 let value: T = serde_json::from_slice(&bytes)?;
166
167 Ok(Some(Self(value)))
168 }
169}