Skip to content

Commit 7823f21

Browse files
authored
Align user deactivation behaviour with Synapse (#4197)
2 parents 4f30463 + ea955b8 commit 7823f21

File tree

56 files changed

+854
-211
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+854
-211
lines changed

crates/axum-utils/src/session.rs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
// Please see LICENSE in the repository root for full details.
66

77
use mas_data_model::BrowserSession;
8-
use mas_storage::{RepositoryAccess, user::BrowserSessionRepository};
8+
use mas_storage::RepositoryAccess;
99
use serde::{Deserialize, Serialize};
1010
use ulid::Ulid;
1111

@@ -33,13 +33,12 @@ impl SessionInfo {
3333
self
3434
}
3535

36-
/// Load the [`BrowserSession`] from database
36+
/// Load the active [`BrowserSession`] from database
3737
///
3838
/// # Errors
3939
///
40-
/// Returns an error if the session is not found or if the session is not
41-
/// active anymore
42-
pub async fn load_session<E>(
40+
/// Returns an error if the underlying repository fails to load the session.
41+
pub async fn load_active_session<E>(
4342
&self,
4443
repo: &mut impl RepositoryAccess<Error = E>,
4544
) -> Result<Option<BrowserSession>, E> {
@@ -56,6 +55,12 @@ impl SessionInfo {
5655

5756
Ok(maybe_session)
5857
}
58+
59+
/// Get the current session ID, if any
60+
#[must_use]
61+
pub fn current_session_id(&self) -> Option<Ulid> {
62+
self.current
63+
}
5964
}
6065

6166
pub trait SessionInfoExt {

crates/data-model/src/users.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,15 @@ pub struct User {
2121
pub sub: String,
2222
pub created_at: DateTime<Utc>,
2323
pub locked_at: Option<DateTime<Utc>>,
24+
pub deactivated_at: Option<DateTime<Utc>>,
2425
pub can_request_admin: bool,
2526
}
2627

2728
impl User {
28-
/// Returns `true` unless the user is locked.
29+
/// Returns `true` unless the user is locked or deactivated.
2930
#[must_use]
3031
pub fn is_valid(&self) -> bool {
31-
self.locked_at.is_none()
32+
self.locked_at.is_none() && self.deactivated_at.is_none()
3233
}
3334
}
3435

@@ -42,6 +43,7 @@ impl User {
4243
sub: "123-456".to_owned(),
4344
created_at: now,
4445
locked_at: None,
46+
deactivated_at: None,
4547
can_request_admin: false,
4648
}]
4749
}

crates/handlers/src/compat/login_sso_complete.rs

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use axum::{
1313
};
1414
use chrono::Duration;
1515
use mas_axum_utils::{
16-
FancyError, SessionInfoExt,
16+
FancyError,
1717
cookies::CookieJar,
1818
csrf::{CsrfExt, ProtectedForm},
1919
};
@@ -28,7 +28,10 @@ use mas_templates::{CompatSsoContext, ErrorContext, TemplateContext, Templates};
2828
use serde::{Deserialize, Serialize};
2929
use ulid::Ulid;
3030

31-
use crate::PreferredLanguage;
31+
use crate::{
32+
PreferredLanguage,
33+
session::{SessionOrFallback, load_session_or_fallback},
34+
};
3235

3336
#[derive(Serialize)]
3437
struct AllParams<'s> {
@@ -61,10 +64,20 @@ pub async fn get(
6164
Path(id): Path<Ulid>,
6265
Query(params): Query<Params>,
6366
) -> Result<Response, FancyError> {
64-
let (session_info, cookie_jar) = cookie_jar.session_info();
65-
let (csrf_token, cookie_jar) = cookie_jar.csrf_token(&clock, &mut rng);
67+
let (cookie_jar, maybe_session) = match load_session_or_fallback(
68+
cookie_jar, &clock, &mut rng, &templates, &locale, &mut repo,
69+
)
70+
.await?
71+
{
72+
SessionOrFallback::MaybeSession {
73+
cookie_jar,
74+
maybe_session,
75+
..
76+
} => (cookie_jar, maybe_session),
77+
SessionOrFallback::Fallback { response } => return Ok(response),
78+
};
6679

67-
let maybe_session = session_info.load_session(&mut repo).await?;
80+
let (csrf_token, cookie_jar) = cookie_jar.csrf_token(&clock, &mut rng);
6881

6982
let Some(session) = maybe_session else {
7083
// If there is no session, redirect to the login or register screen
@@ -126,10 +139,20 @@ pub async fn post(
126139
Query(params): Query<Params>,
127140
Form(form): Form<ProtectedForm<()>>,
128141
) -> Result<Response, FancyError> {
129-
let (session_info, cookie_jar) = cookie_jar.session_info();
130-
cookie_jar.verify_form(&clock, form)?;
142+
let (cookie_jar, maybe_session) = match load_session_or_fallback(
143+
cookie_jar, &clock, &mut rng, &templates, &locale, &mut repo,
144+
)
145+
.await?
146+
{
147+
SessionOrFallback::MaybeSession {
148+
cookie_jar,
149+
maybe_session,
150+
..
151+
} => (cookie_jar, maybe_session),
152+
SessionOrFallback::Fallback { response } => return Ok(response),
153+
};
131154

132-
let maybe_session = session_info.load_session(&mut repo).await?;
155+
cookie_jar.verify_form(&clock, form)?;
133156

134157
let Some(session) = maybe_session else {
135158
// If there is no session, redirect to the login or register screen

crates/handlers/src/graphql/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@ async fn get_requester(
288288

289289
RequestingEntity::OAuth2Session(Box::new((session, user)))
290290
} else {
291-
let maybe_session = session_info.load_session(&mut repo).await?;
291+
let maybe_session = session_info.load_active_session(&mut repo).await?;
292292

293293
if let Some(session) = maybe_session.as_ref() {
294294
activity_tracker

crates/handlers/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ mod activity_tracker;
6464
mod captcha;
6565
mod preferred_language;
6666
mod rate_limit;
67+
mod session;
6768
#[cfg(test)]
6869
mod test_utils;
6970

crates/handlers/src/oauth2/authorization/complete.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ pub(crate) async fn get(
9797
) -> Result<Response, RouteError> {
9898
let (session_info, cookie_jar) = cookie_jar.session_info();
9999

100-
let maybe_session = session_info.load_session(&mut repo).await?;
100+
let maybe_session = session_info.load_active_session(&mut repo).await?;
101101

102102
let user_agent = user_agent.map(|TypedHeader(ua)| ua.to_string());
103103

crates/handlers/src/oauth2/authorization/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ pub(crate) async fn get(
176176
let callback_destination = callback_destination.clone();
177177
let locale = locale.clone();
178178
async move {
179-
let maybe_session = session_info.load_session(&mut repo).await?;
179+
let maybe_session = session_info.load_active_session(&mut repo).await?;
180180
let prompt = params.auth.prompt.as_deref().unwrap_or_default();
181181

182182
// Check if the request/request_uri/registration params are used. If so, reply

crates/handlers/src/oauth2/consent.rs

Lines changed: 73 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2024 New Vector Ltd.
1+
// Copyright 2024, 2025 New Vector Ltd.
22
// Copyright 2022-2024 The Matrix.org Foundation C.I.C.
33
//
44
// SPDX-License-Identifier: AGPL-3.0-only
@@ -11,7 +11,6 @@ use axum::{
1111
use axum_extra::TypedHeader;
1212
use hyper::StatusCode;
1313
use mas_axum_utils::{
14-
SessionInfoExt,
1514
cookies::CookieJar,
1615
csrf::{CsrfExt, ProtectedForm},
1716
sentry::SentryEventID,
@@ -27,7 +26,10 @@ use mas_templates::{ConsentContext, PolicyViolationContext, TemplateContext, Tem
2726
use thiserror::Error;
2827
use ulid::Ulid;
2928

30-
use crate::{BoundActivityTracker, PreferredLanguage, impl_from_error_for_route};
29+
use crate::{
30+
BoundActivityTracker, PreferredLanguage, impl_from_error_for_route,
31+
session::{SessionOrFallback, load_session_or_fallback},
32+
};
3133

3234
#[derive(Debug, Error)]
3335
pub enum RouteError {
@@ -54,6 +56,7 @@ impl_from_error_for_route!(mas_templates::TemplateError);
5456
impl_from_error_for_route!(mas_storage::RepositoryError);
5557
impl_from_error_for_route!(mas_policy::LoadError);
5658
impl_from_error_for_route!(mas_policy::EvaluationError);
59+
impl_from_error_for_route!(crate::session::SessionLoadError);
5760

5861
impl IntoResponse for RouteError {
5962
fn into_response(self) -> axum::response::Response {
@@ -85,9 +88,18 @@ pub(crate) async fn get(
8588
cookie_jar: CookieJar,
8689
Path(grant_id): Path<Ulid>,
8790
) -> Result<Response, RouteError> {
88-
let (session_info, cookie_jar) = cookie_jar.session_info();
89-
90-
let maybe_session = session_info.load_session(&mut repo).await?;
91+
let (cookie_jar, maybe_session) = match load_session_or_fallback(
92+
cookie_jar, &clock, &mut rng, &templates, &locale, &mut repo,
93+
)
94+
.await?
95+
{
96+
SessionOrFallback::MaybeSession {
97+
cookie_jar,
98+
maybe_session,
99+
..
100+
} => (cookie_jar, maybe_session),
101+
SessionOrFallback::Fallback { response } => return Ok(response),
102+
};
91103

92104
let user_agent = user_agent.map(|ua| ua.to_string());
93105

@@ -107,48 +119,48 @@ pub(crate) async fn get(
107119
return Err(RouteError::GrantNotPending);
108120
}
109121

110-
if let Some(session) = maybe_session {
111-
activity_tracker
112-
.record_browser_session(&clock, &session)
113-
.await;
114-
115-
let (csrf_token, cookie_jar) = cookie_jar.csrf_token(&clock, &mut rng);
116-
117-
let res = policy
118-
.evaluate_authorization_grant(mas_policy::AuthorizationGrantInput {
119-
user: Some(&session.user),
120-
client: &client,
121-
scope: &grant.scope,
122-
grant_type: mas_policy::GrantType::AuthorizationCode,
123-
requester: mas_policy::Requester {
124-
ip_address: activity_tracker.ip(),
125-
user_agent,
126-
},
127-
})
128-
.await?;
129-
130-
if res.valid() {
131-
let ctx = ConsentContext::new(grant, client)
132-
.with_session(session)
133-
.with_csrf(csrf_token.form_value())
134-
.with_language(locale);
135-
136-
let content = templates.render_consent(&ctx)?;
137-
138-
Ok((cookie_jar, Html(content)).into_response())
139-
} else {
140-
let ctx = PolicyViolationContext::for_authorization_grant(grant, client)
141-
.with_session(session)
142-
.with_csrf(csrf_token.form_value())
143-
.with_language(locale);
144-
145-
let content = templates.render_policy_violation(&ctx)?;
146-
147-
Ok((cookie_jar, Html(content)).into_response())
148-
}
149-
} else {
122+
let Some(session) = maybe_session else {
150123
let login = mas_router::Login::and_continue_grant(grant_id);
151-
Ok((cookie_jar, url_builder.redirect(&login)).into_response())
124+
return Ok((cookie_jar, url_builder.redirect(&login)).into_response());
125+
};
126+
127+
activity_tracker
128+
.record_browser_session(&clock, &session)
129+
.await;
130+
131+
let (csrf_token, cookie_jar) = cookie_jar.csrf_token(&clock, &mut rng);
132+
133+
let res = policy
134+
.evaluate_authorization_grant(mas_policy::AuthorizationGrantInput {
135+
user: Some(&session.user),
136+
client: &client,
137+
scope: &grant.scope,
138+
grant_type: mas_policy::GrantType::AuthorizationCode,
139+
requester: mas_policy::Requester {
140+
ip_address: activity_tracker.ip(),
141+
user_agent,
142+
},
143+
})
144+
.await?;
145+
146+
if res.valid() {
147+
let ctx = ConsentContext::new(grant, client)
148+
.with_session(session)
149+
.with_csrf(csrf_token.form_value())
150+
.with_language(locale);
151+
152+
let content = templates.render_consent(&ctx)?;
153+
154+
Ok((cookie_jar, Html(content)).into_response())
155+
} else {
156+
let ctx = PolicyViolationContext::for_authorization_grant(grant, client)
157+
.with_session(session)
158+
.with_csrf(csrf_token.form_value())
159+
.with_language(locale);
160+
161+
let content = templates.render_policy_violation(&ctx)?;
162+
163+
Ok((cookie_jar, Html(content)).into_response())
152164
}
153165
}
154166

@@ -161,6 +173,8 @@ pub(crate) async fn get(
161173
pub(crate) async fn post(
162174
mut rng: BoxRng,
163175
clock: BoxClock,
176+
PreferredLanguage(locale): PreferredLanguage,
177+
State(templates): State<Templates>,
164178
mut policy: Policy,
165179
mut repo: BoxRepository,
166180
activity_tracker: BoundActivityTracker,
@@ -172,9 +186,18 @@ pub(crate) async fn post(
172186
) -> Result<Response, RouteError> {
173187
cookie_jar.verify_form(&clock, form)?;
174188

175-
let (session_info, cookie_jar) = cookie_jar.session_info();
176-
177-
let maybe_session = session_info.load_session(&mut repo).await?;
189+
let (cookie_jar, maybe_session) = match load_session_or_fallback(
190+
cookie_jar, &clock, &mut rng, &templates, &locale, &mut repo,
191+
)
192+
.await?
193+
{
194+
SessionOrFallback::MaybeSession {
195+
cookie_jar,
196+
maybe_session,
197+
..
198+
} => (cookie_jar, maybe_session),
199+
SessionOrFallback::Fallback { response } => return Ok(response),
200+
};
178201

179202
let user_agent = user_agent.map(|ua| ua.to_string());
180203

0 commit comments

Comments
 (0)