mas_storage/user/
session.rs

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