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