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