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);