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 /// List [`PersonalSession`]s matching the given filter and pagination
91 /// parameters
92 ///
93 /// # Parameters
94 ///
95 /// * `filter`: The filter parameters
96 /// * `pagination`: The pagination parameters
97 ///
98 /// # Errors
99 ///
100 /// Returns [`Self::Error`] if the underlying repository fails
101 async fn list(
102 &mut self,
103 filter: PersonalSessionFilter<'_>,
104 pagination: Pagination,
105 ) -> Result<Page<(PersonalSession, Option<PersonalAccessToken>)>, Self::Error>;
106
107 /// Count [`PersonalSession`]s matching the given filter
108 ///
109 /// # Parameters
110 ///
111 /// * `filter`: The filter parameters
112 ///
113 /// # Errors
114 ///
115 /// Returns [`Self::Error`] if the underlying repository fails
116 async fn count(&mut self, filter: PersonalSessionFilter<'_>) -> Result<usize, Self::Error>;
117
118 /// Record a batch of [`PersonalSession`] activity
119 ///
120 /// # Parameters
121 ///
122 /// * `activity`: A list of tuples containing the session ID, the last
123 /// activity timestamp and the IP address of the client
124 ///
125 /// # Errors
126 ///
127 /// Returns [`Self::Error`] if the underlying repository fails
128 async fn record_batch_activity(
129 &mut self,
130 activity: Vec<(Ulid, DateTime<Utc>, Option<IpAddr>)>,
131 ) -> Result<(), Self::Error>;
132}
133
134repository_impl!(PersonalSessionRepository:
135 async fn lookup(&mut self, id: Ulid) -> Result<Option<PersonalSession>, Self::Error>;
136
137 async fn add(
138 &mut self,
139 rng: &mut (dyn RngCore + Send),
140 clock: &dyn Clock,
141 owner: PersonalSessionOwner,
142 actor_user: &User,
143 human_name: String,
144 scope: Scope,
145 ) -> Result<PersonalSession, Self::Error>;
146
147 async fn revoke(
148 &mut self,
149 clock: &dyn Clock,
150 personal_session: PersonalSession,
151 ) -> Result<PersonalSession, Self::Error>;
152
153 async fn list(
154 &mut self,
155 filter: PersonalSessionFilter<'_>,
156 pagination: Pagination,
157 ) -> Result<Page<(PersonalSession, Option<PersonalAccessToken>)>, Self::Error>;
158
159 async fn count(&mut self, filter: PersonalSessionFilter<'_>) -> Result<usize, Self::Error>;
160
161 async fn record_batch_activity(
162 &mut self,
163 activity: Vec<(Ulid, DateTime<Utc>, Option<IpAddr>)>,
164 ) -> Result<(), Self::Error>;
165);
166
167/// Filter parameters for listing personal sessions alongside personal access
168/// tokens
169#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
170pub struct PersonalSessionFilter<'a> {
171 owner_user: Option<&'a User>,
172 owner_oauth2_client: Option<&'a Client>,
173 actor_user: Option<&'a User>,
174 device: Option<&'a Device>,
175 state: Option<PersonalSessionState>,
176 scope: Option<&'a Scope>,
177 last_active_before: Option<DateTime<Utc>>,
178 last_active_after: Option<DateTime<Utc>>,
179 expires_before: Option<DateTime<Utc>>,
180 expires_after: Option<DateTime<Utc>>,
181 expires: Option<bool>,
182}
183
184/// Filter for what state a personal session is in.
185#[derive(Clone, Copy, Debug, PartialEq, Eq)]
186pub enum PersonalSessionState {
187 /// The personal session is active, which means it either
188 /// has active access tokens or can have new access tokens generated.
189 Active,
190 /// The personal session is revoked, which means no more access tokens
191 /// can be generated and none are active.
192 Revoked,
193}
194
195impl<'a> PersonalSessionFilter<'a> {
196 /// Create a new [`PersonalSessionFilter`] with default values
197 #[must_use]
198 pub fn new() -> Self {
199 Self::default()
200 }
201
202 /// List sessions owned by a specific user
203 #[must_use]
204 pub fn for_owner_user(mut self, user: &'a User) -> Self {
205 self.owner_user = Some(user);
206 self
207 }
208
209 /// Get the owner user filter
210 ///
211 /// Returns [`None`] if no user filter was set
212 #[must_use]
213 pub fn owner_oauth2_client(&self) -> Option<&'a Client> {
214 self.owner_oauth2_client
215 }
216
217 /// List sessions owned by a specific user
218 #[must_use]
219 pub fn for_owner_oauth2_client(mut self, client: &'a Client) -> Self {
220 self.owner_oauth2_client = Some(client);
221 self
222 }
223
224 /// Get the owner user filter
225 ///
226 /// Returns [`None`] if no user filter was set
227 #[must_use]
228 pub fn owner_user(&self) -> Option<&'a User> {
229 self.owner_user
230 }
231
232 /// List sessions acting as a specific user
233 #[must_use]
234 pub fn for_actor_user(mut self, user: &'a User) -> Self {
235 self.actor_user = Some(user);
236 self
237 }
238
239 /// Get the actor user filter
240 ///
241 /// Returns [`None`] if no user filter was set
242 #[must_use]
243 pub fn actor_user(&self) -> Option<&'a User> {
244 self.actor_user
245 }
246
247 /// Only return sessions with a last active time before the given time
248 #[must_use]
249 pub fn with_last_active_before(mut self, last_active_before: DateTime<Utc>) -> Self {
250 self.last_active_before = Some(last_active_before);
251 self
252 }
253
254 /// Only return sessions with a last active time after the given time
255 #[must_use]
256 pub fn with_last_active_after(mut self, last_active_after: DateTime<Utc>) -> Self {
257 self.last_active_after = Some(last_active_after);
258 self
259 }
260
261 /// Get the last active before filter
262 ///
263 /// Returns [`None`] if no client filter was set
264 #[must_use]
265 pub fn last_active_before(&self) -> Option<DateTime<Utc>> {
266 self.last_active_before
267 }
268
269 /// Get the last active after filter
270 ///
271 /// Returns [`None`] if no client filter was set
272 #[must_use]
273 pub fn last_active_after(&self) -> Option<DateTime<Utc>> {
274 self.last_active_after
275 }
276
277 /// Only return active sessions
278 #[must_use]
279 pub fn active_only(mut self) -> Self {
280 self.state = Some(PersonalSessionState::Active);
281 self
282 }
283
284 /// Only return finished sessions
285 #[must_use]
286 pub fn finished_only(mut self) -> Self {
287 self.state = Some(PersonalSessionState::Revoked);
288 self
289 }
290
291 /// Get the state filter
292 ///
293 /// Returns [`None`] if no state filter was set
294 #[must_use]
295 pub fn state(&self) -> Option<PersonalSessionState> {
296 self.state
297 }
298
299 /// Only return sessions with the given scope
300 #[must_use]
301 pub fn with_scope(mut self, scope: &'a Scope) -> Self {
302 self.scope = Some(scope);
303 self
304 }
305
306 /// Get the scope filter
307 ///
308 /// Returns [`None`] if no scope filter was set
309 #[must_use]
310 pub fn scope(&self) -> Option<&'a Scope> {
311 self.scope
312 }
313
314 /// Only return sessions that have the given device in their scope
315 #[must_use]
316 pub fn for_device(mut self, device: &'a Device) -> Self {
317 self.device = Some(device);
318 self
319 }
320
321 /// Get the device filter
322 ///
323 /// Returns [`None`] if no device filter was set
324 #[must_use]
325 pub fn device(&self) -> Option<&'a Device> {
326 self.device
327 }
328
329 /// Only return sessions whose access tokens expire before the given time
330 #[must_use]
331 pub fn with_expires_before(mut self, expires_before: DateTime<Utc>) -> Self {
332 self.expires_before = Some(expires_before);
333 self
334 }
335
336 /// Get the expires before filter
337 ///
338 /// Returns [`None`] if no expires before filter was set
339 #[must_use]
340 pub fn expires_before(&self) -> Option<DateTime<Utc>> {
341 self.expires_before
342 }
343
344 /// Only return sessions whose access tokens expire after the given time
345 #[must_use]
346 pub fn with_expires_after(mut self, expires_after: DateTime<Utc>) -> Self {
347 self.expires_after = Some(expires_after);
348 self
349 }
350
351 /// Get the expires after filter
352 ///
353 /// Returns [`None`] if no expires after filter was set
354 #[must_use]
355 pub fn expires_after(&self) -> Option<DateTime<Utc>> {
356 self.expires_after
357 }
358
359 /// Only return sessions whose access tokens have, or don't have,
360 /// an expiry time set
361 #[must_use]
362 pub fn with_expires(mut self, expires: bool) -> Self {
363 self.expires = Some(expires);
364 self
365 }
366
367 /// Get the expires filter
368 ///
369 /// Returns [`None`] if no expires filter was set
370 #[must_use]
371 pub fn expires(&self) -> Option<bool> {
372 self.expires
373 }
374}