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::session::{PersonalSession, PersonalSessionOwner},
13};
14use oauth2_types::scope::Scope;
15use rand_core::RngCore;
16use ulid::Ulid;
17
18use crate::{Page, Pagination, repository_impl};
19
20/// A [`PersonalSessionRepository`] helps interacting with
21/// [`PersonalSession`] saved in the storage backend
22#[async_trait]
23pub trait PersonalSessionRepository: Send + Sync {
24    /// The error type returned by the repository
25    type Error;
26
27    /// Lookup a Personal session by its ID
28    ///
29    /// Returns the Personal session if it exists, `None` otherwise
30    ///
31    /// # Parameters
32    ///
33    /// * `id`: The ID of the Personal session to lookup
34    ///
35    /// # Errors
36    ///
37    /// Returns [`Self::Error`] if the underlying repository fails
38    async fn lookup(&mut self, id: Ulid) -> Result<Option<PersonalSession>, Self::Error>;
39
40    /// Start a new Personal session
41    ///
42    /// Returns the newly created Personal session
43    ///
44    /// # Parameters
45    ///
46    /// * `rng`: The random number generator to use
47    /// * `clock`: The clock used to generate timestamps
48    /// * `owner_user`: The user that will own the personal session
49    /// * `actor_user`: The user that will be represented by the personal
50    ///   session
51    /// * `device`: The device ID of this session
52    /// * `human_name`: The human-readable name of the session provided by the
53    ///   client or the user
54    /// * `scope`: The [`Scope`] of the [`PersonalSession`]
55    ///
56    /// # Errors
57    ///
58    /// Returns [`Self::Error`] if the underlying repository fails
59    async fn add(
60        &mut self,
61        rng: &mut (dyn RngCore + Send),
62        clock: &dyn Clock,
63        owner: PersonalSessionOwner,
64        actor_user: &User,
65        human_name: String,
66        scope: Scope,
67    ) -> Result<PersonalSession, Self::Error>;
68
69    /// End a Personal session
70    ///
71    /// Returns the ended Personal session
72    ///
73    /// # Parameters
74    ///
75    /// * `clock`: The clock used to generate timestamps
76    /// * `Personal_session`: The Personal session to end
77    ///
78    /// # Errors
79    ///
80    /// Returns [`Self::Error`] if the underlying repository fails
81    async fn revoke(
82        &mut self,
83        clock: &dyn Clock,
84        personal_session: PersonalSession,
85    ) -> Result<PersonalSession, Self::Error>;
86
87    /// List [`PersonalSession`]s matching the given filter and pagination
88    /// parameters
89    ///
90    /// # Parameters
91    ///
92    /// * `filter`: The filter parameters
93    /// * `pagination`: The pagination parameters
94    ///
95    /// # Errors
96    ///
97    /// Returns [`Self::Error`] if the underlying repository fails
98    async fn list(
99        &mut self,
100        filter: PersonalSessionFilter<'_>,
101        pagination: Pagination,
102    ) -> Result<Page<PersonalSession>, Self::Error>;
103
104    /// Count [`PersonalSession`]s matching the given filter
105    ///
106    /// # Parameters
107    ///
108    /// * `filter`: The filter parameters
109    ///
110    /// # Errors
111    ///
112    /// Returns [`Self::Error`] if the underlying repository fails
113    async fn count(&mut self, filter: PersonalSessionFilter<'_>) -> Result<usize, Self::Error>;
114
115    /// Record a batch of [`PersonalSession`] activity
116    ///
117    /// # Parameters
118    ///
119    /// * `activity`: A list of tuples containing the session ID, the last
120    ///   activity timestamp and the IP address of the client
121    ///
122    /// # Errors
123    ///
124    /// Returns [`Self::Error`] if the underlying repository fails
125    async fn record_batch_activity(
126        &mut self,
127        activity: Vec<(Ulid, DateTime<Utc>, Option<IpAddr>)>,
128    ) -> Result<(), Self::Error>;
129}
130
131repository_impl!(PersonalSessionRepository:
132    async fn lookup(&mut self, id: Ulid) -> Result<Option<PersonalSession>, Self::Error>;
133
134    async fn add(
135        &mut self,
136        rng: &mut (dyn RngCore + Send),
137        clock: &dyn Clock,
138        owner: PersonalSessionOwner,
139        actor_user: &User,
140        human_name: String,
141        scope: Scope,
142    ) -> Result<PersonalSession, Self::Error>;
143
144    async fn revoke(
145        &mut self,
146        clock: &dyn Clock,
147        personal_session: PersonalSession,
148    ) -> Result<PersonalSession, Self::Error>;
149
150    async fn list(
151        &mut self,
152        filter: PersonalSessionFilter<'_>,
153        pagination: Pagination,
154    ) -> Result<Page<PersonalSession>, Self::Error>;
155
156    async fn count(&mut self, filter: PersonalSessionFilter<'_>) -> Result<usize, Self::Error>;
157
158    async fn record_batch_activity(
159        &mut self,
160        activity: Vec<(Ulid, DateTime<Utc>, Option<IpAddr>)>,
161    ) -> Result<(), Self::Error>;
162);
163
164/// Filter parameters for listing personal sessions
165#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
166pub struct PersonalSessionFilter<'a> {
167    owner_user: Option<&'a User>,
168    owner_oauth2_client: Option<&'a Client>,
169    actor_user: Option<&'a User>,
170    device: Option<&'a Device>,
171    state: Option<PersonalSessionState>,
172    scope: Option<&'a Scope>,
173    last_active_before: Option<DateTime<Utc>>,
174    last_active_after: Option<DateTime<Utc>>,
175}
176
177/// Filter for what state a personal session is in.
178#[derive(Clone, Copy, Debug, PartialEq, Eq)]
179pub enum PersonalSessionState {
180    /// The personal session is active, which means it either
181    /// has active access tokens or can have new access tokens generated.
182    Active,
183    /// The personal session is revoked, which means no more access tokens
184    /// can be generated and none are active.
185    Revoked,
186}
187
188impl<'a> PersonalSessionFilter<'a> {
189    /// Create a new [`PersonalSessionFilter`] with default values
190    #[must_use]
191    pub fn new() -> Self {
192        Self::default()
193    }
194
195    /// List sessions owned by a specific user
196    #[must_use]
197    pub fn for_owner_user(mut self, user: &'a User) -> Self {
198        self.owner_user = Some(user);
199        self
200    }
201
202    /// Get the owner user filter
203    ///
204    /// Returns [`None`] if no user filter was set
205    #[must_use]
206    pub fn owner_oauth2_client(&self) -> Option<&'a Client> {
207        self.owner_oauth2_client
208    }
209
210    /// List sessions owned by a specific user
211    #[must_use]
212    pub fn for_owner_oauth2_client(mut self, client: &'a Client) -> Self {
213        self.owner_oauth2_client = Some(client);
214        self
215    }
216
217    /// Get the owner user filter
218    ///
219    /// Returns [`None`] if no user filter was set
220    #[must_use]
221    pub fn owner_user(&self) -> Option<&'a User> {
222        self.owner_user
223    }
224
225    /// List sessions acting as a specific user
226    #[must_use]
227    pub fn for_actor_user(mut self, user: &'a User) -> Self {
228        self.actor_user = Some(user);
229        self
230    }
231
232    /// Get the actor user filter
233    ///
234    /// Returns [`None`] if no user filter was set
235    #[must_use]
236    pub fn actor_user(&self) -> Option<&'a User> {
237        self.actor_user
238    }
239
240    /// Only return sessions with a last active time before the given time
241    #[must_use]
242    pub fn with_last_active_before(mut self, last_active_before: DateTime<Utc>) -> Self {
243        self.last_active_before = Some(last_active_before);
244        self
245    }
246
247    /// Only return sessions with a last active time after the given time
248    #[must_use]
249    pub fn with_last_active_after(mut self, last_active_after: DateTime<Utc>) -> Self {
250        self.last_active_after = Some(last_active_after);
251        self
252    }
253
254    /// Get the last active before filter
255    ///
256    /// Returns [`None`] if no client filter was set
257    #[must_use]
258    pub fn last_active_before(&self) -> Option<DateTime<Utc>> {
259        self.last_active_before
260    }
261
262    /// Get the last active after filter
263    ///
264    /// Returns [`None`] if no client filter was set
265    #[must_use]
266    pub fn last_active_after(&self) -> Option<DateTime<Utc>> {
267        self.last_active_after
268    }
269
270    /// Only return active sessions
271    #[must_use]
272    pub fn active_only(mut self) -> Self {
273        self.state = Some(PersonalSessionState::Active);
274        self
275    }
276
277    /// Only return finished sessions
278    #[must_use]
279    pub fn finished_only(mut self) -> Self {
280        self.state = Some(PersonalSessionState::Revoked);
281        self
282    }
283
284    /// Get the state filter
285    ///
286    /// Returns [`None`] if no state filter was set
287    #[must_use]
288    pub fn state(&self) -> Option<PersonalSessionState> {
289        self.state
290    }
291
292    /// Only return sessions with the given scope
293    #[must_use]
294    pub fn with_scope(mut self, scope: &'a Scope) -> Self {
295        self.scope = Some(scope);
296        self
297    }
298
299    /// Get the scope filter
300    ///
301    /// Returns [`None`] if no scope filter was set
302    #[must_use]
303    pub fn scope(&self) -> Option<&'a Scope> {
304        self.scope
305    }
306
307    /// Only return sessions that have the given device in their scope
308    #[must_use]
309    pub fn for_device(mut self, device: &'a Device) -> Self {
310        self.device = Some(device);
311        self
312    }
313
314    /// Get the device filter
315    ///
316    /// Returns [`None`] if no device filter was set
317    #[must_use]
318    pub fn device(&self) -> Option<&'a Device> {
319        self.device
320    }
321}