4
4
// SPDX-License-Identifier: AGPL-3.0-only
5
5
// Please see LICENSE in the repository root for full details.
6
6
7
- use axum:: { extract:: State , response:: IntoResponse , Json } ;
7
+ use axum:: {
8
+ extract:: { rejection:: JsonRejection , State } ,
9
+ response:: IntoResponse ,
10
+ Json ,
11
+ } ;
8
12
use axum_extra:: typed_header:: TypedHeader ;
9
13
use chrono:: Duration ;
10
14
use hyper:: StatusCode ;
@@ -104,7 +108,9 @@ pub struct RequestBody {
104
108
pub enum Credentials {
105
109
#[ serde( rename = "m.login.password" ) ]
106
110
Password {
107
- identifier : Identifier ,
111
+ identifier : Option < Identifier > ,
112
+ // This property has been deprecated for a while, but some tools still use it.
113
+ user : Option < String > ,
108
114
password : String ,
109
115
} ,
110
116
@@ -145,6 +151,12 @@ pub enum RouteError {
145
151
#[ error( "unsupported login method" ) ]
146
152
Unsupported ,
147
153
154
+ #[ error( "unsupported identifier type" ) ]
155
+ UnsupportedIdentifier ,
156
+
157
+ #[ error( "missing property 'identifier'" ) ]
158
+ MissingIdentifier ,
159
+
148
160
#[ error( "user not found" ) ]
149
161
UserNotFound ,
150
162
@@ -166,6 +178,9 @@ pub enum RouteError {
166
178
#[ error( "invalid login token" ) ]
167
179
InvalidLoginToken ,
168
180
181
+ #[ error( transparent) ]
182
+ InvalidJsonBody ( #[ from] JsonRejection ) ,
183
+
169
184
#[ error( "failed to provision device" ) ]
170
185
ProvisionDeviceFailed ( #[ source] anyhow:: Error ) ,
171
186
}
@@ -188,11 +203,41 @@ impl IntoResponse for RouteError {
188
203
error : "Too many login attempts" ,
189
204
status : StatusCode :: TOO_MANY_REQUESTS ,
190
205
} ,
206
+ Self :: InvalidJsonBody ( JsonRejection :: MissingJsonContentType ( _) ) => MatrixError {
207
+ errcode : "M_NOT_JSON" ,
208
+ error : "Invalid Content-Type header: expected application/json" ,
209
+ status : StatusCode :: BAD_REQUEST ,
210
+ } ,
211
+ Self :: InvalidJsonBody ( JsonRejection :: JsonSyntaxError ( _) ) => MatrixError {
212
+ errcode : "M_NOT_JSON" ,
213
+ error : "Body is not a valid JSON document" ,
214
+ status : StatusCode :: BAD_REQUEST ,
215
+ } ,
216
+ Self :: InvalidJsonBody ( JsonRejection :: JsonDataError ( _) ) => MatrixError {
217
+ errcode : "M_BAD_JSON" ,
218
+ error : "JSON fields are not valid" ,
219
+ status : StatusCode :: BAD_REQUEST ,
220
+ } ,
221
+ Self :: InvalidJsonBody ( _) => MatrixError {
222
+ errcode : "M_UNKNOWN" ,
223
+ error : "Unknown error while parsing JSON body" ,
224
+ status : StatusCode :: BAD_REQUEST ,
225
+ } ,
191
226
Self :: Unsupported => MatrixError {
192
- errcode : "M_UNRECOGNIZED " ,
227
+ errcode : "M_UNKNOWN " ,
193
228
error : "Invalid login type" ,
194
229
status : StatusCode :: BAD_REQUEST ,
195
230
} ,
231
+ Self :: UnsupportedIdentifier => MatrixError {
232
+ errcode : "M_UNKNOWN" ,
233
+ error : "Unsupported login identifier" ,
234
+ status : StatusCode :: BAD_REQUEST ,
235
+ } ,
236
+ Self :: MissingIdentifier => MatrixError {
237
+ errcode : "M_BAD_JSON" ,
238
+ error : "Missing property 'identifier" ,
239
+ status : StatusCode :: BAD_REQUEST ,
240
+ } ,
196
241
Self :: UserNotFound | Self :: NoPassword | Self :: PasswordVerificationFailed ( _) => {
197
242
MatrixError {
198
243
errcode : "M_FORBIDDEN" ,
@@ -228,17 +273,32 @@ pub(crate) async fn post(
228
273
State ( limiter) : State < Limiter > ,
229
274
requester : RequesterFingerprint ,
230
275
user_agent : Option < TypedHeader < headers:: UserAgent > > ,
231
- Json ( input) : Json < RequestBody > ,
276
+ input : Result < Json < RequestBody > , JsonRejection > ,
232
277
) -> Result < impl IntoResponse , RouteError > {
278
+ let Json ( input) = input?;
233
279
let user_agent = user_agent. map ( |ua| UserAgent :: parse ( ua. as_str ( ) . to_owned ( ) ) ) ;
234
280
let ( mut session, user) = match ( password_manager. is_enabled ( ) , input. credentials ) {
235
281
(
236
282
true ,
237
283
Credentials :: Password {
238
- identifier : Identifier :: User { user } ,
284
+ identifier,
285
+ user,
239
286
password,
240
287
} ,
241
288
) => {
289
+ // This is to support both the (very) old and deprecated 'user' property, with
290
+ // the same behavior as Synapse: it takes precendence over the 'identifier' if
291
+ // provided
292
+ let user = match ( identifier, user) {
293
+ ( Some ( Identifier :: User { user } ) , None ) | ( _, Some ( user) ) => user,
294
+ ( Some ( Identifier :: Unsupported ) , None ) => {
295
+ return Err ( RouteError :: UnsupportedIdentifier ) ;
296
+ }
297
+ ( None , None ) => {
298
+ return Err ( RouteError :: MissingIdentifier ) ;
299
+ }
300
+ } ;
301
+
242
302
user_password_login (
243
303
& mut rng,
244
304
& clock,
0 commit comments