66
77use std:: sync:: { Arc , LazyLock } ;
88
9- use axum:: {
10- Json ,
11- extract:: { State , rejection:: JsonRejection } ,
12- response:: IntoResponse ,
13- } ;
14- use axum_extra:: { extract:: WithRejection , typed_header:: TypedHeader } ;
9+ use axum:: { Json , extract:: State , response:: IntoResponse } ;
10+ use axum_extra:: typed_header:: TypedHeader ;
1511use chrono:: Duration ;
1612use hyper:: StatusCode ;
1713use mas_axum_utils:: sentry:: SentryEventID ;
@@ -34,7 +30,7 @@ use serde_with::{DurationMilliSeconds, serde_as, skip_serializing_none};
3430use thiserror:: Error ;
3531use zeroize:: Zeroizing ;
3632
37- use super :: MatrixError ;
33+ use super :: { MatrixError , MatrixJsonBody } ;
3834use crate :: {
3935 BoundActivityTracker , Limiter , METER , RequesterFingerprint , impl_from_error_for_route,
4036 passwords:: PasswordManager , rate_limit:: PasswordCheckLimitedError ,
@@ -206,9 +202,6 @@ pub enum RouteError {
206202 #[ error( "invalid login token" ) ]
207203 InvalidLoginToken ,
208204
209- #[ error( transparent) ]
210- InvalidJsonBody ( #[ from] JsonRejection ) ,
211-
212205 #[ error( "failed to provision device" ) ]
213206 ProvisionDeviceFailed ( #[ source] anyhow:: Error ) ,
214207}
@@ -230,26 +223,6 @@ impl IntoResponse for RouteError {
230223 error : "Too many login attempts" ,
231224 status : StatusCode :: TOO_MANY_REQUESTS ,
232225 } ,
233- Self :: InvalidJsonBody ( JsonRejection :: MissingJsonContentType ( _) ) => MatrixError {
234- errcode : "M_NOT_JSON" ,
235- error : "Invalid Content-Type header: expected application/json" ,
236- status : StatusCode :: BAD_REQUEST ,
237- } ,
238- Self :: InvalidJsonBody ( JsonRejection :: JsonSyntaxError ( _) ) => MatrixError {
239- errcode : "M_NOT_JSON" ,
240- error : "Body is not a valid JSON document" ,
241- status : StatusCode :: BAD_REQUEST ,
242- } ,
243- Self :: InvalidJsonBody ( JsonRejection :: JsonDataError ( _) ) => MatrixError {
244- errcode : "M_BAD_JSON" ,
245- error : "JSON fields are not valid" ,
246- status : StatusCode :: BAD_REQUEST ,
247- } ,
248- Self :: InvalidJsonBody ( _) => MatrixError {
249- errcode : "M_UNKNOWN" ,
250- error : "Unknown error while parsing JSON body" ,
251- status : StatusCode :: BAD_REQUEST ,
252- } ,
253226 Self :: Unsupported => MatrixError {
254227 errcode : "M_UNKNOWN" ,
255228 error : "Invalid login type" ,
@@ -300,7 +273,7 @@ pub(crate) async fn post(
300273 State ( limiter) : State < Limiter > ,
301274 requester : RequesterFingerprint ,
302275 user_agent : Option < TypedHeader < headers:: UserAgent > > ,
303- WithRejection ( Json ( input) , _ ) : WithRejection < Json < RequestBody > , RouteError > ,
276+ MatrixJsonBody ( input) : MatrixJsonBody < RequestBody > ,
304277) -> Result < impl IntoResponse , RouteError > {
305278 let user_agent = user_agent. map ( |ua| UserAgent :: parse ( ua. as_str ( ) . to_owned ( ) ) ) ;
306279 let login_type = input. credentials . login_type ( ) ;
@@ -662,12 +635,12 @@ mod tests {
662635 response. assert_status ( StatusCode :: BAD_REQUEST ) ;
663636 let body: serde_json:: Value = response. json ( ) ;
664637
665- insta:: assert_json_snapshot!( body, @r### "
638+ insta:: assert_json_snapshot!( body, @r#"
666639 {
667640 "errcode": "M_NOT_JSON",
668- "error": "Invalid Content-Type header: expected application/json "
641+ "error": "Body is not a valid JSON document "
669642 }
670- "### ) ;
643+ "# ) ;
671644
672645 // Missing keys in body
673646 let request = Request :: post ( "/_matrix/client/v3/login" ) . json ( serde_json:: json!( { } ) ) ;
@@ -902,6 +875,37 @@ mod tests {
902875 assert_eq ! ( body, old_body) ;
903876 }
904877
878+ /// Test that we can send a login request without a Content-Type header
879+ #[ sqlx:: test( migrator = "mas_storage_pg::MIGRATOR" ) ]
880+ async fn test_no_content_type ( pool : PgPool ) {
881+ setup ( ) ;
882+ let state = TestState :: from_pool ( pool) . await . unwrap ( ) ;
883+
884+ user_with_password ( & state, "alice" , "password" ) . await ;
885+ // Try without a Content-Type header
886+ let mut request = Request :: post ( "/_matrix/client/v3/login" ) . json ( serde_json:: json!( {
887+ "type" : "m.login.password" ,
888+ "identifier" : {
889+ "type" : "m.id.user" ,
890+ "user" : "alice" ,
891+ } ,
892+ "password" : "password" ,
893+ } ) ) ;
894+ request. headers_mut ( ) . remove ( hyper:: header:: CONTENT_TYPE ) ;
895+
896+ let response = state. request ( request) . await ;
897+ response. assert_status ( StatusCode :: OK ) ;
898+
899+ let body: serde_json:: Value = response. json ( ) ;
900+ insta:: assert_json_snapshot!( body, @r###"
901+ {
902+ "access_token": "mct_16tugBE5Ta9LIWoSJaAEHHq2g3fx8S_alcBB4",
903+ "device_id": "ZGpSvYQqlq",
904+ "user_id": "@alice:example.com"
905+ }
906+ "### ) ;
907+ }
908+
905909 /// Test that a user can login with a password using the Matrix
906910 /// compatibility API, using a MXID as identifier
907911 #[ sqlx:: test( migrator = "mas_storage_pg::MIGRATOR" ) ]
0 commit comments