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}