mas_storage/user/
session.rs

1// Copyright 2024, 2025 New Vector Ltd.
2// Copyright 2022-2024 The Matrix.org Foundation C.I.C.
3//
4// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
5// Please see LICENSE files in the repository root for full details.
6
7use std::net::IpAddr;
8
9use async_trait::async_trait;
10use chrono::{DateTime, Utc};
11use mas_data_model::{
12    Authentication, BrowserSession, Clock, Password, UpstreamOAuthAuthorizationSession, User,
13};
14use rand_core::RngCore;
15use ulid::Ulid;
16
17use crate::{
18    Pagination, pagination::Page, repository_impl, upstream_oauth2::UpstreamOAuthSessionFilter,
19};
20
21#[derive(Clone, Copy, Debug, PartialEq, Eq)]
22pub enum BrowserSessionState {
23    Active,
24    Finished,
25}
26
27impl BrowserSessionState {
28    pub fn is_active(self) -> bool {
29        matches!(self, Self::Active)
30    }
31
32    pub fn is_finished(self) -> bool {
33        matches!(self, Self::Finished)
34    }
35}
36
37/// Filter parameters for listing browser sessions
38#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
39pub struct BrowserSessionFilter<'a> {
40    user: Option<&'a User>,
41    state: Option<BrowserSessionState>,
42    last_active_before: Option<DateTime<Utc>>,
43    last_active_after: Option<DateTime<Utc>>,
44    authenticated_by_upstream_sessions: Option<UpstreamOAuthSessionFilter<'a>>,
45}
46
47impl<'a> BrowserSessionFilter<'a> {
48    /// Create a new [`BrowserSessionFilter`] with default values
49    #[must_use]
50    pub fn new() -> Self {
51        Self::default()
52    }
53
54    /// Set the user who owns the browser sessions
55    #[must_use]
56    pub fn for_user(mut self, user: &'a User) -> Self {
57        self.user = Some(user);
58        self
59    }
60
61    /// Get the user filter
62    #[must_use]
63    pub fn user(&self) -> Option<&User> {
64        self.user
65    }
66
67    /// Only return sessions with a last active time before the given time
68    #[must_use]
69    pub fn with_last_active_before(mut self, last_active_before: DateTime<Utc>) -> Self {
70        self.last_active_before = Some(last_active_before);
71        self
72    }
73
74    /// Only return sessions with a last active time after the given time
75    #[must_use]
76    pub fn with_last_active_after(mut self, last_active_after: DateTime<Utc>) -> Self {
77        self.last_active_after = Some(last_active_after);
78        self
79    }
80
81    /// Get the last active before filter
82    ///
83    /// Returns [`None`] if no client filter was set
84    #[must_use]
85    pub fn last_active_before(&self) -> Option<DateTime<Utc>> {
86        self.last_active_before
87    }
88
89    /// Get the last active after filter
90    ///
91    /// Returns [`None`] if no client filter was set
92    #[must_use]
93    pub fn last_active_after(&self) -> Option<DateTime<Utc>> {
94        self.last_active_after
95    }
96
97    /// Only return active browser sessions
98    #[must_use]
99    pub fn active_only(mut self) -> Self {
100        self.state = Some(BrowserSessionState::Active);
101        self
102    }
103
104    /// Only return finished browser sessions
105    #[must_use]
106    pub fn finished_only(mut self) -> Self {
107        self.state = Some(BrowserSessionState::Finished);
108        self
109    }
110
111    /// Get the state filter
112    #[must_use]
113    pub fn state(&self) -> Option<BrowserSessionState> {
114        self.state
115    }
116
117    /// Only return browser sessions authenticated by the given upstream OAuth
118    /// sessions
119    #[must_use]
120    pub fn authenticated_by_upstream_sessions_only(
121        mut self,
122        filter: UpstreamOAuthSessionFilter<'a>,
123    ) -> Self {
124        self.authenticated_by_upstream_sessions = Some(filter);
125        self
126    }
127
128    /// Get the upstream OAuth session filter
129    #[must_use]
130    pub fn authenticated_by_upstream_sessions(&self) -> Option<UpstreamOAuthSessionFilter<'a>> {
131        self.authenticated_by_upstream_sessions
132    }
133}
134
135/// A [`BrowserSessionRepository`] helps interacting with [`BrowserSession`]
136/// saved in the storage backend
137#[async_trait]
138pub trait BrowserSessionRepository: Send + Sync {
139    /// The error type returned by the repository
140    type Error;
141
142    /// Lookup a [`BrowserSession`] by its ID
143    ///
144    /// Returns `None` if the session is not found
145    ///
146    /// # Parameters
147    ///
148    /// * `id`: The ID of the session to lookup
149    ///
150    /// # Errors
151    ///
152    /// Returns [`Self::Error`] if the underlying repository fails
153    async fn lookup(&mut self, id: Ulid) -> Result<Option<BrowserSession>, Self::Error>;
154
155    /// Create a new [`BrowserSession`] for a [`User`]
156    ///
157    /// Returns the newly created [`BrowserSession`]
158    ///
159    /// # Parameters
160    ///
161    /// * `rng`: The random number generator to use
162    /// * `clock`: The clock used to generate timestamps
163    /// * `user`: The user to create the session for
164    /// * `user_agent`: If available, the user agent of the browser
165    ///
166    /// # Errors
167    ///
168    /// Returns [`Self::Error`] if the underlying repository fails
169    async fn add(
170        &mut self,
171        rng: &mut (dyn RngCore + Send),
172        clock: &dyn Clock,
173        user: &User,
174        user_agent: Option<String>,
175    ) -> Result<BrowserSession, Self::Error>;
176
177    /// Finish a [`BrowserSession`]
178    ///
179    /// Returns the finished session
180    ///
181    /// # Parameters
182    ///
183    /// * `clock`: The clock used to generate timestamps
184    /// * `user_session`: The session to finish
185    ///
186    /// # Errors
187    ///
188    /// Returns [`Self::Error`] if the underlying repository fails
189    async fn finish(
190        &mut self,
191        clock: &dyn Clock,
192        user_session: BrowserSession,
193    ) -> Result<BrowserSession, Self::Error>;
194
195    /// Mark all the [`BrowserSession`] matching the given filter as finished
196    ///
197    /// Returns the number of sessions affected
198    ///
199    /// # Parameters
200    ///
201    /// * `clock`: The clock used to generate timestamps
202    /// * `filter`: The filter parameters
203    ///
204    /// # Errors
205    ///
206    /// Returns [`Self::Error`] if the underlying repository fails
207    async fn finish_bulk(
208        &mut self,
209        clock: &dyn Clock,
210        filter: BrowserSessionFilter<'_>,
211    ) -> Result<usize, Self::Error>;
212
213    /// List [`BrowserSession`] with the given filter and pagination
214    ///
215    /// # Parameters
216    ///
217    /// * `filter`: The filter to apply
218    /// * `pagination`: The pagination parameters
219    ///
220    /// # Errors
221    ///
222    /// Returns [`Self::Error`] if the underlying repository fails
223    async fn list(
224        &mut self,
225        filter: BrowserSessionFilter<'_>,
226        pagination: Pagination,
227    ) -> Result<Page<BrowserSession>, Self::Error>;
228
229    /// Count the number of [`BrowserSession`] with the given filter
230    ///
231    /// # Parameters
232    ///
233    /// * `filter`: The filter to apply
234    ///
235    /// # Errors
236    ///
237    /// Returns [`Self::Error`] if the underlying repository fails
238    async fn count(&mut self, filter: BrowserSessionFilter<'_>) -> Result<usize, Self::Error>;
239
240    /// Authenticate a [`BrowserSession`] with the given [`Password`]
241    ///
242    /// # Parameters
243    ///
244    /// * `rng`: The random number generator to use
245    /// * `clock`: The clock used to generate timestamps
246    /// * `user_session`: The session to authenticate
247    /// * `user_password`: The password which was used to authenticate
248    ///
249    /// # Errors
250    ///
251    /// Returns [`Self::Error`] if the underlying repository fails
252    async fn authenticate_with_password(
253        &mut self,
254        rng: &mut (dyn RngCore + Send),
255        clock: &dyn Clock,
256        user_session: &BrowserSession,
257        user_password: &Password,
258    ) -> Result<Authentication, Self::Error>;
259
260    /// Authenticate a [`BrowserSession`] with the given
261    /// [`UpstreamOAuthAuthorizationSession`]
262    ///
263    /// # Parameters
264    ///
265    /// * `rng`: The random number generator to use
266    /// * `clock`: The clock used to generate timestamps
267    /// * `user_session`: The session to authenticate
268    /// * `upstream_oauth_session`: The upstream OAuth session which was used to
269    ///   authenticate
270    ///
271    /// # Errors
272    ///
273    /// Returns [`Self::Error`] if the underlying repository fails
274    async fn authenticate_with_upstream(
275        &mut self,
276        rng: &mut (dyn RngCore + Send),
277        clock: &dyn Clock,
278        user_session: &BrowserSession,
279        upstream_oauth_session: &UpstreamOAuthAuthorizationSession,
280    ) -> Result<Authentication, Self::Error>;
281
282    /// Get the last successful authentication for a [`BrowserSession`]
283    ///
284    /// # Params
285    ///
286    /// * `user_session`: The session for which to get the last authentication
287    ///
288    /// # Errors
289    ///
290    /// Returns [`Self::Error`] if the underlying repository fails
291    async fn get_last_authentication(
292        &mut self,
293        user_session: &BrowserSession,
294    ) -> Result<Option<Authentication>, Self::Error>;
295
296    /// Record a batch of [`BrowserSession`] activity
297    ///
298    /// # Parameters
299    ///
300    /// * `activity`: A list of tuples containing the session ID, the last
301    ///   activity timestamp and the IP address of the client
302    ///
303    /// # Errors
304    ///
305    /// Returns [`Self::Error`] if the underlying repository fails
306    async fn record_batch_activity(
307        &mut self,
308        activity: Vec<(Ulid, DateTime<Utc>, Option<IpAddr>)>,
309    ) -> Result<(), Self::Error>;
310}
311
312repository_impl!(BrowserSessionRepository:
313    async fn lookup(&mut self, id: Ulid) -> Result<Option<BrowserSession>, Self::Error>;
314    async fn add(
315        &mut self,
316        rng: &mut (dyn RngCore + Send),
317        clock: &dyn Clock,
318        user: &User,
319        user_agent: Option<String>,
320    ) -> Result<BrowserSession, Self::Error>;
321    async fn finish(
322        &mut self,
323        clock: &dyn Clock,
324        user_session: BrowserSession,
325    ) -> Result<BrowserSession, Self::Error>;
326
327    async fn finish_bulk(
328        &mut self,
329        clock: &dyn Clock,
330        filter: BrowserSessionFilter<'_>,
331    ) -> Result<usize, Self::Error>;
332
333    async fn list(
334        &mut self,
335        filter: BrowserSessionFilter<'_>,
336        pagination: Pagination,
337    ) -> Result<Page<BrowserSession>, Self::Error>;
338
339    async fn count(&mut self, filter: BrowserSessionFilter<'_>) -> Result<usize, Self::Error>;
340
341    async fn authenticate_with_password(
342        &mut self,
343        rng: &mut (dyn RngCore + Send),
344        clock: &dyn Clock,
345        user_session: &BrowserSession,
346        user_password: &Password,
347    ) -> Result<Authentication, Self::Error>;
348
349    async fn authenticate_with_upstream(
350        &mut self,
351        rng: &mut (dyn RngCore + Send),
352        clock: &dyn Clock,
353        user_session: &BrowserSession,
354        upstream_oauth_session: &UpstreamOAuthAuthorizationSession,
355    ) -> Result<Authentication, Self::Error>;
356
357    async fn get_last_authentication(
358        &mut self,
359        user_session: &BrowserSession,
360    ) -> Result<Option<Authentication>, Self::Error>;
361
362    async fn record_batch_activity(
363        &mut self,
364        activity: Vec<(Ulid, DateTime<Utc>, Option<IpAddr>)>,
365    ) -> Result<(), Self::Error>;
366);