@@ -205,6 +205,9 @@ pub enum RouteError {
205
205
#[ error( "invalid login token" ) ]
206
206
InvalidLoginToken ,
207
207
208
+ #[ error( "user is locked" ) ]
209
+ UserLocked ,
210
+
208
211
#[ error( "failed to provision device" ) ]
209
212
ProvisionDeviceFailed ( #[ source] anyhow:: Error ) ,
210
213
}
@@ -263,6 +266,11 @@ impl IntoResponse for RouteError {
263
266
error : "Invalid login token" ,
264
267
status : StatusCode :: FORBIDDEN ,
265
268
} ,
269
+ Self :: UserLocked => MatrixError {
270
+ errcode : "M_USER_LOCKED" ,
271
+ error : "User account has been locked" ,
272
+ status : StatusCode :: UNAUTHORIZED ,
273
+ } ,
266
274
} ;
267
275
268
276
( sentry_event_id, response) . into_response ( )
@@ -506,7 +514,15 @@ async fn token_login(
506
514
browser_session. id = %browser_session_id,
507
515
"Attempt to exchange login token but browser session is not active"
508
516
) ;
509
- return Err ( RouteError :: InvalidLoginToken ) ;
517
+ return Err (
518
+ if browser_session. finished_at . is_some ( )
519
+ || browser_session. user . deactivated_at . is_some ( )
520
+ {
521
+ RouteError :: InvalidLoginToken
522
+ } else {
523
+ RouteError :: UserLocked
524
+ } ,
525
+ ) ;
510
526
}
511
527
512
528
// We're about to create a device, let's explicitly acquire a lock, so that
@@ -565,9 +581,13 @@ async fn user_password_login(
565
581
. user ( )
566
582
. find_by_username ( username)
567
583
. await ?
568
- . filter ( mas_data_model :: User :: is_valid )
584
+ . filter ( |user| user . deactivated_at . is_none ( ) )
569
585
. ok_or ( RouteError :: UserNotFound ) ?;
570
586
587
+ if user. locked_at . is_some ( ) {
588
+ return Err ( RouteError :: UserLocked ) ;
589
+ }
590
+
571
591
// Check the rate limit
572
592
limiter. check_password ( requester, & user) ?;
573
593
@@ -785,7 +805,12 @@ mod tests {
785
805
"### ) ;
786
806
}
787
807
788
- async fn user_with_password ( state : & TestState , username : & str , password : & str ) {
808
+ async fn user_with_password (
809
+ state : & TestState ,
810
+ username : & str ,
811
+ password : & str ,
812
+ locked : bool ,
813
+ ) -> User {
789
814
let mut rng = state. rng ( ) ;
790
815
let mut repo = state. repository ( ) . await . unwrap ( ) ;
791
816
@@ -811,7 +836,14 @@ mod tests {
811
836
. await
812
837
. unwrap ( ) ;
813
838
839
+ let user = if locked {
840
+ repo. user ( ) . lock ( & state. clock , user) . await . unwrap ( )
841
+ } else {
842
+ user
843
+ } ;
844
+
814
845
repo. save ( ) . await . unwrap ( ) ;
846
+ user
815
847
}
816
848
817
849
/// Test that a user can login with a password using the Matrix
@@ -821,7 +853,7 @@ mod tests {
821
853
setup ( ) ;
822
854
let state = TestState :: from_pool ( pool) . await . unwrap ( ) ;
823
855
824
- user_with_password ( & state, "alice" , "password" ) . await ;
856
+ let user = user_with_password ( & state, "alice" , "password" , true ) . await ;
825
857
826
858
// Now let's try to login with the password, without asking for a refresh token.
827
859
let request = Request :: post ( "/_matrix/client/v3/login" ) . json ( serde_json:: json!( {
@@ -833,14 +865,30 @@ mod tests {
833
865
"password" : "password" ,
834
866
} ) ) ;
835
867
868
+ // First try to login to a locked account
869
+ let response = state. request ( request. clone ( ) ) . await ;
870
+ response. assert_status ( StatusCode :: UNAUTHORIZED ) ;
871
+ let body: serde_json:: Value = response. json ( ) ;
872
+ insta:: assert_json_snapshot!( body, @r###"
873
+ {
874
+ "errcode": "M_USER_LOCKED",
875
+ "error": "User account has been locked"
876
+ }
877
+ "### ) ;
878
+
879
+ // Now try again after unlocking the account
880
+ let mut repo = state. repository ( ) . await . unwrap ( ) ;
881
+ let user = repo. user ( ) . unlock ( user) . await . unwrap ( ) ;
882
+ repo. save ( ) . await . unwrap ( ) ;
883
+
836
884
let response = state. request ( request) . await ;
837
885
response. assert_status ( StatusCode :: OK ) ;
838
886
839
887
let body: serde_json:: Value = response. json ( ) ;
840
888
insta:: assert_json_snapshot!( body, @r###"
841
889
{
842
- "access_token": "mct_16tugBE5Ta9LIWoSJaAEHHq2g3fx8S_alcBB4 ",
843
- "device_id": "ZGpSvYQqlq ",
890
+ "access_token": "mct_cxG6gZXyvelQWW9XqfNbm5KAQovodf_XvJz43 ",
891
+ "device_id": "42oTpLoieH ",
844
892
"user_id": "@alice:example.com"
845
893
}
846
894
"### ) ;
@@ -862,10 +910,10 @@ mod tests {
862
910
let body: serde_json:: Value = response. json ( ) ;
863
911
insta:: assert_json_snapshot!( body, @r###"
864
912
{
865
- "access_token": "mct_cxG6gZXyvelQWW9XqfNbm5KAQovodf_XvJz43 ",
866
- "device_id": "42oTpLoieH ",
913
+ "access_token": "mct_PGMLvvMXC4Ds1A3lCWc6Hx4l9DGzqG_lVEIV2 ",
914
+ "device_id": "Yp7FM44zJN ",
867
915
"user_id": "@alice:example.com",
868
- "refresh_token": "mcr_7IvDc44woP66fRQoS9MVcHXO9OeBmR_0jDGr1 ",
916
+ "refresh_token": "mcr_LoYqtrtBUBcWlE4RX6o47chBCGkadB_9gzpc1 ",
869
917
"expires_in_ms": 300000
870
918
}
871
919
"### ) ;
@@ -883,8 +931,8 @@ mod tests {
883
931
let body: serde_json:: Value = response. json ( ) ;
884
932
insta:: assert_json_snapshot!( body, @r###"
885
933
{
886
- "access_token": "mct_PGMLvvMXC4Ds1A3lCWc6Hx4l9DGzqG_lVEIV2 ",
887
- "device_id": "Yp7FM44zJN ",
934
+ "access_token": "mct_Xl3bbpfh9yNy9NzuRxyR3b3PLW0rqd_DiXAH2 ",
935
+ "device_id": "6cq7FqNSYo ",
888
936
"user_id": "@alice:example.com"
889
937
}
890
938
"### ) ;
@@ -930,6 +978,45 @@ mod tests {
930
978
// The response should be the same as the previous one, so that we don't leak if
931
979
// it's the user that is invalid or the password.
932
980
assert_eq ! ( body, old_body) ;
981
+
982
+ // Try to login to a deactivated account
983
+ let mut repo = state. repository ( ) . await . unwrap ( ) ;
984
+ let user = repo. user ( ) . deactivate ( & state. clock , user) . await . unwrap ( ) ;
985
+ repo. save ( ) . await . unwrap ( ) ;
986
+
987
+ let request = Request :: post ( "/_matrix/client/v3/login" ) . json ( serde_json:: json!( {
988
+ "type" : "m.login.password" ,
989
+ "identifier" : {
990
+ "type" : "m.id.user" ,
991
+ "user" : "alice" ,
992
+ } ,
993
+ "password" : "password" ,
994
+ } ) ) ;
995
+
996
+ let response = state. request ( request. clone ( ) ) . await ;
997
+ response. assert_status ( StatusCode :: FORBIDDEN ) ;
998
+ let body: serde_json:: Value = response. json ( ) ;
999
+ insta:: assert_json_snapshot!( body, @r###"
1000
+ {
1001
+ "errcode": "M_FORBIDDEN",
1002
+ "error": "Invalid username/password"
1003
+ }
1004
+ "### ) ;
1005
+
1006
+ // Should get the same error if the deactivated user is also locked
1007
+ let mut repo = state. repository ( ) . await . unwrap ( ) ;
1008
+ let _user = repo. user ( ) . lock ( & state. clock , user) . await . unwrap ( ) ;
1009
+ repo. save ( ) . await . unwrap ( ) ;
1010
+
1011
+ let response = state. request ( request) . await ;
1012
+ response. assert_status ( StatusCode :: FORBIDDEN ) ;
1013
+ let body: serde_json:: Value = response. json ( ) ;
1014
+ insta:: assert_json_snapshot!( body, @r###"
1015
+ {
1016
+ "errcode": "M_FORBIDDEN",
1017
+ "error": "Invalid username/password"
1018
+ }
1019
+ "### ) ;
933
1020
}
934
1021
935
1022
/// Test that we can send a login request without a Content-Type header
@@ -938,7 +1025,7 @@ mod tests {
938
1025
setup ( ) ;
939
1026
let state = TestState :: from_pool ( pool) . await . unwrap ( ) ;
940
1027
941
- user_with_password ( & state, "alice" , "password" ) . await ;
1028
+ user_with_password ( & state, "alice" , "password" , false ) . await ;
942
1029
// Try without a Content-Type header
943
1030
let mut request = Request :: post ( "/_matrix/client/v3/login" ) . json ( serde_json:: json!( {
944
1031
"type" : "m.login.password" ,
@@ -970,7 +1057,7 @@ mod tests {
970
1057
setup ( ) ;
971
1058
let state = TestState :: from_pool ( pool) . await . unwrap ( ) ;
972
1059
973
- user_with_password ( & state, "alice" , "password" ) . await ;
1060
+ let user = user_with_password ( & state, "alice" , "password" , true ) . await ;
974
1061
975
1062
// Login with a full MXID as identifier
976
1063
let request = Request :: post ( "/_matrix/client/v3/login" ) . json ( serde_json:: json!( {
@@ -982,13 +1069,29 @@ mod tests {
982
1069
"password" : "password" ,
983
1070
} ) ) ;
984
1071
1072
+ // First try to login to a locked account
1073
+ let response = state. request ( request. clone ( ) ) . await ;
1074
+ response. assert_status ( StatusCode :: UNAUTHORIZED ) ;
1075
+ let body: serde_json:: Value = response. json ( ) ;
1076
+ insta:: assert_json_snapshot!( body, @r###"
1077
+ {
1078
+ "errcode": "M_USER_LOCKED",
1079
+ "error": "User account has been locked"
1080
+ }
1081
+ "### ) ;
1082
+
1083
+ // Now try again after unlocking the account
1084
+ let mut repo = state. repository ( ) . await . unwrap ( ) ;
1085
+ let _ = repo. user ( ) . unlock ( user) . await . unwrap ( ) ;
1086
+ repo. save ( ) . await . unwrap ( ) ;
1087
+
985
1088
let response = state. request ( request) . await ;
986
1089
response. assert_status ( StatusCode :: OK ) ;
987
1090
let body: serde_json:: Value = response. json ( ) ;
988
1091
insta:: assert_json_snapshot!( body, @r###"
989
1092
{
990
- "access_token": "mct_16tugBE5Ta9LIWoSJaAEHHq2g3fx8S_alcBB4 ",
991
- "device_id": "ZGpSvYQqlq ",
1093
+ "access_token": "mct_cxG6gZXyvelQWW9XqfNbm5KAQovodf_XvJz43 ",
1094
+ "device_id": "42oTpLoieH ",
992
1095
"user_id": "@alice:example.com"
993
1096
}
994
1097
"### ) ;
@@ -1132,6 +1235,8 @@ mod tests {
1132
1235
. add ( & mut state. rng ( ) , & state. clock , "alice" . to_owned ( ) )
1133
1236
. await
1134
1237
. unwrap ( ) ;
1238
+ // Start with a locked account
1239
+ let user = repo. user ( ) . lock ( & state. clock , user) . await . unwrap ( ) ;
1135
1240
repo. save ( ) . await . unwrap ( ) ;
1136
1241
1137
1242
let mxid = state. homeserver_connection . mxid ( & user. username ) ;
@@ -1164,14 +1269,29 @@ mod tests {
1164
1269
"type" : "m.login.token" ,
1165
1270
"token" : token,
1166
1271
} ) ) ;
1272
+ let response = state. request ( request. clone ( ) ) . await ;
1273
+ response. assert_status ( StatusCode :: UNAUTHORIZED ) ;
1274
+ let body: serde_json:: Value = response. json ( ) ;
1275
+ insta:: assert_json_snapshot!( body, @r###"
1276
+ {
1277
+ "errcode": "M_USER_LOCKED",
1278
+ "error": "User account has been locked"
1279
+ }
1280
+ "### ) ;
1281
+
1282
+ // Now try again after unlocking the account
1283
+ let mut repo = state. repository ( ) . await . unwrap ( ) ;
1284
+ let user = repo. user ( ) . unlock ( user) . await . unwrap ( ) ;
1285
+ repo. save ( ) . await . unwrap ( ) ;
1286
+
1167
1287
let response = state. request ( request) . await ;
1168
1288
response. assert_status ( StatusCode :: OK ) ;
1169
1289
1170
1290
let body: serde_json:: Value = response. json ( ) ;
1171
1291
insta:: assert_json_snapshot!( body, @r#"
1172
1292
{
1173
- "access_token": "mct_bnkWh1tPmm1MZOpygPaXwygX8PfxEY_hE6do1 ",
1174
- "device_id": "O3Ju1MUh3Z ",
1293
+ "access_token": "mct_bUTa4XIh92RARTPTjqQrCZLAkq2ild_0VsYE6 ",
1294
+ "device_id": "uihy4bk51g ",
1175
1295
"user_id": "@alice:example.com"
1176
1296
}
1177
1297
"# ) ;
@@ -1212,6 +1332,41 @@ mod tests {
1212
1332
"error": "Login token expired"
1213
1333
}
1214
1334
"### ) ;
1335
+
1336
+ // Try to login to a deactivated account
1337
+ let token = get_login_token ( & state, & user) . await ;
1338
+
1339
+ let mut repo = state. repository ( ) . await . unwrap ( ) ;
1340
+ let user = repo. user ( ) . deactivate ( & state. clock , user) . await . unwrap ( ) ;
1341
+ repo. save ( ) . await . unwrap ( ) ;
1342
+ let request = Request :: post ( "/_matrix/client/v3/login" ) . json ( serde_json:: json!( {
1343
+ "type" : "m.login.token" ,
1344
+ "token" : token,
1345
+ } ) ) ;
1346
+ let response = state. request ( request. clone ( ) ) . await ;
1347
+ response. assert_status ( StatusCode :: FORBIDDEN ) ;
1348
+ let body: serde_json:: Value = response. json ( ) ;
1349
+ insta:: assert_json_snapshot!( body, @r###"
1350
+ {
1351
+ "errcode": "M_FORBIDDEN",
1352
+ "error": "Invalid login token"
1353
+ }
1354
+ "### ) ;
1355
+
1356
+ // Should get the same error if the deactivated user is also locked
1357
+ let mut repo = state. repository ( ) . await . unwrap ( ) ;
1358
+ let _user = repo. user ( ) . lock ( & state. clock , user) . await . unwrap ( ) ;
1359
+ repo. save ( ) . await . unwrap ( ) ;
1360
+
1361
+ let response = state. request ( request) . await ;
1362
+ response. assert_status ( StatusCode :: FORBIDDEN ) ;
1363
+ let body: serde_json:: Value = response. json ( ) ;
1364
+ insta:: assert_json_snapshot!( body, @r###"
1365
+ {
1366
+ "errcode": "M_FORBIDDEN",
1367
+ "error": "Invalid login token"
1368
+ }
1369
+ "### ) ;
1215
1370
}
1216
1371
1217
1372
/// Get a login token for a user.
0 commit comments