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