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