1// Copyright 2024 New Vector Ltd.
2// Copyright 2023, 2024 The Matrix.org Foundation C.I.C.
3//
4// SPDX-License-Identifier: AGPL-3.0-only
5// Please see LICENSE in the repository root for full details.
67//! Repositories to interact with all kinds of sessions
89use async_trait::async_trait;
10use chrono::{DateTime, Utc};
11use mas_data_model::{BrowserSession, CompatSession, Device, Session, User};
1213use crate::{Clock, Page, Pagination, repository_impl};
1415/// The state of a session
16#[derive(Clone, Copy, Debug, PartialEq, Eq)]
17pub enum AppSessionState {
18/// The session is active
19Active,
20/// The session is finished
21Finished,
22}
2324impl AppSessionState {
25/// Returns [`true`] if we're looking for active sessions
26#[must_use]
27pub fn is_active(self) -> bool {
28matches!(self, Self::Active)
29 }
3031/// Returns [`true`] if we're looking for finished sessions
32#[must_use]
33pub fn is_finished(self) -> bool {
34matches!(self, Self::Finished)
35 }
36}
3738/// An [`AppSession`] is either a [`CompatSession`] or an OAuth 2.0 [`Session`]
39#[derive(Debug, Clone, PartialEq, Eq)]
40pub enum AppSession {
41/// A compatibility layer session
42Compat(Box<CompatSession>),
4344/// An OAuth 2.0 session
45OAuth2(Box<Session>),
46}
4748/// Filtering parameters for application sessions
49#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
50pub struct AppSessionFilter<'a> {
51 user: Option<&'a User>,
52 browser_session: Option<&'a BrowserSession>,
53 state: Option<AppSessionState>,
54 device_id: Option<&'a Device>,
55 last_active_before: Option<DateTime<Utc>>,
56 last_active_after: Option<DateTime<Utc>>,
57}
5859impl<'a> AppSessionFilter<'a> {
60/// Create a new [`AppSessionFilter`] with default values
61#[must_use]
62pub fn new() -> Self {
63Self::default()
64 }
6566/// Set the user who owns the sessions
67#[must_use]
68pub fn for_user(mut self, user: &'a User) -> Self {
69self.user = Some(user);
70self
71}
7273/// Get the user filter
74#[must_use]
75pub fn user(&self) -> Option<&'a User> {
76self.user
77 }
7879/// Set the browser session filter
80#[must_use]
81pub fn for_browser_session(mut self, browser_session: &'a BrowserSession) -> Self {
82self.browser_session = Some(browser_session);
83self
84}
8586/// Get the browser session filter
87#[must_use]
88pub fn browser_session(&self) -> Option<&'a BrowserSession> {
89self.browser_session
90 }
9192/// Set the device ID filter
93#[must_use]
94pub fn for_device(mut self, device_id: &'a Device) -> Self {
95self.device_id = Some(device_id);
96self
97}
9899/// Get the device ID filter
100#[must_use]
101pub fn device(&self) -> Option<&'a Device> {
102self.device_id
103 }
104105/// Only return sessions with a last active time before the given time
106#[must_use]
107pub fn with_last_active_before(mut self, last_active_before: DateTime<Utc>) -> Self {
108self.last_active_before = Some(last_active_before);
109self
110}
111112/// Only return sessions with a last active time after the given time
113#[must_use]
114pub fn with_last_active_after(mut self, last_active_after: DateTime<Utc>) -> Self {
115self.last_active_after = Some(last_active_after);
116self
117}
118119/// Get the last active before filter
120 ///
121 /// Returns [`None`] if no client filter was set
122#[must_use]
123pub fn last_active_before(&self) -> Option<DateTime<Utc>> {
124self.last_active_before
125 }
126127/// Get the last active after filter
128 ///
129 /// Returns [`None`] if no client filter was set
130#[must_use]
131pub fn last_active_after(&self) -> Option<DateTime<Utc>> {
132self.last_active_after
133 }
134135/// Only return active compatibility sessions
136#[must_use]
137pub fn active_only(mut self) -> Self {
138self.state = Some(AppSessionState::Active);
139self
140}
141142/// Only return finished compatibility sessions
143#[must_use]
144pub fn finished_only(mut self) -> Self {
145self.state = Some(AppSessionState::Finished);
146self
147}
148149/// Get the state filter
150#[must_use]
151pub fn state(&self) -> Option<AppSessionState> {
152self.state
153 }
154}
155156/// A [`AppSessionRepository`] helps interacting with both [`CompatSession`] and
157/// OAuth 2.0 [`Session`] at the same time saved in the storage backend
158#[async_trait]
159pub trait AppSessionRepository: Send + Sync {
160/// The error type returned by the repository
161type Error;
162163/// List [`AppSession`] with the given filter and pagination
164 ///
165 /// Returns a page of [`AppSession`] matching the given filter
166 ///
167 /// # Parameters
168 ///
169 /// * `filter`: The filter to apply
170 /// * `pagination`: The pagination parameters
171 ///
172 /// # Errors
173 ///
174 /// Returns [`Self::Error`] if the underlying repository fails
175async fn list(
176&mut self,
177 filter: AppSessionFilter<'_>,
178 pagination: Pagination,
179 ) -> Result<Page<AppSession>, Self::Error>;
180181/// Count the number of [`AppSession`] with the given filter
182 ///
183 /// # Parameters
184 ///
185 /// * `filter`: The filter to apply
186 ///
187 /// # Errors
188 ///
189 /// Returns [`Self::Error`] if the underlying repository fails
190async fn count(&mut self, filter: AppSessionFilter<'_>) -> Result<usize, Self::Error>;
191192/// Finishes any application sessions that are using the specified device's
193 /// ID.
194 ///
195 /// This is intended for logging in using an existing device ID (i.e.
196 /// replacing a device).
197 ///
198 /// Should be called *before* creating a new session for the device.
199async fn finish_sessions_to_replace_device(
200&mut self,
201 clock: &dyn Clock,
202 user: &User,
203 device: &Device,
204 ) -> Result<(), Self::Error>;
205}
206207repository_impl!(AppSessionRepository:
208async fn list(
209&mut self,
210 filter: AppSessionFilter<'_>,
211 pagination: Pagination,
212 ) -> Result<Page<AppSession>, Self::Error>;
213214async fn count(&mut self, filter: AppSessionFilter<'_>) -> Result<usize, Self::Error>;
215216async fn finish_sessions_to_replace_device(
217&mut self,
218 clock: &dyn Clock,
219 user: &User,
220 device: &Device,
221 ) -> Result<(), Self::Error>;
222);