diff --git a/src/core/error.rs b/src/core/error.rs index a826de349..d89b030c4 100644 --- a/src/core/error.rs +++ b/src/core/error.rs @@ -11,6 +11,9 @@ use std::panic::Location; use torrust_tracker_located_error::LocatedError; use torrust_tracker_primitives::info_hash::InfoHash; +use super::auth::ParseKeyError; +use super::databases; + /// Authentication or authorization error returned by the core `Tracker` #[derive(thiserror::Error, Debug, Clone)] pub enum Error { @@ -20,6 +23,7 @@ pub enum Error { key: super::auth::Key, source: LocatedError<'static, dyn std::error::Error + Send + Sync>, }, + #[error("The peer is not authenticated, {location}")] PeerNotAuthenticated { location: &'static Location<'static> }, @@ -30,3 +34,22 @@ pub enum Error { location: &'static Location<'static>, }, } + +/// Errors related to peers keys. +#[allow(clippy::module_name_repetitions)] +#[derive(thiserror::Error, Debug, Clone)] +pub enum PeerKeyError { + #[error("Invalid peer key duration: {seconds_valid:?}, is not valid")] + DurationOverflow { seconds_valid: u64 }, + + #[error("Invalid key: {key}")] + InvalidKey { + key: String, + source: LocatedError<'static, ParseKeyError>, + }, + + #[error("Can't persist key: {source}")] + DatabaseError { + source: LocatedError<'static, databases::error::Error>, + }, +} diff --git a/src/core/mod.rs b/src/core/mod.rs index 90eaa6cad..73b38349c 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -456,10 +456,12 @@ use std::time::Duration; use auth::PeerKey; use databases::driver::Driver; use derive_more::Constructor; +use error::PeerKeyError; use tokio::sync::mpsc::error::SendError; use torrust_tracker_clock::clock::Time; use torrust_tracker_configuration::v2::database; use torrust_tracker_configuration::{AnnouncePolicy, Core, TORRENT_PEERS_LIMIT}; +use torrust_tracker_located_error::Located; use torrust_tracker_primitives::info_hash::InfoHash; use torrust_tracker_primitives::swarm_metadata::SwarmMetadata; use torrust_tracker_primitives::torrent_metrics::TorrentsMetrics; @@ -472,6 +474,7 @@ use self::auth::Key; use self::error::Error; use self::torrent::Torrents; use crate::core::databases::Database; +use crate::servers::apis::v1::context::auth_key::forms::AddKeyForm; use crate::CurrentClock; /// The domain layer tracker service. @@ -793,6 +796,81 @@ impl Tracker { } } + /// Adds new peer keys to the tracker. + /// + /// Keys can be pre-generated or randomly created. They can also be permanent or expire. + /// + /// # Errors + /// + /// Will return an error if: + /// + /// - The key duration overflows the duration type maximum value. + /// - The provided pre-generated key is invalid. + /// - The key could not been persisted due to database issues. + pub async fn add_peer_key(&self, add_key_form: AddKeyForm) -> Result { + // code-review: all methods related to keys should be moved to a new independent "keys" service. + + match add_key_form.opt_key { + // Upload pre-generated key + Some(pre_existing_key) => { + if let Some(seconds_valid) = add_key_form.opt_seconds_valid { + // Expiring key + let Some(valid_until) = CurrentClock::now_add(&Duration::from_secs(seconds_valid)) else { + return Err(PeerKeyError::DurationOverflow { seconds_valid }); + }; + + let key = pre_existing_key.parse::(); + + match key { + Ok(key) => match self.add_auth_key(key, Some(valid_until)).await { + Ok(auth_key) => Ok(auth_key), + Err(err) => Err(PeerKeyError::DatabaseError { + source: Located(err).into(), + }), + }, + Err(err) => Err(PeerKeyError::InvalidKey { + key: pre_existing_key, + source: Located(err).into(), + }), + } + } else { + // Permanent key + let key = pre_existing_key.parse::(); + + match key { + Ok(key) => match self.add_permanent_auth_key(key).await { + Ok(auth_key) => Ok(auth_key), + Err(err) => Err(PeerKeyError::DatabaseError { + source: Located(err).into(), + }), + }, + Err(err) => Err(PeerKeyError::InvalidKey { + key: pre_existing_key, + source: Located(err).into(), + }), + } + } + } + // Generate a new random key + None => match add_key_form.opt_seconds_valid { + // Expiring key + Some(seconds_valid) => match self.generate_auth_key(Some(Duration::from_secs(seconds_valid))).await { + Ok(auth_key) => Ok(auth_key), + Err(err) => Err(PeerKeyError::DatabaseError { + source: Located(err).into(), + }), + }, + // Permanent key + None => match self.generate_permanent_auth_key().await { + Ok(auth_key) => Ok(auth_key), + Err(err) => Err(PeerKeyError::DatabaseError { + source: Located(err).into(), + }), + }, + }, + } + } + /// It generates a new permanent authentication key. /// /// Authentication keys are used by HTTP trackers.