mas_storage/personal/session.rs
1// Copyright 2025 New Vector Ltd.
2//
3// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
4// Please see LICENSE files in the repository root for full details.
5
6use std::net::IpAddr;
7
8use async_trait::async_trait;
9use chrono::{DateTime, Utc};
10use mas_data_model::{
11 Client, Clock, Device, User,
12 personal::{
13 PersonalAccessToken,
14 session::{PersonalSession, PersonalSessionOwner},
15 },
16};
17use oauth2_types::scope::Scope;
18use rand_core::RngCore;
19use ulid::Ulid;
20
21use crate::{Page, Pagination, repository_impl};
22
23/// A [`PersonalSessionRepository`] helps interacting with
24/// [`PersonalSession`] saved in the storage backend
25#[async_trait]
26pub trait PersonalSessionRepository: Send + Sync {
27 /// The error type returned by the repository
28 type Error;
29
30 /// Lookup a Personal session by its ID
31 ///
32 /// Returns the Personal session if it exists, `None` otherwise
33 ///
34 /// # Parameters
35 ///
36 /// * `id`: The ID of the Personal session to lookup
37 ///
38 /// # Errors
39 ///
40 /// Returns [`Self::Error`] if the underlying repository fails
41 async fn lookup(&mut self, id: Ulid) -> Result<Option<PersonalSession>, Self::Error>;
42
43 /// Start a new Personal session
44 ///
45 /// Returns the newly created Personal session
46 ///
47 /// # Parameters
48 ///
49 /// * `rng`: The random number generator to use
50 /// * `clock`: The clock used to generate timestamps
51 /// * `owner_user`: The user that will own the personal session
52 /// * `actor_user`: The user that will be represented by the personal
53 /// session
54 /// * `device`: The device ID of this session
55 /// * `human_name`: The human-readable name of the session provided by the
56 /// client or the user
57 /// * `scope`: The [`Scope`] of the [`PersonalSession`]
58 ///
59 /// # Errors
60 ///
61 /// Returns [`Self::Error`] if the underlying repository fails
62 async fn add(
63 &mut self,
64 rng: &mut (dyn RngCore + Send),
65 clock: &dyn Clock,
66 owner: PersonalSessionOwner,
67 actor_user: &User,
68 human_name: String,
69 scope: Scope,
70 ) -> Result<PersonalSession, Self::Error>;
71
72 /// End a Personal session
73 ///
74 /// Returns the ended Personal session
75 ///
76 /// # Parameters
77 ///
78 /// * `clock`: The clock used to generate timestamps
79 /// * `Personal_session`: The Personal session to end
80 ///
81 /// # Errors
82 ///
83 /// Returns [`Self::Error`] if the underlying repository fails
84 async fn revoke(
85 &mut self,
86 clock: &dyn Clock,
87 personal_session: PersonalSession,
88 ) -> Result<PersonalSession, Self::Error>;
89
90 /// Revoke all the [`PersonalSession`]s matching the given filter.
91 ///
92 /// Returns the number of sessions affected
93 ///
94 /// # Parameters
95 ///
96 /// * `clock`: The clock used to generate timestamps
97 /// * `filter`: The filter to apply
98 ///
99 /// # Errors
100 ///
101 /// Returns [`Self::Error`] if the underlying repository fails
102 async fn revoke_bulk(
103 &mut self,
104 clock: &dyn Clock,
105 filter: PersonalSessionFilter<'_>,
106 ) -> Result<usize, Self::Error>;
107
108 /// List [`PersonalSession`]s matching the given filter and pagination
109 /// parameters
110 ///
111 /// # Parameters
112 ///
113 /// * `filter`: The filter parameters
114 /// * `pagination`: The pagination parameters
115 ///
116 /// # Errors
117 ///
118 /// Returns [`Self::Error`] if the underlying repository fails
119 async fn list(
120 &mut self,
121 filter: PersonalSessionFilter<'_>,
122 pagination: Pagination,
123 ) -> Result<Page<(PersonalSession, Option<PersonalAccessToken>)>, Self::Error>;
124
125 /// Count [`PersonalSession`]s matching the given filter
126 ///
127 /// # Parameters
128 ///
129 /// * `filter`: The filter parameters
130 ///
131 /// # Errors
132 ///
133 /// Returns [`Self::Error`] if the underlying repository fails
134 async fn count(&mut self, filter: PersonalSessionFilter<'_>) -> Result<usize, Self::Error>;
135
136 /// Record a batch of [`PersonalSession`] activity
137 ///
138 /// # Parameters
139 ///
140 /// * `activity`: A list of tuples containing the session ID, the last
141 /// activity timestamp and the IP address of the client
142 ///
143 /// # Errors
144 ///
145 /// Returns [`Self::Error`] if the underlying repository fails
146 async fn record_batch_activity(
147 &mut self,
148 activity: Vec<(Ulid, DateTime<Utc>, Option<IpAddr>)>,
149 ) -> Result<(), Self::Error>;
150}
151
152repository_impl!(PersonalSessionRepository:
153 async fn lookup(&mut self, id: Ulid) -> Result<Option<PersonalSession>, Self::Error>;
154
155 async fn add(
156 &mut self,
157 rng: &mut (dyn RngCore + Send),
158 clock: &dyn Clock,
159 owner: PersonalSessionOwner,
160 actor_user: &User,
161 human_name: String,
162 scope: Scope,
163 ) -> Result<PersonalSession, Self::Error>;
164
165 async fn revoke(
166 &mut self,
167 clock: &dyn Clock,
168 personal_session: PersonalSession,
169 ) -> Result<PersonalSession, Self::Error>;
170
171 async fn revoke_bulk(
172 &mut self,
173 clock: &dyn Clock,
174 filter: PersonalSessionFilter<'_>,
175 ) -> Result<usize, Self::Error>;
176
177 async fn list(
178 &mut self,
179 filter: PersonalSessionFilter<'_>,
180 pagination: Pagination,
181 ) -> Result<Page<(PersonalSession, Option<PersonalAccessToken>)>, Self::Error>;
182
183 async fn count(&mut self, filter: PersonalSessionFilter<'_>) -> Result<usize, Self::Error>;
184
185 async fn record_batch_activity(
186 &mut self,
187 activity: Vec<(Ulid, DateTime<Utc>, Option<IpAddr>)>,
188 ) -> Result<(), Self::Error>;
189);
190
191/// Filter parameters for listing personal sessions alongside personal access
192/// tokens
193#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
194pub struct PersonalSessionFilter<'a> {
195 owner_user: Option<&'a User>,
196 owner_oauth2_client: Option<&'a Client>,
197 actor_user: Option<&'a User>,
198 device: Option<&'a Device>,
199 state: Option<PersonalSessionState>,
200 scope: Option<&'a Scope>,
201 last_active_before: Option<DateTime<Utc>>,
202 last_active_after: Option<DateTime<Utc>>,
203 expires_before: Option<DateTime<Utc>>,
204 expires_after: Option<DateTime<Utc>>,
205 expires: Option<bool>,
206}
207
208/// Filter for what state a personal session is in.
209#[derive(Clone, Copy, Debug, PartialEq, Eq)]
210pub enum PersonalSessionState {
211 /// The personal session is active, which means it either
212 /// has active access tokens or can have new access tokens generated.
213 Active,
214 /// The personal session is revoked, which means no more access tokens
215 /// can be generated and none are active.
216 Revoked,
217}
218
219impl<'a> PersonalSessionFilter<'a> {
220 /// Create a new [`PersonalSessionFilter`] with default values
221 #[must_use]
222 pub fn new() -> Self {
223 Self::default()
224 }
225
226 /// List sessions owned by a specific user
227 #[must_use]
228 pub fn for_owner_user(mut self, user: &'a User) -> Self {
229 self.owner_user = Some(user);
230 self
231 }
232
233 /// Get the owner user filter
234 ///
235 /// Returns [`None`] if no user filter was set
236 #[must_use]
237 pub fn owner_oauth2_client(&self) -> Option<&'a Client> {
238 self.owner_oauth2_client
239 }
240
241 /// List sessions owned by a specific user
242 #[must_use]
243 pub fn for_owner_oauth2_client(mut self, client: &'a Client) -> Self {
244 self.owner_oauth2_client = Some(client);
245 self
246 }
247
248 /// Get the owner user filter
249 ///
250 /// Returns [`None`] if no user filter was set
251 #[must_use]
252 pub fn owner_user(&self) -> Option<&'a User> {
253 self.owner_user
254 }
255
256 /// List sessions acting as a specific user
257 #[must_use]
258 pub fn for_actor_user(mut self, user: &'a User) -> Self {
259 self.actor_user = Some(user);
260 self
261 }
262
263 /// Get the actor user filter
264 ///
265 /// Returns [`None`] if no user filter was set
266 #[must_use]
267 pub fn actor_user(&self) -> Option<&'a User> {
268 self.actor_user
269 }
270
271 /// Only return sessions with a last active time before the given time
272 #[must_use]
273 pub fn with_last_active_before(mut self, last_active_before: DateTime<Utc>) -> Self {
274 self.last_active_before = Some(last_active_before);
275 self
276 }
277
278 /// Only return sessions with a last active time after the given time
279 #[must_use]
280 pub fn with_last_active_after(mut self, last_active_after: DateTime<Utc>) -> Self {
281 self.last_active_after = Some(last_active_after);
282 self
283 }
284
285 /// Get the last active before filter
286 ///
287 /// Returns [`None`] if no client filter was set
288 #[must_use]
289 pub fn last_active_before(&self) -> Option<DateTime<Utc>> {
290 self.last_active_before
291 }
292
293 /// Get the last active after filter
294 ///
295 /// Returns [`None`] if no client filter was set
296 #[must_use]
297 pub fn last_active_after(&self) -> Option<DateTime<Utc>> {
298 self.last_active_after
299 }
300
301 /// Only return active sessions
302 #[must_use]
303 pub fn active_only(mut self) -> Self {
304 self.state = Some(PersonalSessionState::Active);
305 self
306 }
307
308 /// Only return finished sessions
309 #[must_use]
310 pub fn finished_only(mut self) -> Self {
311 self.state = Some(PersonalSessionState::Revoked);
312 self
313 }
314
315 /// Get the state filter
316 ///
317 /// Returns [`None`] if no state filter was set
318 #[must_use]
319 pub fn state(&self) -> Option<PersonalSessionState> {
320 self.state
321 }
322
323 /// Only return sessions with the given scope
324 #[must_use]
325 pub fn with_scope(mut self, scope: &'a Scope) -> Self {
326 self.scope = Some(scope);
327 self
328 }
329
330 /// Get the scope filter
331 ///
332 /// Returns [`None`] if no scope filter was set
333 #[must_use]
334 pub fn scope(&self) -> Option<&'a Scope> {
335 self.scope
336 }
337
338 /// Only return sessions that have the given device in their scope
339 #[must_use]
340 pub fn for_device(mut self, device: &'a Device) -> Self {
341 self.device = Some(device);
342 self
343 }
344
345 /// Get the device filter
346 ///
347 /// Returns [`None`] if no device filter was set
348 #[must_use]
349 pub fn device(&self) -> Option<&'a Device> {
350 self.device
351 }
352
353 /// Only return sessions whose access tokens expire before the given time
354 #[must_use]
355 pub fn with_expires_before(mut self, expires_before: DateTime<Utc>) -> Self {
356 self.expires_before = Some(expires_before);
357 self
358 }
359
360 /// Get the expires before filter
361 ///
362 /// Returns [`None`] if no expires before filter was set
363 #[must_use]
364 pub fn expires_before(&self) -> Option<DateTime<Utc>> {
365 self.expires_before
366 }
367
368 /// Only return sessions whose access tokens expire after the given time
369 #[must_use]
370 pub fn with_expires_after(mut self, expires_after: DateTime<Utc>) -> Self {
371 self.expires_after = Some(expires_after);
372 self
373 }
374
375 /// Get the expires after filter
376 ///
377 /// Returns [`None`] if no expires after filter was set
378 #[must_use]
379 pub fn expires_after(&self) -> Option<DateTime<Utc>> {
380 self.expires_after
381 }
382
383 /// Only return sessions whose access tokens have, or don't have,
384 /// an expiry time set
385 #[must_use]
386 pub fn with_expires(mut self, expires: bool) -> Self {
387 self.expires = Some(expires);
388 self
389 }
390
391 /// Get the expires filter
392 ///
393 /// Returns [`None`] if no expires filter was set
394 #[must_use]
395 pub fn expires(&self) -> Option<bool> {
396 self.expires
397 }
398}