Skip to main content

mas_storage/compat/
sso_login.rs

1// Copyright 2025, 2026 Element Creations Ltd.
2// Copyright 2024, 2025 New Vector Ltd.
3// Copyright 2023, 2024 The Matrix.org Foundation C.I.C.
4//
5// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
6// Please see LICENSE files in the repository root for full details.
7
8use async_trait::async_trait;
9use mas_data_model::{BrowserSession, Clock, CompatSession, CompatSsoLogin, User};
10use rand_core::Rng;
11use ulid::Ulid;
12use url::Url;
13
14use crate::{Pagination, pagination::Page, repository_impl};
15
16#[derive(Clone, Copy, Debug, PartialEq, Eq)]
17pub enum CompatSsoLoginState {
18    Pending,
19    Fulfilled,
20    Exchanged,
21}
22
23impl CompatSsoLoginState {
24    /// Returns [`true`] if we're looking for pending SSO logins
25    #[must_use]
26    pub fn is_pending(self) -> bool {
27        matches!(self, Self::Pending)
28    }
29
30    /// Returns [`true`] if we're looking for fulfilled SSO logins
31    #[must_use]
32    pub fn is_fulfilled(self) -> bool {
33        matches!(self, Self::Fulfilled)
34    }
35
36    /// Returns [`true`] if we're looking for exchanged SSO logins
37    #[must_use]
38    pub fn is_exchanged(self) -> bool {
39        matches!(self, Self::Exchanged)
40    }
41}
42
43/// Filter parameters for listing compat SSO logins
44#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
45pub struct CompatSsoLoginFilter<'a> {
46    user: Option<&'a User>,
47    state: Option<CompatSsoLoginState>,
48}
49
50impl<'a> CompatSsoLoginFilter<'a> {
51    /// Create a new empty filter
52    #[must_use]
53    pub fn new() -> Self {
54        Self::default()
55    }
56
57    /// Set the user who owns the SSO logins sessions
58    #[must_use]
59    pub fn for_user(mut self, user: &'a User) -> Self {
60        self.user = Some(user);
61        self
62    }
63
64    /// Get the user filter
65    #[must_use]
66    pub fn user(&self) -> Option<&User> {
67        self.user
68    }
69
70    /// Only return pending SSO logins
71    #[must_use]
72    pub fn pending_only(mut self) -> Self {
73        self.state = Some(CompatSsoLoginState::Pending);
74        self
75    }
76
77    /// Only return fulfilled SSO logins
78    #[must_use]
79    pub fn fulfilled_only(mut self) -> Self {
80        self.state = Some(CompatSsoLoginState::Fulfilled);
81        self
82    }
83
84    /// Only return exchanged SSO logins
85    #[must_use]
86    pub fn exchanged_only(mut self) -> Self {
87        self.state = Some(CompatSsoLoginState::Exchanged);
88        self
89    }
90
91    /// Get the state filter
92    #[must_use]
93    pub fn state(&self) -> Option<CompatSsoLoginState> {
94        self.state
95    }
96}
97
98/// A [`CompatSsoLoginRepository`] helps interacting with
99/// [`CompatSsoLoginRepository`] saved in the storage backend
100#[async_trait]
101pub trait CompatSsoLoginRepository: Send + Sync {
102    /// The error type returned by the repository
103    type Error;
104
105    /// Lookup a compat SSO login by its ID
106    ///
107    /// Returns the compat SSO login if it exists, `None` otherwise
108    ///
109    /// # Parameters
110    ///
111    /// * `id`: The ID of the compat SSO login to lookup
112    ///
113    /// # Errors
114    ///
115    /// Returns [`Self::Error`] if the underlying repository fails
116    async fn lookup(&mut self, id: Ulid) -> Result<Option<CompatSsoLogin>, Self::Error>;
117
118    /// Find a compat SSO login by its session
119    ///
120    /// Returns the compat SSO login if it exists, `None` otherwise
121    ///
122    /// # Parameters
123    ///
124    /// * `session`: The session of the compat SSO login to lookup
125    ///
126    /// # Errors
127    ///
128    /// Returns [`Self::Error`] if the underlying repository fails
129    async fn find_for_session(
130        &mut self,
131        session: &CompatSession,
132    ) -> Result<Option<CompatSsoLogin>, Self::Error>;
133
134    /// Find a compat SSO login by its login token
135    ///
136    /// Returns the compat SSO login if found, `None` otherwise
137    ///
138    /// # Parameters
139    ///
140    /// * `login_token`: The login token of the compat SSO login to lookup
141    ///
142    /// # Errors
143    ///
144    /// Returns [`Self::Error`] if the underlying repository fails
145    async fn find_by_token(
146        &mut self,
147        login_token: &str,
148    ) -> Result<Option<CompatSsoLogin>, Self::Error>;
149
150    /// Start a new compat SSO login token
151    ///
152    /// Returns the newly created compat SSO login
153    ///
154    /// # Parameters
155    ///
156    /// * `rng`: The random number generator to use
157    /// * `clock`: The clock used to generate the timestamps
158    /// * `login_token`: The login token given to the client
159    /// * `redirect_uri`: The redirect URI given by the client
160    ///
161    /// # Errors
162    ///
163    /// Returns [`Self::Error`] if the underlying repository fails
164    async fn add(
165        &mut self,
166        rng: &mut (dyn Rng + Send),
167        clock: &dyn Clock,
168        login_token: String,
169        redirect_uri: Url,
170    ) -> Result<CompatSsoLogin, Self::Error>;
171
172    /// Fulfill a compat SSO login by providing a browser session
173    ///
174    /// Returns the fulfilled compat SSO login
175    ///
176    /// # Parameters
177    ///
178    /// * `clock`: The clock used to generate the timestamps
179    /// * `compat_sso_login`: The compat SSO login to fulfill
180    /// * `browser_session`: The browser session to associate with the compat
181    ///   SSO login
182    ///
183    /// # Errors
184    ///
185    /// Returns [`Self::Error`] if the underlying repository fails
186    async fn fulfill(
187        &mut self,
188        clock: &dyn Clock,
189        compat_sso_login: CompatSsoLogin,
190        browser_session: &BrowserSession,
191    ) -> Result<CompatSsoLogin, Self::Error>;
192
193    /// Mark a compat SSO login as exchanged
194    ///
195    /// Returns the exchanged compat SSO login
196    ///
197    /// # Parameters
198    ///
199    /// * `clock`: The clock used to generate the timestamps
200    /// * `compat_sso_login`: The compat SSO login to mark as exchanged
201    /// * `compat_session`: The compat session created as a result of the
202    ///   exchange
203    ///
204    /// # Errors
205    ///
206    /// Returns [`Self::Error`] if the underlying repository fails
207    async fn exchange(
208        &mut self,
209        clock: &dyn Clock,
210        compat_sso_login: CompatSsoLogin,
211        compat_session: &CompatSession,
212    ) -> Result<CompatSsoLogin, Self::Error>;
213
214    /// List [`CompatSsoLogin`] with the given filter and pagination
215    ///
216    /// Returns a page of compat SSO logins
217    ///
218    /// # Parameters
219    ///
220    /// * `filter`: The filter to apply
221    /// * `pagination`: The pagination parameters
222    ///
223    /// # Errors
224    ///
225    /// Returns [`Self::Error`] if the underlying repository fails
226    async fn list(
227        &mut self,
228        filter: CompatSsoLoginFilter<'_>,
229        pagination: Pagination,
230    ) -> Result<Page<CompatSsoLogin>, Self::Error>;
231
232    /// Count the number of [`CompatSsoLogin`] with the given filter
233    ///
234    /// # Parameters
235    ///
236    /// * `filter`: The filter to apply
237    ///
238    /// # Errors
239    ///
240    /// Returns [`Self::Error`] if the underlying repository fails
241    async fn count(&mut self, filter: CompatSsoLoginFilter<'_>) -> Result<usize, Self::Error>;
242}
243
244repository_impl!(CompatSsoLoginRepository:
245    async fn lookup(&mut self, id: Ulid) -> Result<Option<CompatSsoLogin>, Self::Error>;
246
247    async fn find_for_session(
248        &mut self,
249        session: &CompatSession,
250    ) -> Result<Option<CompatSsoLogin>, Self::Error>;
251
252    async fn find_by_token(
253        &mut self,
254        login_token: &str,
255    ) -> Result<Option<CompatSsoLogin>, Self::Error>;
256
257    async fn add(
258        &mut self,
259        rng: &mut (dyn Rng + Send),
260        clock: &dyn Clock,
261        login_token: String,
262        redirect_uri: Url,
263    ) -> Result<CompatSsoLogin, Self::Error>;
264
265    async fn fulfill(
266        &mut self,
267        clock: &dyn Clock,
268        compat_sso_login: CompatSsoLogin,
269        browser_session: &BrowserSession,
270    ) -> Result<CompatSsoLogin, Self::Error>;
271
272    async fn exchange(
273        &mut self,
274        clock: &dyn Clock,
275        compat_sso_login: CompatSsoLogin,
276        compat_session: &CompatSession,
277    ) -> Result<CompatSsoLogin, Self::Error>;
278
279    async fn list(
280        &mut self,
281        filter: CompatSsoLoginFilter<'_>,
282        pagination: Pagination,
283    ) -> Result<Page<CompatSsoLogin>, Self::Error>;
284
285    async fn count(&mut self, filter: CompatSsoLoginFilter<'_>) -> Result<usize, Self::Error>;
286);