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