tuwunel_service/registration_tokens/
data.rs1use std::{sync::Arc, time::SystemTime};
7
8use futures::Stream;
9use serde::{Deserialize, Serialize};
10use tuwunel_core::{
11 Err, Result, err,
12 utils::{
13 self,
14 stream::{ReadyExt, TryIgnore},
15 },
16};
17use tuwunel_database::{Database, Deserialized, Json, Map};
18
19pub(super) struct Data {
24 registrationtoken_info: Arc<Map>,
25}
26
27#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
33pub struct DatabaseTokenInfo {
34 pub uses: u64,
36
37 pub expires: TokenExpires,
39}
40
41impl DatabaseTokenInfo {
42 pub(super) fn new(expires: TokenExpires) -> Self { Self { uses: 0, expires } }
46
47 #[must_use]
52 pub fn is_valid(&self) -> bool {
53 if let Some(max_uses) = self.expires.max_uses
54 && self.uses >= max_uses
55 {
56 return false;
57 }
58
59 if let Some(max_age) = self.expires.max_age {
60 let now = SystemTime::now();
61
62 if now > max_age {
63 return false;
64 }
65 }
66
67 true
68 }
69}
70
71impl std::fmt::Display for DatabaseTokenInfo {
72 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
73 write!(f, "Token used {} times. {}", self.uses, self.expires)?;
74
75 Ok(())
76 }
77}
78
79#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
83pub struct TokenExpires {
84 pub max_uses: Option<u64>,
86
87 pub max_age: Option<SystemTime>,
89}
90
91impl std::fmt::Display for TokenExpires {
92 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
93 let mut msgs = vec![];
94
95 if let Some(max_uses) = self.max_uses {
96 msgs.push(format!("after {max_uses} uses"));
97 }
98
99 if let Some(max_age) = self.max_age {
100 let now = SystemTime::now();
101 let expires_at = utils::time::format(max_age, "%F %T");
102
103 match max_age.duration_since(now) {
104 | Ok(duration) => {
105 let expires_in = utils::time::pretty(duration);
106 msgs.push(format!("in {expires_in} ({expires_at})"));
107 },
108 | Err(_) => {
109 write!(f, "Expired at {expires_at}")?;
110 return Ok(());
111 },
112 }
113 }
114
115 if !msgs.is_empty() {
116 write!(f, "Expires {}.", msgs.join(" or "))?;
117 } else {
118 write!(f, "Never expires.")?;
119 }
120
121 Ok(())
122 }
123}
124
125impl Data {
126 pub(super) fn new(db: &Arc<Database>) -> Self {
130 Self {
131 registrationtoken_info: db["registrationtoken_info"].clone(),
132 }
133 }
134
135 pub(super) async fn save_token(
139 &self,
140 token: &str,
141 expires: TokenExpires,
142 ) -> Result<DatabaseTokenInfo> {
143 if self
144 .registrationtoken_info
145 .exists(token)
146 .await
147 .is_err()
148 {
149 let info = DatabaseTokenInfo::new(expires);
150
151 self.registrationtoken_info
152 .raw_put(token, Json(&info));
153
154 Ok(info)
155 } else {
156 Err!(Request(InvalidParam("Registration token already exists")))
157 }
158 }
159
160 pub(super) async fn revoke_token(&self, token: &str) -> Result {
164 if self
165 .registrationtoken_info
166 .exists(token)
167 .await
168 .is_ok()
169 {
170 self.registrationtoken_info.remove(token);
171
172 Ok(())
173 } else {
174 Err!(Request(NotFound("Registration token not found")))
175 }
176 }
177
178 pub(super) async fn check_token(&self, token: &str, consume: bool) -> bool {
183 let info = self
184 .registrationtoken_info
185 .get(token)
186 .await
187 .deserialized::<DatabaseTokenInfo>()
188 .ok();
189
190 info.map(|mut info| {
191 if !info.is_valid() {
192 self.registrationtoken_info.remove(token);
193 return false;
194 }
195
196 if consume {
197 info.uses = info.uses.saturating_add(1);
198
199 if info.is_valid() {
200 self.registrationtoken_info
201 .raw_put(token, Json(info));
202 } else {
203 self.registrationtoken_info.remove(token);
204 }
205 }
206
207 true
208 })
209 .unwrap_or(false)
210 }
211
212 pub(super) async fn get_token_info(&self, token: &str) -> Result<DatabaseTokenInfo> {
217 self.registrationtoken_info
218 .get(token)
219 .await
220 .deserialized()
221 .map_err(|_| err!(Request(NotFound("Registration token not found"))))
222 }
223
224 pub(super) async fn update_token(
228 &self,
229 token: &str,
230 expires: TokenExpires,
231 ) -> Result<DatabaseTokenInfo> {
232 let current = self.get_token_info(token).await?;
233
234 let info = DatabaseTokenInfo { uses: current.uses, expires };
235
236 self.registrationtoken_info
237 .raw_put(token, Json(&info));
238
239 Ok(info)
240 }
241
242 pub(super) fn iterate_and_clean_tokens(
247 &self,
248 ) -> impl Stream<Item = (&str, DatabaseTokenInfo)> + Send + '_ {
249 self.registrationtoken_info
250 .stream()
251 .ignore_err()
252 .ready_filter_map(|(token, info): (&str, DatabaseTokenInfo)| {
253 if info.is_valid() {
254 Some((token, info))
255 } else {
256 self.registrationtoken_info.remove(token);
257 None
258 }
259 })
260 }
261}