tuwunel_service/registration_tokens/
mod.rs1mod data;
2
3use std::{collections::HashSet, fmt::Display, sync::Arc};
4
5use data::Data;
6pub use data::{DatabaseTokenInfo, TokenExpires};
7use futures::{Stream, StreamExt, pin_mut};
8use tuwunel_core::{
9 Err, Result, error,
10 utils::{IterStream, random_string},
11};
12
13const RANDOM_TOKEN_LENGTH: usize = 16;
14
15pub struct Service {
16 db: Data,
17 services: Arc<crate::services::OnceServices>,
18}
19
20#[derive(Debug)]
22pub struct ValidToken {
23 pub token: String,
24 pub info: TokenInfo,
25}
26
27impl Display for ValidToken {
28 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29 write!(f, "`{}` --- {}", self.token, self.info)
30 }
31}
32
33impl PartialEq<str> for ValidToken {
34 fn eq(&self, other: &str) -> bool { self.token == other }
35}
36
37#[derive(Clone, Copy, Debug)]
38pub enum TokenInfo {
39 Config,
42 Database(DatabaseTokenInfo),
44}
45
46impl Display for TokenInfo {
47 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48 match self {
49 | Self::Config => write!(f, "Token defined in config file"),
50 | Self::Database(info) => info.fmt(f),
51 }
52 }
53}
54
55impl crate::Service for Service {
56 fn build(args: &crate::Args<'_>) -> Result<Arc<Self>> {
57 Ok(Arc::new(Self {
58 db: Data::new(args.db),
59 services: args.services.clone(),
60 }))
61 }
62
63 fn name(&self) -> &str { crate::service::make_name(std::module_path!()) }
64}
65
66impl Service {
67 pub async fn create_token(
71 &self,
72 token: Option<&str>,
73 length: Option<usize>,
74 expires: TokenExpires,
75 ) -> Result<(String, DatabaseTokenInfo)> {
76 let token = token.map(ToOwned::to_owned).unwrap_or_else(|| {
77 let length = length.unwrap_or(RANDOM_TOKEN_LENGTH);
78
79 random_string(length)
80 });
81
82 let info = self.db.save_token(&token, expires).await?;
83
84 Ok((token, info))
85 }
86
87 pub async fn get_token_info(&self, token: &str) -> Result<TokenInfo> {
89 if self.get_config_tokens().await.contains(token) {
90 return Ok(TokenInfo::Config);
91 }
92
93 self.db
94 .get_token_info(token)
95 .await
96 .map(TokenInfo::Database)
97 }
98
99 pub async fn update_token(
102 &self,
103 token: &str,
104 expires: TokenExpires,
105 ) -> Result<DatabaseTokenInfo> {
106 if self.get_config_tokens().await.contains(token) {
107 return Err!(Request(Forbidden(
108 "The token set in the config file cannot be updated"
109 )));
110 }
111
112 self.db.update_token(token, expires).await
113 }
114
115 pub async fn is_enabled(&self) -> bool {
116 let stream = self.iterate_tokens().await;
117
118 pin_mut!(stream);
119
120 stream.next().await.is_some()
121 }
122
123 pub async fn get_config_tokens(&self) -> HashSet<String> {
124 let mut tokens = HashSet::new();
125
126 if let Some(file) = &self.services.config.registration_token_file {
127 match tokio::fs::read_to_string(file).await {
128 | Err(e) => error!("Failed to read the registration token file: {e}"),
129 | Ok(text) => tokens.extend(
130 text.split_ascii_whitespace()
131 .map(ToOwned::to_owned),
132 ),
133 }
134 }
135
136 if let Some(token) = &self.services.config.registration_token {
137 tokens.insert(token.to_owned());
138 }
139
140 tokens
141 }
142
143 pub async fn is_token_valid(&self, token: &str) -> Result { self.check(token, false).await }
144
145 pub async fn try_consume(&self, token: &str) -> Result { self.check(token, true).await }
146
147 async fn check(&self, token: &str, consume: bool) -> Result {
148 if self.get_config_tokens().await.contains(token)
149 || self.db.check_token(token, consume).await
150 {
151 return Ok(());
152 }
153
154 Err!(Request(Forbidden("Registration token not valid")))
155 }
156
157 pub async fn revoke_token(&self, token: &str) -> Result {
161 if self.get_config_tokens().await.contains(token) {
162 return Err!(Request(Forbidden(
163 "The token set in the config file cannot be revoked. Edit the config file to \
164 change it."
165 )));
166 }
167
168 self.db.revoke_token(token).await
169 }
170
171 pub async fn iterate_tokens(&self) -> impl Stream<Item = ValidToken> + Send + '_ {
173 let config_tokens = self
174 .get_config_tokens()
175 .await
176 .into_iter()
177 .map(|token| ValidToken { token, info: TokenInfo::Config })
178 .stream();
179
180 let db_tokens = self
181 .db
182 .iterate_and_clean_tokens()
183 .map(|(token, info)| ValidToken {
184 token: token.to_owned(),
185 info: TokenInfo::Database(info),
186 });
187
188 config_tokens.chain(db_tokens)
189 }
190}