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