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