mas_storage/compat/
sso_login.rs

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