Skip to content
This repository was archived by the owner on Sep 10, 2024. It is now read-only.

Commit 3f30498

Browse files
committed
Merge remote-tracking branch 'origin/main' into quenting/user-reactivation
2 parents a319976 + 3eab106 commit 3f30498

File tree

19 files changed

+465
-121
lines changed

19 files changed

+465
-121
lines changed

crates/cli/src/commands/manage.rs

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ use mas_matrix_synapse::SynapseConnection;
2828
use mas_storage::{
2929
compat::{CompatAccessTokenRepository, CompatSessionRepository},
3030
job::{
31-
DeactivateUserJob, DeleteDeviceJob, JobRepositoryExt, ProvisionUserJob, ReactivateUserJob,
31+
DeactivateUserJob, JobRepositoryExt, ProvisionUserJob, ReactivateUserJob, SyncDevicesJob,
3232
},
3333
user::{UserEmailRepository, UserPasswordRepository, UserRepository},
3434
Clock, RepositoryAccess, SystemClock,
@@ -370,10 +370,6 @@ impl Options {
370370
if dry_run {
371371
continue;
372372
}
373-
374-
let job = DeleteDeviceJob::new(&user, &compat_session.device);
375-
repo.job().schedule_job(job).await?;
376-
repo.compat_session().finish(&clock, compat_session).await?;
377373
}
378374

379375
let oauth2_sessions_ids: Vec<Uuid> = sqlx::query_scalar(
@@ -400,16 +396,6 @@ impl Options {
400396
if dry_run {
401397
continue;
402398
}
403-
404-
for scope in &*oauth2_session.scope {
405-
if let Some(device) = Device::from_scope_token(scope) {
406-
// Schedule a job to delete the device.
407-
repo.job()
408-
.schedule_job(DeleteDeviceJob::new(&user, &device))
409-
.await?;
410-
}
411-
}
412-
413399
repo.oauth2_session().finish(&clock, oauth2_session).await?;
414400
}
415401

@@ -441,6 +427,10 @@ impl Options {
441427
.await?;
442428
}
443429

430+
// Schedule a job to sync the devices of the user with the homeserver
431+
warn!("Scheduling job to sync devices for the user");
432+
repo.job().schedule_job(SyncDevicesJob::new(&user)).await?;
433+
444434
let txn = repo.into_inner();
445435
if dry_run {
446436
info!("Dry run, not saving");

crates/handlers/src/compat/login.rs

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ use mas_storage::{
2626
CompatAccessTokenRepository, CompatRefreshTokenRepository, CompatSessionRepository,
2727
CompatSsoLoginRepository,
2828
},
29-
job::{JobRepositoryExt, ProvisionDeviceJob},
3029
user::{UserPasswordRepository, UserRepository},
3130
BoxClock, BoxRepository, BoxRng, Clock, RepositoryAccess,
3231
};
@@ -168,6 +167,9 @@ pub enum RouteError {
168167

169168
#[error("invalid login token")]
170169
InvalidLoginToken,
170+
171+
#[error("failed to provision device")]
172+
ProvisionDeviceFailed(#[source] anyhow::Error),
171173
}
172174

173175
impl_from_error_for_route!(mas_storage::RepositoryError);
@@ -176,11 +178,13 @@ impl IntoResponse for RouteError {
176178
fn into_response(self) -> axum::response::Response {
177179
let event_id = sentry::capture_error(&self);
178180
let response = match self {
179-
Self::Internal(_) | Self::SessionNotFound => MatrixError {
180-
errcode: "M_UNKNOWN",
181-
error: "Internal server error",
182-
status: StatusCode::INTERNAL_SERVER_ERROR,
183-
},
181+
Self::Internal(_) | Self::SessionNotFound | Self::ProvisionDeviceFailed(_) => {
182+
MatrixError {
183+
errcode: "M_UNKNOWN",
184+
error: "Internal server error",
185+
status: StatusCode::INTERNAL_SERVER_ERROR,
186+
}
187+
}
184188
Self::Unsupported => MatrixError {
185189
errcode: "M_UNRECOGNIZED",
186190
error: "Invalid login type",
@@ -235,6 +239,7 @@ pub(crate) async fn post(
235239
&clock,
236240
&password_manager,
237241
&mut repo,
242+
&homeserver,
238243
user,
239244
password,
240245
)
@@ -368,6 +373,7 @@ async fn user_password_login(
368373
clock: &impl Clock,
369374
password_manager: &PasswordManager,
370375
repo: &mut BoxRepository,
376+
homeserver: &BoxHomeserverConnection,
371377
username: String,
372378
password: String,
373379
) -> Result<(CompatSession, User), RouteError> {
@@ -413,11 +419,16 @@ async fn user_password_login(
413419
.await?;
414420
}
415421

422+
// Lock the user sync to make sure we don't get into a race condition
423+
repo.user().acquire_lock_for_sync(&user).await?;
424+
416425
// Now that the user credentials have been verified, start a new compat session
417426
let device = Device::generate(&mut rng);
418-
repo.job()
419-
.schedule_job(ProvisionDeviceJob::new(&user, &device))
420-
.await?;
427+
let mxid = homeserver.mxid(&user.username);
428+
homeserver
429+
.create_device(&mxid, device.as_str())
430+
.await
431+
.map_err(RouteError::ProvisionDeviceFailed)?;
421432

422433
let session = repo
423434
.compat_session()
@@ -430,6 +441,7 @@ async fn user_password_login(
430441
#[cfg(test)]
431442
mod tests {
432443
use hyper::Request;
444+
use mas_matrix::{HomeserverConnection, ProvisionRequest};
433445
use rand::distributions::{Alphanumeric, DistString};
434446
use sqlx::PgPool;
435447

@@ -532,6 +544,13 @@ mod tests {
532544
.await
533545
.unwrap();
534546

547+
let mxid = state.homeserver_connection.mxid(&user.username);
548+
state
549+
.homeserver_connection
550+
.provision_user(&ProvisionRequest::new(mxid, &user.sub))
551+
.await
552+
.unwrap();
553+
535554
let (version, hashed_password) = state
536555
.password_manager
537556
.hash(
@@ -664,6 +683,13 @@ mod tests {
664683
.unwrap();
665684
repo.save().await.unwrap();
666685

686+
let mxid = state.homeserver_connection.mxid(&user.username);
687+
state
688+
.homeserver_connection
689+
.provision_user(&ProvisionRequest::new(mxid, &user.sub))
690+
.await
691+
.unwrap();
692+
667693
// First try with an invalid token
668694
let request = Request::post("/_matrix/client/v3/login").json(serde_json::json!({
669695
"type": "m.login.token",

crates/handlers/src/compat/login_sso_complete.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@ use mas_axum_utils::{
2727
FancyError, SessionInfoExt,
2828
};
2929
use mas_data_model::Device;
30+
use mas_matrix::BoxHomeserverConnection;
3031
use mas_router::{CompatLoginSsoAction, PostAuthAction, UrlBuilder};
3132
use mas_storage::{
3233
compat::{CompatSessionRepository, CompatSsoLoginRepository},
33-
job::{JobRepositoryExt, ProvisionDeviceJob},
3434
BoxClock, BoxRepository, BoxRng, Clock, RepositoryAccess,
3535
};
3636
use mas_templates::{CompatSsoContext, ErrorContext, TemplateContext, Templates};
@@ -136,6 +136,7 @@ pub async fn post(
136136
PreferredLanguage(locale): PreferredLanguage,
137137
State(templates): State<Templates>,
138138
State(url_builder): State<UrlBuilder>,
139+
State(homeserver): State<BoxHomeserverConnection>,
139140
cookie_jar: CookieJar,
140141
Path(id): Path<Ulid>,
141142
Query(params): Query<Params>,
@@ -201,10 +202,15 @@ pub async fn post(
201202
redirect_uri
202203
};
203204

205+
// Lock the user sync to make sure we don't get into a race condition
206+
repo.user().acquire_lock_for_sync(&session.user).await?;
207+
204208
let device = Device::generate(&mut rng);
205-
repo.job()
206-
.schedule_job(ProvisionDeviceJob::new(&session.user, &device))
207-
.await?;
209+
let mxid = homeserver.mxid(&session.user.username);
210+
homeserver
211+
.create_device(&mxid, device.as_str())
212+
.await
213+
.context("Failed to provision device")?;
208214

209215
let compat_session = repo
210216
.compat_session()

crates/handlers/src/compat/logout.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use mas_axum_utils::sentry::SentryEventID;
2020
use mas_data_model::TokenType;
2121
use mas_storage::{
2222
compat::{CompatAccessTokenRepository, CompatSessionRepository},
23-
job::{DeleteDeviceJob, JobRepositoryExt},
23+
job::{JobRepositoryExt, SyncDevicesJob},
2424
BoxClock, BoxRepository, Clock, RepositoryAccess,
2525
};
2626
use thiserror::Error;
@@ -111,9 +111,8 @@ pub(crate) async fn post(
111111
// XXX: this is probably not the right error
112112
.ok_or(RouteError::InvalidAuthorization)?;
113113

114-
repo.job()
115-
.schedule_job(DeleteDeviceJob::new(&user, &session.device))
116-
.await?;
114+
// Schedule a job to sync the devices of the user with the homeserver
115+
repo.job().schedule_job(SyncDevicesJob::new(&user)).await?;
117116

118117
repo.compat_session().finish(&clock, session).await?;
119118

crates/handlers/src/graphql/mutations/compat_session.rs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use anyhow::Context as _;
1616
use async_graphql::{Context, Enum, InputObject, Object, ID};
1717
use mas_storage::{
1818
compat::CompatSessionRepository,
19-
job::{DeleteDeviceJob, JobRepositoryExt},
19+
job::{JobRepositoryExt, SyncDevicesJob},
2020
RepositoryAccess,
2121
};
2222

@@ -101,10 +101,8 @@ impl CompatSessionMutations {
101101
.await?
102102
.context("Could not load user")?;
103103

104-
// Schedule a job to delete the device.
105-
repo.job()
106-
.schedule_job(DeleteDeviceJob::new(&user, &session.device))
107-
.await?;
104+
// Schedule a job to sync the devices of the user with the homeserver
105+
repo.job().schedule_job(SyncDevicesJob::new(&user)).await?;
108106

109107
let session = repo.compat_session().finish(&clock, session).await?;
110108

crates/handlers/src/graphql/mutations/oauth2_session.rs

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use async_graphql::{Context, Description, Enum, InputObject, Object, ID};
1717
use chrono::Duration;
1818
use mas_data_model::{Device, TokenType};
1919
use mas_storage::{
20-
job::{DeleteDeviceJob, JobRepositoryExt, ProvisionDeviceJob},
20+
job::{JobRepositoryExt, SyncDevicesJob},
2121
oauth2::{
2222
OAuth2AccessTokenRepository, OAuth2ClientRepository, OAuth2RefreshTokenRepository,
2323
OAuth2SessionRepository,
@@ -129,6 +129,7 @@ impl OAuth2SessionMutations {
129129
input: CreateOAuth2SessionInput,
130130
) -> Result<CreateOAuth2SessionPayload, async_graphql::Error> {
131131
let state = ctx.state();
132+
let homeserver = state.homeserver_connection();
132133
let user_id = NodeType::User.extract_ulid(&input.user_id)?;
133134
let scope: Scope = input.scope.parse().context("Invalid scope")?;
134135
let permanent = input.permanent.unwrap_or(false);
@@ -167,12 +168,17 @@ impl OAuth2SessionMutations {
167168
.add(&mut rng, &clock, &client, Some(&user), None, scope)
168169
.await?;
169170

171+
// Lock the user sync to make sure we don't get into a race condition
172+
repo.user().acquire_lock_for_sync(&user).await?;
173+
170174
// Look for devices to provision
175+
let mxid = homeserver.mxid(&user.username);
171176
for scope in &*session.scope {
172177
if let Some(device) = Device::from_scope_token(scope) {
173-
repo.job()
174-
.schedule_job(ProvisionDeviceJob::new(&user, &device))
175-
.await?;
178+
homeserver
179+
.create_device(&mxid, device.as_str())
180+
.await
181+
.context("Failed to provision device")?;
176182
}
177183
}
178184

@@ -236,20 +242,8 @@ impl OAuth2SessionMutations {
236242
.await?
237243
.context("Could not load user")?;
238244

239-
// Scan the scopes of the session to find if there is any device that should be
240-
// deleted from the Matrix server.
241-
// TODO: this should be moved in a higher level "end oauth session" method.
242-
// XXX: this might not be the right semantic, but it's the best we
243-
// can do for now, since we're not explicitly storing devices for OAuth2
244-
// sessions.
245-
for scope in &*session.scope {
246-
if let Some(device) = Device::from_scope_token(scope) {
247-
// Schedule a job to delete the device.
248-
repo.job()
249-
.schedule_job(DeleteDeviceJob::new(&user, &device))
250-
.await?;
251-
}
252-
}
245+
// Schedule a job to sync the devices of the user with the homeserver
246+
repo.job().schedule_job(SyncDevicesJob::new(&user)).await?;
253247
}
254248

255249
let session = repo.oauth2_session().finish(&clock, session).await?;

crates/handlers/src/graphql/tests.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use axum::http::Request;
1616
use hyper::StatusCode;
1717
use mas_data_model::{AccessToken, Client, TokenType, User};
18+
use mas_matrix::{HomeserverConnection, ProvisionRequest};
1819
use mas_router::SimpleRoute;
1920
use mas_storage::{
2021
oauth2::{OAuth2AccessTokenRepository, OAuth2ClientRepository},
@@ -517,7 +518,7 @@ async fn test_oauth2_client_credentials(pool: PgPool) {
517518
response.assert_status(StatusCode::OK);
518519
let response: GraphQLResponse = response.json();
519520
assert!(response.errors.is_empty(), "{:?}", response.errors);
520-
let user_id = &response.data["addUser"]["user"]["id"];
521+
let user_id = response.data["addUser"]["user"]["id"].as_str().unwrap();
521522

522523
assert_eq!(
523524
response.data,
@@ -531,6 +532,16 @@ async fn test_oauth2_client_credentials(pool: PgPool) {
531532
})
532533
);
533534

535+
// XXX: we don't run the task worker here, so even though the addUser mutation
536+
// should have scheduled a job to provision the user, it won't run in the test,
537+
// so we need to do it manually
538+
let mxid = state.homeserver_connection.mxid("alice");
539+
state
540+
.homeserver_connection
541+
.provision_user(&ProvisionRequest::new(mxid, user_id))
542+
.await
543+
.unwrap();
544+
534545
// We should now be able to create an arbitrary access token for the user
535546
let request = Request::post("/graphql")
536547
.bearer(&access_token)

crates/handlers/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ where
186186
Encrypter: FromRef<S>,
187187
HttpClientFactory: FromRef<S>,
188188
SiteConfig: FromRef<S>,
189+
BoxHomeserverConnection: FromRef<S>,
189190
BoxClock: FromRequestParts<S>,
190191
BoxRng: FromRequestParts<S>,
191192
Policy: FromRequestParts<S>,

crates/handlers/src/oauth2/introspection.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,7 @@ mod tests {
461461
use hyper::{Request, StatusCode};
462462
use mas_data_model::{AccessToken, RefreshToken};
463463
use mas_iana::oauth::OAuthTokenTypeHint;
464+
use mas_matrix::{HomeserverConnection, ProvisionRequest};
464465
use mas_router::{OAuth2Introspection, OAuth2RegistrationEndpoint, SimpleRoute};
465466
use mas_storage::Clock;
466467
use oauth2_types::{
@@ -518,6 +519,13 @@ mod tests {
518519
.await
519520
.unwrap();
520521

522+
let mxid = state.homeserver_connection.mxid(&user.username);
523+
state
524+
.homeserver_connection
525+
.provision_user(&ProvisionRequest::new(mxid, &user.sub))
526+
.await
527+
.unwrap();
528+
521529
let client = repo
522530
.oauth2_client()
523531
.find_by_client_id(&client_id)
@@ -703,6 +711,13 @@ mod tests {
703711
.await
704712
.unwrap();
705713

714+
let mxid = state.homeserver_connection.mxid(&user.username);
715+
state
716+
.homeserver_connection
717+
.provision_user(&ProvisionRequest::new(mxid, &user.sub))
718+
.await
719+
.unwrap();
720+
706721
let (version, hashed_password) = state
707722
.password_manager
708723
.hash(&mut state.rng(), Zeroizing::new(b"password".to_vec()))

0 commit comments

Comments
 (0)