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