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