Skip to main content

mas_storage/
app_session.rs

1// Copyright 2025, 2026 Element Creations Ltd.
2// Copyright 2024, 2025 New Vector Ltd.
3// Copyright 2023, 2024 The Matrix.org Foundation C.I.C.
4//
5// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
6// Please see LICENSE files in the repository root for full details.
7
8//! Repositories to interact with all kinds of sessions
9
10use async_trait::async_trait;
11use chrono::{DateTime, Utc};
12use mas_data_model::{BrowserSession, Clock, CompatSession, Device, Session, User};
13
14use crate::{Page, Pagination, repository_impl};
15
16/// The state of a session
17#[derive(Clone, Copy, Debug, PartialEq, Eq)]
18pub enum AppSessionState {
19    /// The session is active
20    Active,
21    /// The session is finished
22    Finished,
23}
24
25impl AppSessionState {
26    /// Returns [`true`] if we're looking for active sessions
27    #[must_use]
28    pub fn is_active(self) -> bool {
29        matches!(self, Self::Active)
30    }
31
32    /// Returns [`true`] if we're looking for finished sessions
33    #[must_use]
34    pub fn is_finished(self) -> bool {
35        matches!(self, Self::Finished)
36    }
37}
38
39/// An [`AppSession`] is either a [`CompatSession`] or an OAuth 2.0 [`Session`]
40#[derive(Debug, Clone, PartialEq, Eq)]
41pub enum AppSession {
42    /// A compatibility layer session
43    Compat(Box<CompatSession>),
44
45    /// An OAuth 2.0 session
46    OAuth2(Box<Session>),
47}
48
49/// Filtering parameters for application sessions
50#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
51pub struct AppSessionFilter<'a> {
52    user: Option<&'a User>,
53    browser_session: Option<&'a BrowserSession>,
54    state: Option<AppSessionState>,
55    device_id: Option<&'a Device>,
56    last_active_before: Option<DateTime<Utc>>,
57    last_active_after: Option<DateTime<Utc>>,
58    created_before: Option<DateTime<Utc>>,
59    created_after: Option<DateTime<Utc>>,
60}
61
62impl<'a> AppSessionFilter<'a> {
63    /// Create a new [`AppSessionFilter`] with default values
64    #[must_use]
65    pub fn new() -> Self {
66        Self::default()
67    }
68
69    /// Set the user who owns the sessions
70    #[must_use]
71    pub fn for_user(mut self, user: &'a User) -> Self {
72        self.user = Some(user);
73        self
74    }
75
76    /// Get the user filter
77    #[must_use]
78    pub fn user(&self) -> Option<&'a User> {
79        self.user
80    }
81
82    /// Set the browser session filter
83    #[must_use]
84    pub fn for_browser_session(mut self, browser_session: &'a BrowserSession) -> Self {
85        self.browser_session = Some(browser_session);
86        self
87    }
88
89    /// Get the browser session filter
90    #[must_use]
91    pub fn browser_session(&self) -> Option<&'a BrowserSession> {
92        self.browser_session
93    }
94
95    /// Set the device ID filter
96    #[must_use]
97    pub fn for_device(mut self, device_id: &'a Device) -> Self {
98        self.device_id = Some(device_id);
99        self
100    }
101
102    /// Get the device ID filter
103    #[must_use]
104    pub fn device(&self) -> Option<&'a Device> {
105        self.device_id
106    }
107
108    /// Only return sessions with a last active time before the given time
109    #[must_use]
110    pub fn with_last_active_before(mut self, last_active_before: DateTime<Utc>) -> Self {
111        self.last_active_before = Some(last_active_before);
112        self
113    }
114
115    /// Only return sessions with a last active time after the given time
116    #[must_use]
117    pub fn with_last_active_after(mut self, last_active_after: DateTime<Utc>) -> Self {
118        self.last_active_after = Some(last_active_after);
119        self
120    }
121
122    /// Get the last active before filter
123    ///
124    /// Returns [`None`] if no client filter was set
125    #[must_use]
126    pub fn last_active_before(&self) -> Option<DateTime<Utc>> {
127        self.last_active_before
128    }
129
130    /// Get the last active after filter
131    ///
132    /// Returns [`None`] if no client filter was set
133    #[must_use]
134    pub fn last_active_after(&self) -> Option<DateTime<Utc>> {
135        self.last_active_after
136    }
137
138    /// Only return sessions created before the given time
139    #[must_use]
140    pub fn with_created_before(mut self, created_before: DateTime<Utc>) -> Self {
141        self.created_before = Some(created_before);
142        self
143    }
144
145    /// Only return sessions created after the given time
146    #[must_use]
147    pub fn with_created_after(mut self, created_after: DateTime<Utc>) -> Self {
148        self.created_after = Some(created_after);
149        self
150    }
151
152    /// Get the created-before filter
153    ///
154    /// Returns [`None`] if no filter was set
155    #[must_use]
156    pub fn created_before(&self) -> Option<DateTime<Utc>> {
157        self.created_before
158    }
159
160    /// Get the created-after filter
161    ///
162    /// Returns [`None`] if no filter was set
163    #[must_use]
164    pub fn created_after(&self) -> Option<DateTime<Utc>> {
165        self.created_after
166    }
167
168    /// Only return active compatibility sessions
169    #[must_use]
170    pub fn active_only(mut self) -> Self {
171        self.state = Some(AppSessionState::Active);
172        self
173    }
174
175    /// Only return finished compatibility sessions
176    #[must_use]
177    pub fn finished_only(mut self) -> Self {
178        self.state = Some(AppSessionState::Finished);
179        self
180    }
181
182    /// Get the state filter
183    #[must_use]
184    pub fn state(&self) -> Option<AppSessionState> {
185        self.state
186    }
187}
188
189/// A [`AppSessionRepository`] helps interacting with both [`CompatSession`] and
190/// OAuth 2.0 [`Session`] at the same time saved in the storage backend
191#[async_trait]
192pub trait AppSessionRepository: Send + Sync {
193    /// The error type returned by the repository
194    type Error;
195
196    /// List [`AppSession`] with the given filter and pagination
197    ///
198    /// Returns a page of [`AppSession`] matching the given filter
199    ///
200    /// # Parameters
201    ///
202    /// * `filter`: The filter to apply
203    /// * `pagination`: The pagination parameters
204    ///
205    /// # Errors
206    ///
207    /// Returns [`Self::Error`] if the underlying repository fails
208    async fn list(
209        &mut self,
210        filter: AppSessionFilter<'_>,
211        pagination: Pagination,
212    ) -> Result<Page<AppSession>, Self::Error>;
213
214    /// Count the number of [`AppSession`] with the given filter
215    ///
216    /// # Parameters
217    ///
218    /// * `filter`: The filter to apply
219    ///
220    /// # Errors
221    ///
222    /// Returns [`Self::Error`] if the underlying repository fails
223    async fn count(&mut self, filter: AppSessionFilter<'_>) -> Result<usize, Self::Error>;
224
225    /// Finishes any application sessions that are using the specified device's
226    /// ID.
227    ///
228    /// This is intended for logging in using an existing device ID (i.e.
229    /// replacing a device).
230    ///
231    /// Should be called *before* creating a new session for the device.
232    ///
233    /// Returns true if a session was finished.
234    async fn finish_sessions_to_replace_device(
235        &mut self,
236        clock: &dyn Clock,
237        user: &User,
238        device: &Device,
239    ) -> Result<bool, Self::Error>;
240}
241
242repository_impl!(AppSessionRepository:
243    async fn list(
244        &mut self,
245        filter: AppSessionFilter<'_>,
246        pagination: Pagination,
247    ) -> Result<Page<AppSession>, Self::Error>;
248
249    async fn count(&mut self, filter: AppSessionFilter<'_>) -> Result<usize, Self::Error>;
250
251    async fn finish_sessions_to_replace_device(
252        &mut self,
253        clock: &dyn Clock,
254        user: &User,
255        device: &Device,
256    ) -> Result<bool, Self::Error>;
257);