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);