Skip to main content

mas_storage/upstream_oauth2/
provider.rs

1// Copyright 2025, 2026 Element Creations Ltd.
2// Copyright 2024, 2025 New Vector Ltd.
3// Copyright 2022-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 std::marker::PhantomData;
9
10use async_trait::async_trait;
11use mas_data_model::{
12    Clock, UpstreamOAuthProvider, UpstreamOAuthProviderClaimsImports,
13    UpstreamOAuthProviderDiscoveryMode, UpstreamOAuthProviderOnBackchannelLogout,
14    UpstreamOAuthProviderPkceMode, UpstreamOAuthProviderResponseMode,
15    UpstreamOAuthProviderTokenAuthMethod,
16};
17use mas_iana::jose::JsonWebSignatureAlg;
18use oauth2_types::scope::Scope;
19use rand_core::Rng;
20use ulid::Ulid;
21use url::Url;
22
23use crate::{Pagination, pagination::Page, repository_impl};
24
25/// Structure which holds parameters when inserting or updating an upstream
26/// OAuth 2.0 provider
27pub struct UpstreamOAuthProviderParams {
28    /// The OIDC issuer of the provider
29    pub issuer: Option<String>,
30
31    /// A human-readable name for the provider
32    pub human_name: Option<String>,
33
34    /// A brand identifier, e.g. "apple" or "google"
35    pub brand_name: Option<String>,
36
37    /// The scope to request during the authorization flow
38    pub scope: Scope,
39
40    /// The token endpoint authentication method
41    pub token_endpoint_auth_method: UpstreamOAuthProviderTokenAuthMethod,
42
43    /// The JWT signing algorithm to use when then `client_secret_jwt` or
44    /// `private_key_jwt` authentication methods are used
45    pub token_endpoint_signing_alg: Option<JsonWebSignatureAlg>,
46
47    /// Expected signature for the JWT payload returned by the token
48    /// authentication endpoint.
49    ///
50    /// Defaults to `RS256`.
51    pub id_token_signed_response_alg: JsonWebSignatureAlg,
52
53    /// Whether to fetch the user profile from the userinfo endpoint,
54    /// or to rely on the data returned in the `id_token` from the
55    /// `token_endpoint`.
56    pub fetch_userinfo: bool,
57
58    /// Expected signature for the JWT payload returned by the userinfo
59    /// endpoint.
60    ///
61    /// If not specified, the response is expected to be an unsigned JSON
62    /// payload. Defaults to `None`.
63    pub userinfo_signed_response_alg: Option<JsonWebSignatureAlg>,
64
65    /// The client ID to use when authenticating to the upstream
66    pub client_id: String,
67
68    /// The encrypted client secret to use when authenticating to the upstream
69    pub encrypted_client_secret: Option<String>,
70
71    /// How claims should be imported from the upstream provider
72    pub claims_imports: UpstreamOAuthProviderClaimsImports,
73
74    /// The URL to use as the authorization endpoint. If `None`, the URL will be
75    /// discovered
76    pub authorization_endpoint_override: Option<Url>,
77
78    /// The URL to use as the token endpoint. If `None`, the URL will be
79    /// discovered
80    pub token_endpoint_override: Option<Url>,
81
82    /// The URL to use as the userinfo endpoint. If `None`, the URL will be
83    /// discovered
84    pub userinfo_endpoint_override: Option<Url>,
85
86    /// The URL to use when fetching JWKS. If `None`, the URL will be discovered
87    pub jwks_uri_override: Option<Url>,
88
89    /// How the provider metadata should be discovered
90    pub discovery_mode: UpstreamOAuthProviderDiscoveryMode,
91
92    /// How should PKCE be used
93    pub pkce_mode: UpstreamOAuthProviderPkceMode,
94
95    /// What response mode it should ask
96    pub response_mode: Option<UpstreamOAuthProviderResponseMode>,
97
98    /// Additional parameters to include in the authorization request
99    pub additional_authorization_parameters: Vec<(String, String)>,
100
101    /// Whether to forward the login hint to the upstream provider.
102    pub forward_login_hint: bool,
103
104    /// The position of the provider in the UI
105    pub ui_order: i32,
106
107    /// The behavior when receiving a backchannel logout notification
108    pub on_backchannel_logout: UpstreamOAuthProviderOnBackchannelLogout,
109
110    /// Whether or not to require a registration token on `OAuth2` auth
111    pub registration_token_required: bool,
112}
113
114/// Filter parameters for listing upstream OAuth 2.0 providers
115#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
116pub struct UpstreamOAuthProviderFilter<'a> {
117    /// Filter by whether the provider is enabled
118    ///
119    /// If `None`, all providers are returned
120    enabled: Option<bool>,
121
122    _lifetime: PhantomData<&'a ()>,
123}
124
125impl UpstreamOAuthProviderFilter<'_> {
126    /// Create a new [`UpstreamOAuthProviderFilter`] with default values
127    #[must_use]
128    pub fn new() -> Self {
129        Self::default()
130    }
131
132    /// Return only enabled providers
133    #[must_use]
134    pub const fn enabled_only(mut self) -> Self {
135        self.enabled = Some(true);
136        self
137    }
138
139    /// Return only disabled providers
140    #[must_use]
141    pub const fn disabled_only(mut self) -> Self {
142        self.enabled = Some(false);
143        self
144    }
145
146    /// Get the enabled filter
147    ///
148    /// Returns `None` if the filter is not set
149    #[must_use]
150    pub const fn enabled(&self) -> Option<bool> {
151        self.enabled
152    }
153}
154
155/// An [`UpstreamOAuthProviderRepository`] helps interacting with
156/// [`UpstreamOAuthProvider`] saved in the storage backend
157#[async_trait]
158pub trait UpstreamOAuthProviderRepository: Send + Sync {
159    /// The error type returned by the repository
160    type Error;
161
162    /// Lookup an upstream OAuth provider by its ID
163    ///
164    /// Returns `None` if the provider was not found
165    ///
166    /// # Parameters
167    ///
168    /// * `id`: The ID of the provider to lookup
169    ///
170    /// # Errors
171    ///
172    /// Returns [`Self::Error`] if the underlying repository fails
173    async fn lookup(&mut self, id: Ulid) -> Result<Option<UpstreamOAuthProvider>, Self::Error>;
174
175    /// Add a new upstream OAuth provider
176    ///
177    /// Returns the newly created provider
178    ///
179    /// # Parameters
180    ///
181    /// * `rng`: A random number generator
182    /// * `clock`: The clock used to generate timestamps
183    /// * `params`: The parameters of the provider to add
184    ///
185    /// # Errors
186    ///
187    /// Returns [`Self::Error`] if the underlying repository fails
188    async fn add(
189        &mut self,
190        rng: &mut (dyn Rng + Send),
191        clock: &dyn Clock,
192        params: UpstreamOAuthProviderParams,
193    ) -> Result<UpstreamOAuthProvider, Self::Error>;
194
195    /// Delete an upstream OAuth provider
196    ///
197    /// # Parameters
198    ///
199    /// * `provider`: The provider to delete
200    ///
201    /// # Errors
202    ///
203    /// Returns [`Self::Error`] if the underlying repository fails
204    async fn delete(&mut self, provider: UpstreamOAuthProvider) -> Result<(), Self::Error> {
205        self.delete_by_id(provider.id).await
206    }
207
208    /// Delete an upstream OAuth provider by its ID
209    ///
210    /// # Parameters
211    ///
212    /// * `id`: The ID of the provider to delete
213    ///
214    /// # Errors
215    ///
216    /// Returns [`Self::Error`] if the underlying repository fails
217    async fn delete_by_id(&mut self, id: Ulid) -> Result<(), Self::Error>;
218
219    /// Insert or update an upstream OAuth provider
220    ///
221    /// # Parameters
222    ///
223    /// * `clock`: The clock used to generate timestamps
224    /// * `id`: The ID of the provider to update
225    /// * `params`: The parameters of the provider to update
226    ///
227    /// # Errors
228    ///
229    /// Returns [`Self::Error`] if the underlying repository fails
230    async fn upsert(
231        &mut self,
232        clock: &dyn Clock,
233        id: Ulid,
234        params: UpstreamOAuthProviderParams,
235    ) -> Result<UpstreamOAuthProvider, Self::Error>;
236
237    /// Disable an upstream OAuth provider
238    ///
239    /// Returns the disabled provider
240    ///
241    /// # Parameters
242    ///
243    /// * `clock`: The clock used to generate timestamps
244    /// * `provider`: The provider to disable
245    ///
246    /// # Errors
247    ///
248    /// Returns [`Self::Error`] if the underlying repository fails
249    async fn disable(
250        &mut self,
251        clock: &dyn Clock,
252        provider: UpstreamOAuthProvider,
253    ) -> Result<UpstreamOAuthProvider, Self::Error>;
254
255    /// List [`UpstreamOAuthProvider`] with the given filter and pagination
256    ///
257    /// # Parameters
258    ///
259    /// * `filter`: The filter to apply
260    /// * `pagination`: The pagination parameters
261    ///
262    /// # Errors
263    ///
264    /// Returns [`Self::Error`] if the underlying repository fails
265    async fn list(
266        &mut self,
267        filter: UpstreamOAuthProviderFilter<'_>,
268        pagination: Pagination,
269    ) -> Result<Page<UpstreamOAuthProvider>, Self::Error>;
270
271    /// Count the number of [`UpstreamOAuthProvider`] with the given filter
272    ///
273    /// # Parameters
274    ///
275    /// * `filter`: The filter to apply
276    ///
277    /// # Errors
278    ///
279    /// Returns [`Self::Error`] if the underlying repository fails
280    async fn count(
281        &mut self,
282        filter: UpstreamOAuthProviderFilter<'_>,
283    ) -> Result<usize, Self::Error>;
284
285    /// Get all enabled upstream OAuth providers
286    ///
287    /// # Errors
288    ///
289    /// Returns [`Self::Error`] if the underlying repository fails
290    async fn all_enabled(&mut self) -> Result<Vec<UpstreamOAuthProvider>, Self::Error>;
291}
292
293repository_impl!(UpstreamOAuthProviderRepository:
294    async fn lookup(&mut self, id: Ulid) -> Result<Option<UpstreamOAuthProvider>, Self::Error>;
295
296    async fn add(
297        &mut self,
298        rng: &mut (dyn Rng + Send),
299        clock: &dyn Clock,
300        params: UpstreamOAuthProviderParams
301    ) -> Result<UpstreamOAuthProvider, Self::Error>;
302
303    async fn upsert(
304        &mut self,
305        clock: &dyn Clock,
306        id: Ulid,
307        params: UpstreamOAuthProviderParams
308    ) -> Result<UpstreamOAuthProvider, Self::Error>;
309
310    async fn delete(&mut self, provider: UpstreamOAuthProvider) -> Result<(), Self::Error>;
311
312    async fn delete_by_id(&mut self, id: Ulid) -> Result<(), Self::Error>;
313
314    async fn disable(
315        &mut self,
316        clock: &dyn Clock,
317        provider: UpstreamOAuthProvider
318    ) -> Result<UpstreamOAuthProvider, Self::Error>;
319
320    async fn list(
321        &mut self,
322        filter: UpstreamOAuthProviderFilter<'_>,
323        pagination: Pagination
324    ) -> Result<Page<UpstreamOAuthProvider>, Self::Error>;
325
326    async fn count(
327        &mut self,
328        filter: UpstreamOAuthProviderFilter<'_>
329    ) -> Result<usize, Self::Error>;
330
331    async fn all_enabled(&mut self) -> Result<Vec<UpstreamOAuthProvider>, Self::Error>;
332);