@@ -14,11 +14,12 @@ use mas_axum_utils::record_error;
1414use mas_data_model:: { CompatSession , CompatSsoLoginState , Device , SiteConfig , TokenType , User } ;
1515use mas_matrix:: HomeserverConnection ;
1616use mas_storage:: {
17- BoxClock , BoxRepository , BoxRng , Clock , RepositoryAccess ,
17+ BoxClock , BoxRepository , BoxRepositoryFactory , BoxRng , Clock , RepositoryAccess ,
1818 compat:: {
1919 CompatAccessTokenRepository , CompatRefreshTokenRepository , CompatSessionRepository ,
2020 CompatSsoLoginRepository ,
2121 } ,
22+ queue:: { QueueJobRepositoryExt as _, SyncDevicesJob } ,
2223 user:: { UserPasswordRepository , UserRepository } ,
2324} ;
2425use opentelemetry:: { Key , KeyValue , metrics:: Counter } ;
@@ -268,7 +269,7 @@ pub(crate) async fn post(
268269 mut rng : BoxRng ,
269270 clock : BoxClock ,
270271 State ( password_manager) : State < PasswordManager > ,
271- mut repo : BoxRepository ,
272+ State ( repository_factory ) : State < BoxRepositoryFactory > ,
272273 activity_tracker : BoundActivityTracker ,
273274 State ( homeserver) : State < Arc < dyn HomeserverConnection > > ,
274275 State ( site_config) : State < SiteConfig > ,
@@ -279,6 +280,7 @@ pub(crate) async fn post(
279280) -> Result < impl IntoResponse , RouteError > {
280281 let user_agent = user_agent. map ( |ua| ua. as_str ( ) . to_owned ( ) ) ;
281282 let login_type = input. credentials . login_type ( ) ;
283+ let mut repo = repository_factory. create ( ) . await ?;
282284 let ( mut session, user) = match ( password_manager. is_enabled ( ) , input. credentials ) {
283285 (
284286 true ,
@@ -301,15 +303,17 @@ pub(crate) async fn post(
301303 }
302304 } ;
303305
306+ // Try getting the localpart out of the MXID
307+ let username = homeserver. localpart ( & user) . unwrap_or ( & user) ;
308+
304309 user_password_login (
305310 & mut rng,
306311 & clock,
307312 & password_manager,
308313 & limiter,
309314 requester,
310315 & mut repo,
311- & homeserver,
312- user,
316+ username,
313317 password,
314318 input. device_id , // TODO check for validity
315319 input. initial_device_display_name ,
@@ -322,7 +326,6 @@ pub(crate) async fn post(
322326 & mut rng,
323327 & clock,
324328 & mut repo,
325- & homeserver,
326329 & token,
327330 input. device_id ,
328331 input. initial_device_display_name ,
@@ -374,6 +377,47 @@ pub(crate) async fn post(
374377 . record_compat_session ( & clock, & session)
375378 . await ;
376379
380+ // This session will have for sure the device on it, both methods create a
381+ // device
382+ let Some ( device) = & session. device else {
383+ unreachable ! ( )
384+ } ;
385+
386+ // Now we can create the device on the homeserver, without holding the
387+ // transaction
388+ if let Err ( err) = homeserver
389+ . create_device ( & user_id, device. as_str ( ) , session. human_name . as_deref ( ) )
390+ . await
391+ {
392+ // Uh-oh. Something went wrong, let's end this session and schedule a device
393+ // sync
394+ let mut repo = repository_factory. create ( ) . await ?;
395+ let session = repo. compat_session ( ) . finish ( & clock, session) . await ?;
396+
397+ repo. queue_job ( )
398+ . schedule_job (
399+ & mut rng,
400+ & clock,
401+ SyncDevicesJob :: new_for_id ( session. user_id ) ,
402+ )
403+ . await ?;
404+
405+ repo. save ( ) . await ?;
406+
407+ return Err ( RouteError :: ProvisionDeviceFailed ( err) ) ;
408+ }
409+
410+ // We always trigger a device sync job, to clean up any races we ended up with
411+ let mut repo = repository_factory. create ( ) . await ?;
412+ repo. queue_job ( )
413+ . schedule_job (
414+ & mut rng,
415+ & clock,
416+ SyncDevicesJob :: new_for_id ( session. user_id ) ,
417+ )
418+ . await ?;
419+ repo. save ( ) . await ?;
420+
377421 LOGIN_COUNTER . add (
378422 1 ,
379423 & [
@@ -395,7 +439,6 @@ async fn token_login(
395439 rng : & mut ( dyn RngCore + Send ) ,
396440 clock : & dyn Clock ,
397441 repo : & mut BoxRepository ,
398- homeserver : & dyn HomeserverConnection ,
399442 token : & str ,
400443 requested_device_id : Option < String > ,
401444 initial_device_display_name : Option < String > ,
@@ -461,30 +504,19 @@ async fn token_login(
461504 return Err ( RouteError :: InvalidLoginToken ) ;
462505 }
463506
464- // Lock the user sync to make sure we don't get into a race condition
465- repo. user ( )
466- . acquire_lock_for_sync ( & browser_session. user )
467- . await ?;
468-
469507 let device = if let Some ( requested_device_id) = requested_device_id {
470508 Device :: from ( requested_device_id)
471509 } else {
472510 Device :: generate ( rng)
473511 } ;
474- let mxid = homeserver. mxid ( & browser_session. user . username ) ;
475- homeserver
476- . create_device (
477- & mxid,
478- device. as_str ( ) ,
479- initial_device_display_name. as_deref ( ) ,
480- )
481- . await
482- . map_err ( RouteError :: ProvisionDeviceFailed ) ?;
483512
484513 repo. app_session ( )
485514 . finish_sessions_to_replace_device ( clock, & browser_session. user , & device)
486515 . await ?;
487516
517+ // We first create the session in the database, commit the transaction, then
518+ // create it on the homeserver, scheduling a device sync job afterwards to
519+ // make sure we don't end up in an inconsistent state.
488520 let compat_session = repo
489521 . compat_session ( )
490522 . add (
@@ -512,15 +544,11 @@ async fn user_password_login(
512544 limiter : & Limiter ,
513545 requester : RequesterFingerprint ,
514546 repo : & mut BoxRepository ,
515- homeserver : & dyn HomeserverConnection ,
516- username : String ,
547+ username : & str ,
517548 password : String ,
518549 requested_device_id : Option < String > ,
519550 initial_device_display_name : Option < String > ,
520551) -> Result < ( CompatSession , User ) , RouteError > {
521- // Try getting the localpart out of the MXID
522- let username = homeserver. localpart ( & username) . unwrap_or ( & username) ;
523-
524552 // Find the user
525553 let user = repo
526554 . user ( )
@@ -566,25 +594,12 @@ async fn user_password_login(
566594 . await ?;
567595 }
568596
569- // Lock the user sync to make sure we don't get into a race condition
570- repo. user ( ) . acquire_lock_for_sync ( & user) . await ?;
571-
572- let mxid = homeserver. mxid ( & user. username ) ;
573-
574597 // Now that the user credentials have been verified, start a new compat session
575598 let device = if let Some ( requested_device_id) = requested_device_id {
576599 Device :: from ( requested_device_id)
577600 } else {
578601 Device :: generate ( & mut rng)
579602 } ;
580- homeserver
581- . create_device (
582- & mxid,
583- device. as_str ( ) ,
584- initial_device_display_name. as_deref ( ) ,
585- )
586- . await
587- . map_err ( RouteError :: ProvisionDeviceFailed ) ?;
588603
589604 repo. app_session ( )
590605 . finish_sessions_to_replace_device ( clock, & user, & device)
0 commit comments