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}