Add helper function to count user sessions for limiting

This commit is contained in:
Olivier 'reivilibre
2025-11-04 15:05:46 +00:00
parent 1c056bfdad
commit db54d90a32

View File

@@ -8,9 +8,13 @@
use axum::response::{Html, IntoResponse as _, Response};
use mas_axum_utils::{SessionInfoExt, cookies::CookieJar, csrf::CsrfExt};
use mas_data_model::{BrowserSession, Clock};
use mas_data_model::{BrowserSession, Clock, User};
use mas_i18n::DataLocale;
use mas_storage::{BoxRepository, RepositoryError};
use mas_policy::model::SessionCounts;
use mas_storage::{
BoxRepository, RepositoryError, compat::CompatSessionFilter, oauth2::OAuth2SessionFilter,
personal::PersonalSessionFilter,
};
use mas_templates::{AccountInactiveContext, TemplateContext, Templates};
use rand::RngCore;
use thiserror::Error;
@@ -102,3 +106,62 @@ pub async fn load_session_or_fallback(
maybe_session: Some(session),
})
}
/// Get a count of sessions for the given user, for the purposes of session
/// limiting.
///
/// Includes:
/// - OAuth 2 sessions
/// - Compatibility sessions
/// - Personal sessions (unless owned by a different user)
///
/// # Backstory
///
/// Originally, we were only intending to count sessions with devices in this
/// result, because those are the entries that are expensive for Synapse and
/// also would not hinder use of deviceless clients (like Element Admin, an
/// admin dashboard).
///
/// However, to do so, we would need to count only sessions including device
/// scopes. To do this efficiently, we'd need a partial index on sessions
/// including device scopes.
///
/// It turns out that this can't be done cleanly (as we need to, in Postgres,
/// match scope lists where one of the scopes matches one of 2 known prefixes),
/// at least not without somewhat uncomfortable stored functions.
///
/// So for simplicity's sake, we now count all sessions.
/// For practical use cases, it's not likely to make a noticeable difference
/// (and maybe it's good that there's an overall limit).
pub(crate) async fn count_user_sessions_for_limiting(
repo: &mut BoxRepository,
user: &User,
) -> anyhow::Result<SessionCounts> {
let oauth2 = repo
.oauth2_session()
.count(OAuth2SessionFilter::new().active_only().for_user(user))
.await? as u64;
let compat = repo
.compat_session()
.count(CompatSessionFilter::new().active_only().for_user(user))
.await? as u64;
// Only include self-owned personal sessions, not administratively-owned ones
let personal = repo
.personal_session()
.count(
PersonalSessionFilter::new()
.active_only()
.for_actor_user(user)
.for_owner_user(user),
)
.await? as u64;
Ok(SessionCounts {
total: oauth2 + compat + personal,
oauth2,
compat,
personal,
})
}