mas_storage/oauth2/refresh_token.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::{AccessToken, Clock, RefreshToken, Session};
10use rand_core::RngCore;
11use ulid::Ulid;
12
13use crate::repository_impl;
14
15/// An [`OAuth2RefreshTokenRepository`] helps interacting with [`RefreshToken`]
16/// saved in the storage backend
17#[async_trait]
18pub trait OAuth2RefreshTokenRepository: Send + Sync {
19 /// The error type returned by the repository
20 type Error;
21
22 /// Lookup a refresh token by its ID
23 ///
24 /// Returns `None` if no [`RefreshToken`] was found
25 ///
26 /// # Parameters
27 ///
28 /// * `id`: The ID of the [`RefreshToken`] to lookup
29 ///
30 /// # Errors
31 ///
32 /// Returns [`Self::Error`] if the underlying repository fails
33 async fn lookup(&mut self, id: Ulid) -> Result<Option<RefreshToken>, Self::Error>;
34
35 /// Find a refresh token by its token
36 ///
37 /// Returns `None` if no [`RefreshToken`] was found
38 ///
39 /// # Parameters
40 ///
41 /// * `token`: The token of the [`RefreshToken`] to lookup
42 ///
43 /// # Errors
44 ///
45 /// Returns [`Self::Error`] if the underlying repository fails
46 async fn find_by_token(
47 &mut self,
48 refresh_token: &str,
49 ) -> Result<Option<RefreshToken>, Self::Error>;
50
51 /// Add a new refresh token to the database
52 ///
53 /// Returns the newly created [`RefreshToken`]
54 ///
55 /// # Parameters
56 ///
57 /// * `rng`: The random number generator to use
58 /// * `clock`: The clock used to generate timestamps
59 /// * `session`: The [`Session`] in which to create the [`RefreshToken`]
60 /// * `access_token`: The [`AccessToken`] created alongside this
61 /// [`RefreshToken`]
62 /// * `refresh_token`: The refresh token to store
63 ///
64 /// # Errors
65 ///
66 /// Returns [`Self::Error`] if the underlying repository fails
67 async fn add(
68 &mut self,
69 rng: &mut (dyn RngCore + Send),
70 clock: &dyn Clock,
71 session: &Session,
72 access_token: &AccessToken,
73 refresh_token: String,
74 ) -> Result<RefreshToken, Self::Error>;
75
76 /// Consume a refresh token
77 ///
78 /// Returns the updated [`RefreshToken`]
79 ///
80 /// # Parameters
81 ///
82 /// * `clock`: The clock used to generate timestamps
83 /// * `refresh_token`: The [`RefreshToken`] to consume
84 /// * `replaced_by`: The [`RefreshToken`] which replaced this one
85 ///
86 /// # Errors
87 ///
88 /// Returns [`Self::Error`] if the underlying repository fails, or if the
89 /// token was already consumed or revoked
90 async fn consume(
91 &mut self,
92 clock: &dyn Clock,
93 refresh_token: RefreshToken,
94 replaced_by: &RefreshToken,
95 ) -> Result<RefreshToken, Self::Error>;
96
97 /// Revoke a refresh token
98 ///
99 /// Returns the updated [`RefreshToken`]
100 ///
101 /// # Parameters
102 ///
103 /// * `clock`: The clock used to generate timestamps
104 /// * `refresh_token`: The [`RefreshToken`] to revoke
105 ///
106 /// # Errors
107 ///
108 /// Returns [`Self::Error`] if the underlying repository fails, or if the
109 /// token was already revoked or consumed
110 async fn revoke(
111 &mut self,
112 clock: &dyn Clock,
113 refresh_token: RefreshToken,
114 ) -> Result<RefreshToken, Self::Error>;
115
116 /// Cleanup revoked refresh tokens that were revoked before a certain time
117 ///
118 /// Returns the number of deleted tokens and the last `revoked_at` timestamp
119 /// processed
120 ///
121 /// # Parameters
122 ///
123 /// * `since`: An optional timestamp to start from
124 /// * `until`: The timestamp before which to delete tokens
125 /// * `limit`: The maximum number of tokens to delete
126 ///
127 /// # Errors
128 ///
129 /// Returns [`Self::Error`] if the underlying repository fails
130 async fn cleanup_revoked(
131 &mut self,
132 since: Option<chrono::DateTime<chrono::Utc>>,
133 until: chrono::DateTime<chrono::Utc>,
134 limit: usize,
135 ) -> Result<(usize, Option<chrono::DateTime<chrono::Utc>>), Self::Error>;
136
137 /// Cleanup consumed refresh tokens that were consumed before a certain time
138 ///
139 /// A token is considered as fully consumed only if both the `consumed_at`
140 /// column is set and the next refresh token in the chain also has its
141 /// `consumed_at` set.
142 ///
143 /// Returns the number of deleted tokens and the last `consumed_at`
144 /// timestamp processed
145 ///
146 /// # Parameters
147 ///
148 /// * `since`: An optional timestamp to start from
149 /// * `until`: The timestamp before which to delete tokens
150 /// * `limit`: The maximum number of tokens to delete
151 ///
152 /// # Errors
153 ///
154 /// Returns [`Self::Error`] if the underlying repository fails
155 async fn cleanup_consumed(
156 &mut self,
157 since: Option<chrono::DateTime<chrono::Utc>>,
158 until: chrono::DateTime<chrono::Utc>,
159 limit: usize,
160 ) -> Result<(usize, Option<chrono::DateTime<chrono::Utc>>), Self::Error>;
161}
162
163repository_impl!(OAuth2RefreshTokenRepository:
164 async fn lookup(&mut self, id: Ulid) -> Result<Option<RefreshToken>, Self::Error>;
165
166 async fn find_by_token(
167 &mut self,
168 refresh_token: &str,
169 ) -> Result<Option<RefreshToken>, Self::Error>;
170
171 async fn add(
172 &mut self,
173 rng: &mut (dyn RngCore + Send),
174 clock: &dyn Clock,
175 session: &Session,
176 access_token: &AccessToken,
177 refresh_token: String,
178 ) -> Result<RefreshToken, Self::Error>;
179
180 async fn consume(
181 &mut self,
182 clock: &dyn Clock,
183 refresh_token: RefreshToken,
184 replaced_by: &RefreshToken,
185 ) -> Result<RefreshToken, Self::Error>;
186
187 async fn revoke(
188 &mut self,
189 clock: &dyn Clock,
190 refresh_token: RefreshToken,
191 ) -> Result<RefreshToken, Self::Error>;
192
193 async fn cleanup_revoked(
194 &mut self,
195 since: Option<chrono::DateTime<chrono::Utc>>,
196 until: chrono::DateTime<chrono::Utc>,
197 limit: usize,
198 ) -> Result<(usize, Option<chrono::DateTime<chrono::Utc>>), Self::Error>;
199
200 async fn cleanup_consumed(
201 &mut self,
202 since: Option<chrono::DateTime<chrono::Utc>>,
203 until: chrono::DateTime<chrono::Utc>,
204 limit: usize,
205 ) -> Result<(usize, Option<chrono::DateTime<chrono::Utc>>), Self::Error>;
206);