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