mas_storage/compat/
refresh_token.rs

1// Copyright 2024, 2025 New Vector Ltd.
2// Copyright 2023, 2024 The Matrix.org Foundation C.I.C.
3//
4// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
5// Please see LICENSE files in the repository root for full details.
6
7use async_trait::async_trait;
8use mas_data_model::{Clock, CompatAccessToken, CompatRefreshToken, CompatSession};
9use rand_core::RngCore;
10use ulid::Ulid;
11
12use crate::repository_impl;
13
14/// A [`CompatRefreshTokenRepository`] helps interacting with
15/// [`CompatRefreshToken`] saved in the storage backend
16#[async_trait]
17pub trait CompatRefreshTokenRepository: Send + Sync {
18    /// The error type returned by the repository
19    type Error;
20
21    /// Lookup a compat refresh token by its ID
22    ///
23    /// Returns the compat refresh token if it exists, `None` otherwise
24    ///
25    /// # Parameters
26    ///
27    /// * `id`: The ID of the compat refresh token to lookup
28    ///
29    /// # Errors
30    ///
31    /// Returns [`Self::Error`] if the underlying repository fails
32    async fn lookup(&mut self, id: Ulid) -> Result<Option<CompatRefreshToken>, Self::Error>;
33
34    /// Find a compat refresh token by its token
35    ///
36    /// Returns the compat refresh token if found, `None` otherwise
37    ///
38    /// # Parameters
39    ///
40    /// * `refresh_token`: The token of the compat refresh token to lookup
41    ///
42    /// # Errors
43    ///
44    /// Returns [`Self::Error`] if the underlying repository fails
45    async fn find_by_token(
46        &mut self,
47        refresh_token: &str,
48    ) -> Result<Option<CompatRefreshToken>, Self::Error>;
49
50    /// Add a new compat refresh token to the database
51    ///
52    /// Returns the newly created compat refresh token
53    ///
54    /// # Parameters
55    ///
56    /// * `rng`: The random number generator to use
57    /// * `clock`: The clock used to generate timestamps
58    /// * `compat_session`: The compat session associated with this refresh
59    ///   token
60    /// * `compat_access_token`: The compat access token created alongside this
61    ///   refresh token
62    /// * `token`: The token of the refresh token
63    async fn add(
64        &mut self,
65        rng: &mut (dyn RngCore + Send),
66        clock: &dyn Clock,
67        compat_session: &CompatSession,
68        compat_access_token: &CompatAccessToken,
69        token: String,
70    ) -> Result<CompatRefreshToken, Self::Error>;
71
72    /// Consume the given compat refresh token, as well as all other refresh
73    /// tokens from the same session, except for the given successor compat
74    /// refresh token.
75    ///
76    /// The given successor refresh token will thereafter be the only valid
77    /// refresh token for the session.
78    ///
79    /// # Historical context
80    ///
81    /// When using a refresh token, we must be able to mark multiple other
82    /// refresh tokens in the same session as consumed.
83    /// This is desirable because the syn2mas migration process can import
84    /// multiple refresh tokens for one device (compat session).
85    /// But once the user uses one of those, the others should no longer
86    /// be valid.
87    ///
88    /// # Parameters
89    ///
90    /// * `clock`: The clock used to generate timestamps
91    /// * `compat_refresh_token`: The compat refresh token to consume
92    ///
93    /// # Errors
94    ///
95    /// - Returns [`Self::Error`] if the underlying repository fails
96    /// - Returns an error if `compat_refresh_token` is not valid to be
97    ///   consumed.
98    /// - Returns an error if no refresh tokens would be consumed.
99    async fn consume_and_replace(
100        &mut self,
101        clock: &dyn Clock,
102        compat_refresh_token: CompatRefreshToken,
103        successor_compat_refresh_token: &CompatRefreshToken,
104    ) -> Result<CompatRefreshToken, Self::Error>;
105}
106
107repository_impl!(CompatRefreshTokenRepository:
108    async fn lookup(&mut self, id: Ulid) -> Result<Option<CompatRefreshToken>, Self::Error>;
109
110    async fn find_by_token(
111        &mut self,
112        refresh_token: &str,
113    ) -> Result<Option<CompatRefreshToken>, Self::Error>;
114
115    async fn add(
116        &mut self,
117        rng: &mut (dyn RngCore + Send),
118        clock: &dyn Clock,
119        compat_session: &CompatSession,
120        compat_access_token: &CompatAccessToken,
121        token: String,
122    ) -> Result<CompatRefreshToken, Self::Error>;
123
124    async fn consume_and_replace(
125        &mut self,
126        clock: &dyn Clock,
127        compat_refresh_token: CompatRefreshToken,
128        successor_compat_refresh_token: &CompatRefreshToken,
129    ) -> Result<CompatRefreshToken, Self::Error>;
130);