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