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::{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 compat 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    /// * `compat_session`: The compat session to associate with the compat SSO
180    ///   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        compat_session: &CompatSession,
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    ///
201    /// # Errors
202    ///
203    /// Returns [`Self::Error`] if the underlying repository fails
204    async fn exchange(
205        &mut self,
206        clock: &dyn Clock,
207        compat_sso_login: CompatSsoLogin,
208    ) -> Result<CompatSsoLogin, Self::Error>;
209
210    /// List [`CompatSsoLogin`] with the given filter and pagination
211    ///
212    /// Returns a page of compat SSO logins
213    ///
214    /// # Parameters
215    ///
216    /// * `filter`: The filter to apply
217    /// * `pagination`: The pagination parameters
218    ///
219    /// # Errors
220    ///
221    /// Returns [`Self::Error`] if the underlying repository fails
222    async fn list(
223        &mut self,
224        filter: CompatSsoLoginFilter<'_>,
225        pagination: Pagination,
226    ) -> Result<Page<CompatSsoLogin>, Self::Error>;
227
228    /// Count the number of [`CompatSsoLogin`] with the given filter
229    ///
230    /// # Parameters
231    ///
232    /// * `filter`: The filter to apply
233    ///
234    /// # Errors
235    ///
236    /// Returns [`Self::Error`] if the underlying repository fails
237    async fn count(&mut self, filter: CompatSsoLoginFilter<'_>) -> Result<usize, Self::Error>;
238}
239
240repository_impl!(CompatSsoLoginRepository:
241    async fn lookup(&mut self, id: Ulid) -> Result<Option<CompatSsoLogin>, Self::Error>;
242
243    async fn find_for_session(
244        &mut self,
245        session: &CompatSession,
246    ) -> Result<Option<CompatSsoLogin>, Self::Error>;
247
248    async fn find_by_token(
249        &mut self,
250        login_token: &str,
251    ) -> Result<Option<CompatSsoLogin>, Self::Error>;
252
253    async fn add(
254        &mut self,
255        rng: &mut (dyn RngCore + Send),
256        clock: &dyn Clock,
257        login_token: String,
258        redirect_uri: Url,
259    ) -> Result<CompatSsoLogin, Self::Error>;
260
261    async fn fulfill(
262        &mut self,
263        clock: &dyn Clock,
264        compat_sso_login: CompatSsoLogin,
265        compat_session: &CompatSession,
266    ) -> Result<CompatSsoLogin, Self::Error>;
267
268    async fn exchange(
269        &mut self,
270        clock: &dyn Clock,
271        compat_sso_login: CompatSsoLogin,
272    ) -> Result<CompatSsoLogin, Self::Error>;
273
274    async fn list(
275        &mut self,
276        filter: CompatSsoLoginFilter<'_>,
277        pagination: Pagination,
278    ) -> Result<Page<CompatSsoLogin>, Self::Error>;
279
280    async fn count(&mut self, filter: CompatSsoLoginFilter<'_>) -> Result<usize, Self::Error>;
281);