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