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