Skip to main content

mas_storage/oauth2/
authorization_grant.rs

1// Copyright 2025, 2026 Element Creations Ltd.
2// Copyright 2024, 2025 New Vector Ltd.
3// Copyright 2021-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::collections::BTreeMap;
9
10use async_trait::async_trait;
11use mas_data_model::{
12    AuthorizationCode, AuthorizationGrant, BrowserSession, Client, Clock, Session,
13};
14use oauth2_types::{requests::ResponseMode, scope::Scope};
15use rand_core::RngCore;
16use ulid::Ulid;
17use url::Url;
18
19use crate::repository_impl;
20
21/// An [`OAuth2AuthorizationGrantRepository`] helps interacting with
22/// [`AuthorizationGrant`] saved in the storage backend
23#[async_trait]
24pub trait OAuth2AuthorizationGrantRepository: Send + Sync {
25    /// The error type returned by the repository
26    type Error;
27
28    /// Create a new authorization grant
29    ///
30    /// Returns the newly created authorization grant
31    ///
32    /// # Parameters
33    ///
34    /// * `rng`: A random number generator
35    /// * `clock`: The clock used to generate timestamps
36    /// * `client`: The client that requested the authorization grant
37    /// * `redirect_uri`: The redirect URI the client requested
38    /// * `scope`: The scope the client requested
39    /// * `code`: The authorization code used by this grant, if the `code`
40    ///   `response_type` was requested
41    /// * `state`: The state the client sent, if set
42    /// * `nonce`: The nonce the client sent, if set
43    /// * `response_mode`: The response mode the client requested
44    /// * `response_type_id_token`: Whether the `id_token` `response_type` was
45    ///   requested
46    /// * `login_hint`: The `login_hint` the client sent, if set
47    /// * `locale`: The locale the detected when the user asked for the
48    ///   authorization grant
49    /// * `raw_parameters`: The raw query parameters of the authorization
50    ///   request, used to template the parameters forwarded to the upstream
51    ///   provider
52    ///
53    /// # Errors
54    ///
55    /// Returns [`Self::Error`] if the underlying repository fails
56    #[expect(clippy::too_many_arguments)]
57    async fn add(
58        &mut self,
59        rng: &mut (dyn RngCore + Send),
60        clock: &dyn Clock,
61        client: &Client,
62        redirect_uri: Url,
63        scope: Scope,
64        code: Option<AuthorizationCode>,
65        state: Option<String>,
66        nonce: Option<String>,
67        response_mode: ResponseMode,
68        response_type_id_token: bool,
69        login_hint: Option<String>,
70        locale: Option<String>,
71        raw_parameters: BTreeMap<String, String>,
72    ) -> Result<AuthorizationGrant, Self::Error>;
73
74    /// Lookup an authorization grant by its ID
75    ///
76    /// Returns the authorization grant if found, `None` otherwise
77    ///
78    /// # Parameters
79    ///
80    /// * `id`: The ID of the authorization grant to lookup
81    ///
82    /// # Errors
83    ///
84    /// Returns [`Self::Error`] if the underlying repository fails
85    async fn lookup(&mut self, id: Ulid) -> Result<Option<AuthorizationGrant>, Self::Error>;
86
87    /// Find an authorization grant by its code
88    ///
89    /// Returns the authorization grant if found, `None` otherwise
90    ///
91    /// # Parameters
92    ///
93    /// * `code`: The code of the authorization grant to lookup
94    ///
95    /// # Errors
96    ///
97    /// Returns [`Self::Error`] if the underlying repository fails
98    async fn find_by_code(&mut self, code: &str)
99    -> Result<Option<AuthorizationGrant>, Self::Error>;
100
101    /// Fulfill an authorization grant, by giving the [`BrowserSession`] that
102    /// approved it
103    ///
104    /// Returns the updated authorization grant
105    ///
106    /// # Parameters
107    ///
108    /// * `clock`: The clock used to generate timestamps
109    /// * `browser_session`: The browser session that approved this grant
110    /// * `authorization_grant`: The authorization grant to fulfill
111    ///
112    /// # Errors
113    ///
114    /// Returns [`Self::Error`] if the underlying repository fails
115    async fn fulfill(
116        &mut self,
117        clock: &dyn Clock,
118        browser_session: &BrowserSession,
119        authorization_grant: AuthorizationGrant,
120    ) -> Result<AuthorizationGrant, Self::Error>;
121
122    /// Mark an authorization grant as exchanged
123    ///
124    /// Returns the updated authorization grant
125    ///
126    /// # Parameters
127    ///
128    /// * `clock`: The clock used to generate timestamps
129    /// * `authorization_grant`: The authorization grant to mark as exchanged
130    /// * `session`: The `OAuth2` session created for this grant
131    ///
132    /// # Errors
133    ///
134    /// Returns [`Self::Error`] if the underlying repository fails
135    async fn exchange(
136        &mut self,
137        clock: &dyn Clock,
138        authorization_grant: AuthorizationGrant,
139        session: &Session,
140    ) -> Result<AuthorizationGrant, Self::Error>;
141
142    /// Cleanup old authorization grants
143    ///
144    /// This will delete authorization grants with IDs up to and including
145    /// `until`. Uses ULID cursor-based pagination for efficiency.
146    ///
147    /// Returns the number of grants deleted and the cursor for the next batch
148    ///
149    /// # Parameters
150    ///
151    /// * `since`: The cursor to start from (exclusive), or `None` to start from
152    ///   the beginning
153    /// * `until`: The maximum ULID to delete (inclusive upper bound)
154    /// * `limit`: The maximum number of grants to delete in this batch
155    ///
156    /// # Errors
157    ///
158    /// Returns [`Self::Error`] if the underlying repository fails
159    async fn cleanup(
160        &mut self,
161        since: Option<Ulid>,
162        until: Ulid,
163        limit: usize,
164    ) -> Result<(usize, Option<Ulid>), Self::Error>;
165}
166
167repository_impl!(OAuth2AuthorizationGrantRepository:
168    async fn add(
169        &mut self,
170        rng: &mut (dyn RngCore + Send),
171        clock: &dyn Clock,
172        client: &Client,
173        redirect_uri: Url,
174        scope: Scope,
175        code: Option<AuthorizationCode>,
176        state: Option<String>,
177        nonce: Option<String>,
178        response_mode: ResponseMode,
179        response_type_id_token: bool,
180        login_hint: Option<String>,
181        locale: Option<String>,
182        raw_parameters: BTreeMap<String, String>,
183    ) -> Result<AuthorizationGrant, Self::Error>;
184
185    async fn lookup(&mut self, id: Ulid) -> Result<Option<AuthorizationGrant>, Self::Error>;
186
187    async fn find_by_code(&mut self, code: &str)
188        -> Result<Option<AuthorizationGrant>, Self::Error>;
189
190    async fn fulfill(
191        &mut self,
192        clock: &dyn Clock,
193        browser_session: &BrowserSession,
194        authorization_grant: AuthorizationGrant,
195    ) -> Result<AuthorizationGrant, Self::Error>;
196
197    async fn exchange(
198        &mut self,
199        clock: &dyn Clock,
200        authorization_grant: AuthorizationGrant,
201        session: &Session,
202    ) -> Result<AuthorizationGrant, Self::Error>;
203
204    async fn cleanup(
205        &mut self,
206        since: Option<Ulid>,
207        until: Ulid,
208        limit: usize,
209    ) -> Result<(usize, Option<Ulid>), Self::Error>;
210);