torrust_tracker/core/
auth.rs1use std::panic::Location;
40use std::str::FromStr;
41use std::sync::Arc;
42use std::time::Duration;
43
44use derive_more::Display;
45use rand::distributions::Alphanumeric;
46use rand::{thread_rng, Rng};
47use serde::{Deserialize, Serialize};
48use thiserror::Error;
49use torrust_tracker_clock::clock::Time;
50use torrust_tracker_clock::conv::convert_from_timestamp_to_datetime_utc;
51use torrust_tracker_located_error::{DynError, LocatedError};
52use torrust_tracker_primitives::DurationSinceUnixEpoch;
53
54use crate::shared::bit_torrent::common::AUTH_KEY_LENGTH;
55use crate::CurrentClock;
56
57#[must_use]
59pub fn generate_permanent_key() -> PeerKey {
60 generate_key(None)
61}
62
63#[must_use]
75pub fn generate_key(lifetime: Option<Duration>) -> PeerKey {
76 let random_id: String = thread_rng()
77 .sample_iter(&Alphanumeric)
78 .take(AUTH_KEY_LENGTH)
79 .map(char::from)
80 .collect();
81
82 if let Some(lifetime) = lifetime {
83 tracing::debug!("Generated key: {}, valid for: {:?} seconds", random_id, lifetime);
84
85 PeerKey {
86 key: random_id.parse::<Key>().unwrap(),
87 valid_until: Some(CurrentClock::now_add(&lifetime).unwrap()),
88 }
89 } else {
90 tracing::debug!("Generated key: {}, permanent", random_id);
91
92 PeerKey {
93 key: random_id.parse::<Key>().unwrap(),
94 valid_until: None,
95 }
96 }
97}
98
99pub fn verify_key_expiration(auth_key: &PeerKey) -> Result<(), Error> {
109 let current_time: DurationSinceUnixEpoch = CurrentClock::now();
110
111 match auth_key.valid_until {
112 Some(valid_until) => {
113 if valid_until < current_time {
114 Err(Error::KeyExpired {
115 location: Location::caller(),
116 })
117 } else {
118 Ok(())
119 }
120 }
121 None => Ok(()), }
123}
124
125#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
128pub struct PeerKey {
129 pub key: Key,
131
132 pub valid_until: Option<DurationSinceUnixEpoch>,
135}
136
137impl std::fmt::Display for PeerKey {
138 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
139 match self.expiry_time() {
140 Some(expire_time) => write!(f, "key: `{}`, valid until `{}`", self.key, expire_time),
141 None => write!(f, "key: `{}`, permanent", self.key),
142 }
143 }
144}
145
146impl PeerKey {
147 #[must_use]
148 pub fn key(&self) -> Key {
149 self.key.clone()
150 }
151
152 #[must_use]
161 pub fn expiry_time(&self) -> Option<chrono::DateTime<chrono::Utc>> {
162 self.valid_until.map(convert_from_timestamp_to_datetime_utc)
163 }
164}
165
166#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone, Display, Hash)]
172pub struct Key(String);
173
174impl Key {
175 pub fn new(value: &str) -> Result<Self, ParseKeyError> {
180 if value.len() != AUTH_KEY_LENGTH {
181 return Err(ParseKeyError::InvalidKeyLength);
182 }
183
184 if !value.chars().all(|c| c.is_ascii_alphanumeric()) {
185 return Err(ParseKeyError::InvalidChars);
186 }
187
188 Ok(Self(value.to_owned()))
189 }
190
191 #[must_use]
192 pub fn value(&self) -> &str {
193 &self.0
194 }
195}
196
197#[derive(Debug, Error)]
213pub enum ParseKeyError {
214 #[error("Invalid key length. Key must be have 32 chars")]
215 InvalidKeyLength,
216 #[error("Invalid chars for key. Key can only alphanumeric chars (0-9, a-z, A-Z)")]
217 InvalidChars,
218}
219
220impl FromStr for Key {
221 type Err = ParseKeyError;
222
223 fn from_str(s: &str) -> Result<Self, Self::Err> {
224 Key::new(s)?;
225 Ok(Self(s.to_string()))
226 }
227}
228
229#[derive(Debug, Error)]
232#[allow(dead_code)]
233pub enum Error {
234 #[error("Key could not be verified: {source}")]
235 KeyVerificationError {
236 source: LocatedError<'static, dyn std::error::Error + Send + Sync>,
237 },
238 #[error("Failed to read key: {key}, {location}")]
239 UnableToReadKey {
240 location: &'static Location<'static>,
241 key: Box<Key>,
242 },
243 #[error("Key has expired, {location}")]
244 KeyExpired { location: &'static Location<'static> },
245}
246
247impl From<r2d2_sqlite::rusqlite::Error> for Error {
248 fn from(e: r2d2_sqlite::rusqlite::Error) -> Self {
249 Error::KeyVerificationError {
250 source: (Arc::new(e) as DynError).into(),
251 }
252 }
253}
254
255#[cfg(test)]
256mod tests {
257
258 mod key {
259 use std::str::FromStr;
260
261 use crate::core::auth::Key;
262
263 #[test]
264 fn should_be_parsed_from_an_string() {
265 let key_string = "YZSl4lMZupRuOpSRC3krIKR5BPB14nrJ";
266 let key = Key::from_str(key_string);
267
268 assert!(key.is_ok());
269 assert_eq!(key.unwrap().to_string(), key_string);
270 }
271
272 #[test]
273 fn length_should_be_32() {
274 let key = Key::new("");
275 assert!(key.is_err());
276
277 let string_longer_than_32 = "012345678901234567890123456789012"; let key = Key::new(string_longer_than_32);
279 assert!(key.is_err());
280 }
281
282 #[test]
283 fn should_only_include_alphanumeric_chars() {
284 let key = Key::new("%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%");
285 assert!(key.is_err());
286 }
287 }
288
289 mod expiring_auth_key {
290 use std::str::FromStr;
291 use std::time::Duration;
292
293 use torrust_tracker_clock::clock;
294 use torrust_tracker_clock::clock::stopped::Stopped as _;
295
296 use crate::core::auth;
297
298 #[test]
299 fn should_be_parsed_from_an_string() {
300 let key_string = "YZSl4lMZupRuOpSRC3krIKR5BPB14nrJ";
301 let auth_key = auth::Key::from_str(key_string);
302
303 assert!(auth_key.is_ok());
304 assert_eq!(auth_key.unwrap().to_string(), key_string);
305 }
306
307 #[test]
308 fn should_be_displayed() {
309 clock::Stopped::local_set_to_unix_epoch();
311
312 let expiring_key = auth::generate_key(Some(Duration::from_secs(0)));
313
314 assert_eq!(
315 expiring_key.to_string(),
316 format!("key: `{}`, valid until `1970-01-01 00:00:00 UTC`", expiring_key.key) );
318 }
319
320 #[test]
321 fn should_be_generated_with_a_expiration_time() {
322 let expiring_key = auth::generate_key(Some(Duration::new(9999, 0)));
323
324 assert!(auth::verify_key_expiration(&expiring_key).is_ok());
325 }
326
327 #[test]
328 fn should_be_generate_and_verified() {
329 clock::Stopped::local_set_to_system_time_now();
331
332 let expiring_key = auth::generate_key(Some(Duration::from_secs(19)));
334
335 clock::Stopped::local_add(&Duration::from_secs(10)).unwrap();
337
338 assert!(auth::verify_key_expiration(&expiring_key).is_ok());
339
340 clock::Stopped::local_add(&Duration::from_secs(10)).unwrap();
342
343 assert!(auth::verify_key_expiration(&expiring_key).is_err());
344 }
345 }
346}