From c5beff551e51f95e05e36a315350902d3b104153 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 31 Jul 2024 15:27:40 +0100 Subject: [PATCH] feat: [#979] permanent keys This commit adds a new feature. It allow creating permanent keys (keys that do not expire). THis is an example for making a request to the endpoint using curl: ```console curl -X POST http://localhost:1212/api/v1/keys?token=MyAccessToken \ -H "Content-Type: application/json" \ -d '{ "key": null, "seconds_valid": null }' ``` NOTICE: both the `key` and the `seconds_valid` fields can be null. - If `key` is `null` a new random key will be generated. You can use an string with a pre-generated key like `Xc1L4PbQJSFGlrgSRZl8wxSFAuMa2110`. That will allow users to migrate to the Torrust Tracker wihtout forcing the users to re-start downloading/seeding with new keys. - If `seconds_valid` is `null` the key will be permanent. Otherwise it will expire after the seconds specified in this value. --- migrations/README.md | 5 + ...3000_torrust_tracker_create_all_tables.sql | 21 +++ ...rust_tracker_keys_valid_until_nullable.sql | 1 + ...3000_torrust_tracker_create_all_tables.sql | 19 ++ ...rust_tracker_keys_valid_until_nullable.sql | 12 ++ src/core/auth.rs | 107 ++++++++---- src/core/databases/mod.rs | 8 +- src/core/databases/mysql.rs | 39 +++-- src/core/databases/sqlite.rs | 59 +++++-- src/core/error.rs | 23 +++ src/core/mod.rs | 162 ++++++++++++++++-- src/servers/apis/v1/context/auth_key/forms.rs | 16 +- .../apis/v1/context/auth_key/handlers.rs | 48 ++---- src/servers/apis/v1/context/auth_key/mod.rs | 10 +- .../apis/v1/context/auth_key/resources.rs | 53 +++--- .../apis/v1/context/auth_key/responses.rs | 5 +- src/shared/bit_torrent/common.rs | 4 +- tests/servers/api/v1/client.rs | 2 +- .../api/v1/contract/context/auth_key.rs | 24 +-- tests/servers/http/v1/contract.rs | 4 +- 20 files changed, 455 insertions(+), 167 deletions(-) create mode 100644 migrations/README.md create mode 100644 migrations/mysql/20240730183000_torrust_tracker_create_all_tables.sql create mode 100644 migrations/mysql/20240730183500_torrust_tracker_keys_valid_until_nullable.sql create mode 100644 migrations/sqlite/20240730183000_torrust_tracker_create_all_tables.sql create mode 100644 migrations/sqlite/20240730183500_torrust_tracker_keys_valid_until_nullable.sql diff --git a/migrations/README.md b/migrations/README.md new file mode 100644 index 000000000..090c46ccb --- /dev/null +++ b/migrations/README.md @@ -0,0 +1,5 @@ +# Database Migrations + +We don't support automatic migrations yet. The tracker creates all the needed tables when it starts. The SQL sentences are hardcoded in each database driver. + +The migrations in this folder were introduced to add some new changes (permanent keys) and to allow users to migrate to the new version. In the future, we will remove the hardcoded SQL and start using a Rust crate for database migrations. For the time being, if you are using the initial schema described in the migration `20240730183000_torrust_tracker_create_all_tables.sql` you will need to run all the subsequent migrations manually. diff --git a/migrations/mysql/20240730183000_torrust_tracker_create_all_tables.sql b/migrations/mysql/20240730183000_torrust_tracker_create_all_tables.sql new file mode 100644 index 000000000..407ae4dd1 --- /dev/null +++ b/migrations/mysql/20240730183000_torrust_tracker_create_all_tables.sql @@ -0,0 +1,21 @@ +CREATE TABLE + IF NOT EXISTS whitelist ( + id integer PRIMARY KEY AUTO_INCREMENT, + info_hash VARCHAR(40) NOT NULL UNIQUE + ); + +CREATE TABLE + IF NOT EXISTS torrents ( + id integer PRIMARY KEY AUTO_INCREMENT, + info_hash VARCHAR(40) NOT NULL UNIQUE, + completed INTEGER DEFAULT 0 NOT NULL + ); + +CREATE TABLE + IF NOT EXISTS `keys` ( + `id` INT NOT NULL AUTO_INCREMENT, + `key` VARCHAR(32) NOT NULL, + `valid_until` INT (10) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE (`key`) + ); \ No newline at end of file diff --git a/migrations/mysql/20240730183500_torrust_tracker_keys_valid_until_nullable.sql b/migrations/mysql/20240730183500_torrust_tracker_keys_valid_until_nullable.sql new file mode 100644 index 000000000..2602797d6 --- /dev/null +++ b/migrations/mysql/20240730183500_torrust_tracker_keys_valid_until_nullable.sql @@ -0,0 +1 @@ +ALTER TABLE `keys` CHANGE `valid_until` `valid_until` INT (10); \ No newline at end of file diff --git a/migrations/sqlite/20240730183000_torrust_tracker_create_all_tables.sql b/migrations/sqlite/20240730183000_torrust_tracker_create_all_tables.sql new file mode 100644 index 000000000..bd451bf8b --- /dev/null +++ b/migrations/sqlite/20240730183000_torrust_tracker_create_all_tables.sql @@ -0,0 +1,19 @@ +CREATE TABLE + IF NOT EXISTS whitelist ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + info_hash TEXT NOT NULL UNIQUE + ); + +CREATE TABLE + IF NOT EXISTS torrents ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + info_hash TEXT NOT NULL UNIQUE, + completed INTEGER DEFAULT 0 NOT NULL + ); + +CREATE TABLE + IF NOT EXISTS keys ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + key TEXT NOT NULL UNIQUE, + valid_until INTEGER NOT NULL + ); \ No newline at end of file diff --git a/migrations/sqlite/20240730183500_torrust_tracker_keys_valid_until_nullable.sql b/migrations/sqlite/20240730183500_torrust_tracker_keys_valid_until_nullable.sql new file mode 100644 index 000000000..c6746e3ee --- /dev/null +++ b/migrations/sqlite/20240730183500_torrust_tracker_keys_valid_until_nullable.sql @@ -0,0 +1,12 @@ +CREATE TABLE + IF NOT EXISTS keys_new ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + key TEXT NOT NULL UNIQUE, + valid_until INTEGER + ); + +INSERT INTO keys_new SELECT * FROM `keys`; + +DROP TABLE `keys`; + +ALTER TABLE keys_new RENAME TO `keys`; \ No newline at end of file diff --git a/src/core/auth.rs b/src/core/auth.rs index 783faa0da..999b43615 100644 --- a/src/core/auth.rs +++ b/src/core/auth.rs @@ -4,7 +4,7 @@ //! Tracker keys are tokens used to authenticate the tracker clients when the tracker runs //! in `private` or `private_listed` modes. //! -//! There are services to [`generate`] and [`verify`] authentication keys. +//! There are services to [`generate_key`] and [`verify_key`] authentication keys. //! //! Authentication keys are used only by [`HTTP`](crate::servers::http) trackers. All keys have an expiration time, that means //! they are only valid during a period of time. After that time the expiring key will no longer be valid. @@ -19,7 +19,7 @@ //! /// Random 32-char string. For example: `YZSl4lMZupRuOpSRC3krIKR5BPB14nrJ` //! pub key: Key, //! /// Timestamp, the key will be no longer valid after this timestamp -//! pub valid_until: DurationSinceUnixEpoch, +//! pub valid_until: Option, //! } //! ``` //! @@ -29,11 +29,11 @@ //! use torrust_tracker::core::auth; //! use std::time::Duration; //! -//! let expiring_key = auth::generate(Duration::new(9999, 0)); +//! let expiring_key = auth::generate_key(Some(Duration::new(9999, 0))); //! //! // And you can later verify it with: //! -//! assert!(auth::verify(&expiring_key).is_ok()); +//! assert!(auth::verify_key(&expiring_key).is_ok()); //! ``` use std::panic::Location; @@ -55,63 +55,96 @@ use tracing::debug; use crate::shared::bit_torrent::common::AUTH_KEY_LENGTH; use crate::CurrentClock; +/// It generates a new permanent random key [`PeerKey`]. #[must_use] -/// It generates a new random 32-char authentication [`ExpiringKey`] +pub fn generate_permanent_key() -> PeerKey { + generate_key(None) +} + +/// It generates a new random 32-char authentication [`PeerKey`]. +/// +/// It can be an expiring or permanent key. /// /// # Panics /// /// It would panic if the `lifetime: Duration` + Duration is more than `Duration::MAX`. -pub fn generate(lifetime: Duration) -> ExpiringKey { +/// +/// # Arguments +/// +/// * `lifetime`: if `None` the key will be permanent. +#[must_use] +pub fn generate_key(lifetime: Option) -> PeerKey { let random_id: String = thread_rng() .sample_iter(&Alphanumeric) .take(AUTH_KEY_LENGTH) .map(char::from) .collect(); - debug!("Generated key: {}, valid for: {:?} seconds", random_id, lifetime); + if let Some(lifetime) = lifetime { + debug!("Generated key: {}, valid for: {:?} seconds", random_id, lifetime); + + PeerKey { + key: random_id.parse::().unwrap(), + valid_until: Some(CurrentClock::now_add(&lifetime).unwrap()), + } + } else { + debug!("Generated key: {}, permanent", random_id); - ExpiringKey { - key: random_id.parse::().unwrap(), - valid_until: CurrentClock::now_add(&lifetime).unwrap(), + PeerKey { + key: random_id.parse::().unwrap(), + valid_until: None, + } } } -/// It verifies an [`ExpiringKey`]. It checks if the expiration date has passed. +/// It verifies an [`PeerKey`]. It checks if the expiration date has passed. +/// Permanent keys without duration (`None`) do not expire. /// /// # Errors /// -/// Will return `Error::KeyExpired` if `auth_key.valid_until` is past the `current_time`. +/// Will return: /// -/// Will return `Error::KeyInvalid` if `auth_key.valid_until` is past the `None`. -pub fn verify(auth_key: &ExpiringKey) -> Result<(), Error> { +/// - `Error::KeyExpired` if `auth_key.valid_until` is past the `current_time`. +/// - `Error::KeyInvalid` if `auth_key.valid_until` is past the `None`. +pub fn verify_key(auth_key: &PeerKey) -> Result<(), Error> { let current_time: DurationSinceUnixEpoch = CurrentClock::now(); - if auth_key.valid_until < current_time { - Err(Error::KeyExpired { - location: Location::caller(), - }) - } else { - Ok(()) + match auth_key.valid_until { + Some(valid_until) => { + if valid_until < current_time { + Err(Error::KeyExpired { + location: Location::caller(), + }) + } else { + Ok(()) + } + } + None => Ok(()), // Permanent key } } -/// An authentication key which has an expiration time. +/// An authentication key which can potentially have an expiration time. /// After that time is will automatically become invalid. #[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] -pub struct ExpiringKey { +pub struct PeerKey { /// Random 32-char string. For example: `YZSl4lMZupRuOpSRC3krIKR5BPB14nrJ` pub key: Key, - /// Timestamp, the key will be no longer valid after this timestamp - pub valid_until: DurationSinceUnixEpoch, + + /// Timestamp, the key will be no longer valid after this timestamp. + /// If `None` the keys will not expire (permanent key). + pub valid_until: Option, } -impl std::fmt::Display for ExpiringKey { +impl std::fmt::Display for PeerKey { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "key: `{}`, valid until `{}`", self.key, self.expiry_time()) + match self.expiry_time() { + Some(expire_time) => write!(f, "key: `{}`, valid until `{}`", self.key, expire_time), + None => write!(f, "key: `{}`, permanent", self.key), + } } } -impl ExpiringKey { +impl PeerKey { #[must_use] pub fn key(&self) -> Key { self.key.clone() @@ -126,8 +159,8 @@ impl ExpiringKey { /// Will panic when the key timestamp overflows the internal i64 type. /// (this will naturally happen in 292.5 billion years) #[must_use] - pub fn expiry_time(&self) -> chrono::DateTime { - convert_from_timestamp_to_datetime_utc(self.valid_until) + pub fn expiry_time(&self) -> Option> { + self.valid_until.map(convert_from_timestamp_to_datetime_utc) } } @@ -194,8 +227,8 @@ impl FromStr for Key { } } -/// Verification error. Error returned when an [`ExpiringKey`] cannot be -/// verified with the [`verify(...)`](crate::core::auth::verify) function. +/// Verification error. Error returned when an [`PeerKey`] cannot be +/// verified with the (`crate::core::auth::verify_key`) function. #[derive(Debug, Error)] #[allow(dead_code)] pub enum Error { @@ -277,7 +310,7 @@ mod tests { // Set the time to the current time. clock::Stopped::local_set_to_unix_epoch(); - let expiring_key = auth::generate(Duration::from_secs(0)); + let expiring_key = auth::generate_key(Some(Duration::from_secs(0))); assert_eq!( expiring_key.to_string(), @@ -287,9 +320,9 @@ mod tests { #[test] fn should_be_generated_with_a_expiration_time() { - let expiring_key = auth::generate(Duration::new(9999, 0)); + let expiring_key = auth::generate_key(Some(Duration::new(9999, 0))); - assert!(auth::verify(&expiring_key).is_ok()); + assert!(auth::verify_key(&expiring_key).is_ok()); } #[test] @@ -298,17 +331,17 @@ mod tests { clock::Stopped::local_set_to_system_time_now(); // Make key that is valid for 19 seconds. - let expiring_key = auth::generate(Duration::from_secs(19)); + let expiring_key = auth::generate_key(Some(Duration::from_secs(19))); // Mock the time has passed 10 sec. clock::Stopped::local_add(&Duration::from_secs(10)).unwrap(); - assert!(auth::verify(&expiring_key).is_ok()); + assert!(auth::verify_key(&expiring_key).is_ok()); // Mock the time has passed another 10 sec. clock::Stopped::local_add(&Duration::from_secs(10)).unwrap(); - assert!(auth::verify(&expiring_key).is_err()); + assert!(auth::verify_key(&expiring_key).is_err()); } } } diff --git a/src/core/databases/mod.rs b/src/core/databases/mod.rs index cdb4c7ce5..f559eb80e 100644 --- a/src/core/databases/mod.rs +++ b/src/core/databases/mod.rs @@ -195,11 +195,11 @@ pub trait Database: Sync + Send { /// # Errors /// /// Will return `Err` if unable to load. - fn load_keys(&self) -> Result, Error>; + fn load_keys(&self) -> Result, Error>; /// It gets an expiring authentication key from the database. /// - /// It returns `Some(ExpiringKey)` if a [`ExpiringKey`](crate::core::auth::ExpiringKey) + /// It returns `Some(PeerKey)` if a [`PeerKey`](crate::core::auth::PeerKey) /// with the input [`Key`] exists, `None` otherwise. /// /// # Context: Authentication Keys @@ -207,7 +207,7 @@ pub trait Database: Sync + Send { /// # Errors /// /// Will return `Err` if unable to load. - fn get_key_from_keys(&self, key: &Key) -> Result, Error>; + fn get_key_from_keys(&self, key: &Key) -> Result, Error>; /// It adds an expiring authentication key to the database. /// @@ -216,7 +216,7 @@ pub trait Database: Sync + Send { /// # Errors /// /// Will return `Err` if unable to save. - fn add_key_to_keys(&self, auth_key: &auth::ExpiringKey) -> Result; + fn add_key_to_keys(&self, auth_key: &auth::PeerKey) -> Result; /// It removes an expiring authentication key from the database. /// diff --git a/src/core/databases/mysql.rs b/src/core/databases/mysql.rs index 40eced900..3a06c4982 100644 --- a/src/core/databases/mysql.rs +++ b/src/core/databases/mysql.rs @@ -60,7 +60,7 @@ impl Database for Mysql { CREATE TABLE IF NOT EXISTS `keys` ( `id` INT NOT NULL AUTO_INCREMENT, `key` VARCHAR({}) NOT NULL, - `valid_until` INT(10) NOT NULL, + `valid_until` INT(10), PRIMARY KEY (`id`), UNIQUE (`key`) );", @@ -119,14 +119,20 @@ impl Database for Mysql { } /// Refer to [`databases::Database::load_keys`](crate::core::databases::Database::load_keys). - fn load_keys(&self) -> Result, Error> { + fn load_keys(&self) -> Result, Error> { let mut conn = self.pool.get().map_err(|e| (e, DRIVER))?; let keys = conn.query_map( "SELECT `key`, valid_until FROM `keys`", - |(key, valid_until): (String, i64)| auth::ExpiringKey { - key: key.parse::().unwrap(), - valid_until: Duration::from_secs(valid_until.unsigned_abs()), + |(key, valid_until): (String, Option)| match valid_until { + Some(valid_until) => auth::PeerKey { + key: key.parse::().unwrap(), + valid_until: Some(Duration::from_secs(valid_until.unsigned_abs())), + }, + None => auth::PeerKey { + key: key.parse::().unwrap(), + valid_until: None, + }, }, )?; @@ -197,28 +203,37 @@ impl Database for Mysql { } /// Refer to [`databases::Database::get_key_from_keys`](crate::core::databases::Database::get_key_from_keys). - fn get_key_from_keys(&self, key: &Key) -> Result, Error> { + fn get_key_from_keys(&self, key: &Key) -> Result, Error> { let mut conn = self.pool.get().map_err(|e| (e, DRIVER))?; - let query = conn.exec_first::<(String, i64), _, _>( + let query = conn.exec_first::<(String, Option), _, _>( "SELECT `key`, valid_until FROM `keys` WHERE `key` = :key", params! { "key" => key.to_string() }, ); let key = query?; - Ok(key.map(|(key, expiry)| auth::ExpiringKey { - key: key.parse::().unwrap(), - valid_until: Duration::from_secs(expiry.unsigned_abs()), + Ok(key.map(|(key, opt_valid_until)| match opt_valid_until { + Some(valid_until) => auth::PeerKey { + key: key.parse::().unwrap(), + valid_until: Some(Duration::from_secs(valid_until.unsigned_abs())), + }, + None => auth::PeerKey { + key: key.parse::().unwrap(), + valid_until: None, + }, })) } /// Refer to [`databases::Database::add_key_to_keys`](crate::core::databases::Database::add_key_to_keys). - fn add_key_to_keys(&self, auth_key: &auth::ExpiringKey) -> Result { + fn add_key_to_keys(&self, auth_key: &auth::PeerKey) -> Result { let mut conn = self.pool.get().map_err(|e| (e, DRIVER))?; let key = auth_key.key.to_string(); - let valid_until = auth_key.valid_until.as_secs().to_string(); + let valid_until = match auth_key.valid_until { + Some(valid_until) => valid_until.as_secs().to_string(), + None => todo!(), + }; conn.exec_drop( "INSERT INTO `keys` (`key`, valid_until) VALUES (:key, :valid_until)", diff --git a/src/core/databases/sqlite.rs b/src/core/databases/sqlite.rs index 3acbf9e77..69470ee04 100644 --- a/src/core/databases/sqlite.rs +++ b/src/core/databases/sqlite.rs @@ -3,6 +3,8 @@ use std::panic::Location; use std::str::FromStr; use r2d2::Pool; +use r2d2_sqlite::rusqlite::params; +use r2d2_sqlite::rusqlite::types::Null; use r2d2_sqlite::SqliteConnectionManager; use torrust_tracker_primitives::info_hash::InfoHash; use torrust_tracker_primitives::{DurationSinceUnixEpoch, PersistentTorrents}; @@ -51,7 +53,7 @@ impl Database for Sqlite { CREATE TABLE IF NOT EXISTS keys ( id INTEGER PRIMARY KEY AUTOINCREMENT, key TEXT NOT NULL UNIQUE, - valid_until INTEGER NOT NULL + valid_until INTEGER );" .to_string(); @@ -104,22 +106,28 @@ impl Database for Sqlite { } /// Refer to [`databases::Database::load_keys`](crate::core::databases::Database::load_keys). - fn load_keys(&self) -> Result, Error> { + fn load_keys(&self) -> Result, Error> { let conn = self.pool.get().map_err(|e| (e, DRIVER))?; let mut stmt = conn.prepare("SELECT key, valid_until FROM keys")?; let keys_iter = stmt.query_map([], |row| { let key: String = row.get(0)?; - let valid_until: i64 = row.get(1)?; - - Ok(auth::ExpiringKey { - key: key.parse::().unwrap(), - valid_until: DurationSinceUnixEpoch::from_secs(valid_until.unsigned_abs()), - }) + let opt_valid_until: Option = row.get(1)?; + + match opt_valid_until { + Some(valid_until) => Ok(auth::PeerKey { + key: key.parse::().unwrap(), + valid_until: Some(DurationSinceUnixEpoch::from_secs(valid_until.unsigned_abs())), + }), + None => Ok(auth::PeerKey { + key: key.parse::().unwrap(), + valid_until: None, + }), + } })?; - let keys: Vec = keys_iter.filter_map(std::result::Result::ok).collect(); + let keys: Vec = keys_iter.filter_map(std::result::Result::ok).collect(); Ok(keys) } @@ -208,7 +216,7 @@ impl Database for Sqlite { } /// Refer to [`databases::Database::get_key_from_keys`](crate::core::databases::Database::get_key_from_keys). - fn get_key_from_keys(&self, key: &Key) -> Result, Error> { + fn get_key_from_keys(&self, key: &Key) -> Result, Error> { let conn = self.pool.get().map_err(|e| (e, DRIVER))?; let mut stmt = conn.prepare("SELECT key, valid_until FROM keys WHERE key = ?")?; @@ -218,23 +226,36 @@ impl Database for Sqlite { let key = rows.next()?; Ok(key.map(|f| { - let expiry: i64 = f.get(1).unwrap(); + let valid_until: Option = f.get(1).unwrap(); let key: String = f.get(0).unwrap(); - auth::ExpiringKey { - key: key.parse::().unwrap(), - valid_until: DurationSinceUnixEpoch::from_secs(expiry.unsigned_abs()), + + match valid_until { + Some(valid_until) => auth::PeerKey { + key: key.parse::().unwrap(), + valid_until: Some(DurationSinceUnixEpoch::from_secs(valid_until.unsigned_abs())), + }, + None => auth::PeerKey { + key: key.parse::().unwrap(), + valid_until: None, + }, } })) } /// Refer to [`databases::Database::add_key_to_keys`](crate::core::databases::Database::add_key_to_keys). - fn add_key_to_keys(&self, auth_key: &auth::ExpiringKey) -> Result { + fn add_key_to_keys(&self, auth_key: &auth::PeerKey) -> Result { let conn = self.pool.get().map_err(|e| (e, DRIVER))?; - let insert = conn.execute( - "INSERT INTO keys (key, valid_until) VALUES (?1, ?2)", - [auth_key.key.to_string(), auth_key.valid_until.as_secs().to_string()], - )?; + let insert = match auth_key.valid_until { + Some(valid_until) => conn.execute( + "INSERT INTO keys (key, valid_until) VALUES (?1, ?2)", + [auth_key.key.to_string(), valid_until.as_secs().to_string()], + )?, + None => conn.execute( + "INSERT INTO keys (key, valid_until) VALUES (?1, ?2)", + params![auth_key.key.to_string(), Null], + )?, + }; if insert == 0 { Err(Error::InsertFailed { 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 f0853ec27..f4cff8daf 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -453,13 +453,15 @@ use std::panic::Location; use std::sync::Arc; use std::time::Duration; -use auth::ExpiringKey; +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; @@ -492,7 +494,7 @@ pub struct Tracker { database: Arc>, /// Tracker users' keys. Only for private trackers. - keys: tokio::sync::RwLock>, + keys: tokio::sync::RwLock>, /// The list of allowed torrents. Only for listed trackers. whitelist: tokio::sync::RwLock>, @@ -556,6 +558,20 @@ impl ScrapeData { } } +/// This type contains the info needed to add a new tracker key. +/// +/// You can upload a pre-generated key or let the app to generate a new one. +/// You can also set an expiration date or leave it empty (`None`) if you want +/// to create a permanent key that does not expire. +#[derive(Debug)] +pub struct AddKeyRequest { + /// The pre-generated key. Use `None` to generate a random key. + pub opt_key: Option, + + /// How long the key will be valid in seconds. Use `None` for permanent keys. + pub opt_seconds_valid: Option, +} + impl Tracker { /// `Tracker` constructor. /// @@ -793,9 +809,96 @@ 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_req: AddKeyRequest) -> Result { + // code-review: all methods related to keys should be moved to a new independent "keys" service. + + match add_key_req.opt_key { + // Upload pre-generated key + Some(pre_existing_key) => { + if let Some(seconds_valid) = add_key_req.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_req.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. + /// + /// # Context: Authentication + /// + /// # Errors + /// + /// Will return a `database::Error` if unable to add the `auth_key` to the database. + pub async fn generate_permanent_auth_key(&self) -> Result { + self.generate_auth_key(None).await + } + /// It generates a new expiring authentication key. - /// `lifetime` param is the duration in seconds for the new key. - /// The key will be no longer valid after `lifetime` seconds. + /// /// Authentication keys are used by HTTP trackers. /// /// # Context: Authentication @@ -803,14 +906,37 @@ impl Tracker { /// # Errors /// /// Will return a `database::Error` if unable to add the `auth_key` to the database. - pub async fn generate_auth_key(&self, lifetime: Duration) -> Result { - let auth_key = auth::generate(lifetime); + /// + /// # Arguments + /// + /// * `lifetime` - The duration in seconds for the new key. The key will be + /// no longer valid after `lifetime` seconds. + pub async fn generate_auth_key(&self, lifetime: Option) -> Result { + let auth_key = auth::generate_key(lifetime); self.database.add_key_to_keys(&auth_key)?; self.keys.write().await.insert(auth_key.key.clone(), auth_key.clone()); Ok(auth_key) } + /// It adds a pre-generated permanent authentication key. + /// + /// Authentication keys are used by HTTP trackers. + /// + /// # Context: Authentication + /// + /// # Errors + /// + /// Will return a `database::Error` if unable to add the `auth_key` to the + /// database. For example, if the key already exist. + /// + /// # Arguments + /// + /// * `key` - The pre-generated key. + pub async fn add_permanent_auth_key(&self, key: Key) -> Result { + self.add_auth_key(key, None).await + } + /// It adds a pre-generated authentication key. /// /// Authentication keys are used by HTTP trackers. @@ -824,14 +950,15 @@ impl Tracker { /// /// # Arguments /// + /// * `key` - The pre-generated key. /// * `lifetime` - The duration in seconds for the new key. The key will be /// no longer valid after `lifetime` seconds. pub async fn add_auth_key( &self, key: Key, - valid_until: DurationSinceUnixEpoch, - ) -> Result { - let auth_key = ExpiringKey { key, valid_until }; + valid_until: Option, + ) -> Result { + let auth_key = PeerKey { key, valid_until }; // code-review: should we return a friendly error instead of the DB // constrain error when the key already exist? For now, it's returning @@ -869,7 +996,7 @@ impl Tracker { location: Location::caller(), key: Box::new(key.clone()), }), - Some(key) => auth::verify(key), + Some(key) => auth::verify_key(key), } } @@ -1661,16 +1788,19 @@ mod tests { async fn it_should_generate_the_expiring_authentication_keys() { let tracker = private_tracker(); - let key = tracker.generate_auth_key(Duration::from_secs(100)).await.unwrap(); + let key = tracker.generate_auth_key(Some(Duration::from_secs(100))).await.unwrap(); - assert_eq!(key.valid_until, CurrentClock::now_add(&Duration::from_secs(100)).unwrap()); + assert_eq!( + key.valid_until, + Some(CurrentClock::now_add(&Duration::from_secs(100)).unwrap()) + ); } #[tokio::test] async fn it_should_authenticate_a_peer_by_using_a_key() { let tracker = private_tracker(); - let expiring_key = tracker.generate_auth_key(Duration::from_secs(100)).await.unwrap(); + let expiring_key = tracker.generate_auth_key(Some(Duration::from_secs(100))).await.unwrap(); let result = tracker.authenticate(&expiring_key.key()).await; @@ -1694,7 +1824,7 @@ mod tests { // `verify_auth_key` should be a private method. let tracker = private_tracker(); - let expiring_key = tracker.generate_auth_key(Duration::from_secs(100)).await.unwrap(); + let expiring_key = tracker.generate_auth_key(Some(Duration::from_secs(100))).await.unwrap(); assert!(tracker.verify_auth_key(&expiring_key.key()).await.is_ok()); } @@ -1712,7 +1842,7 @@ mod tests { async fn it_should_remove_an_authentication_key() { let tracker = private_tracker(); - let expiring_key = tracker.generate_auth_key(Duration::from_secs(100)).await.unwrap(); + let expiring_key = tracker.generate_auth_key(Some(Duration::from_secs(100))).await.unwrap(); let result = tracker.remove_auth_key(&expiring_key.key()).await; @@ -1724,7 +1854,7 @@ mod tests { async fn it_should_load_authentication_keys_from_the_database() { let tracker = private_tracker(); - let expiring_key = tracker.generate_auth_key(Duration::from_secs(100)).await.unwrap(); + let expiring_key = tracker.generate_auth_key(Some(Duration::from_secs(100))).await.unwrap(); // Remove the newly generated key in memory tracker.keys.write().await.remove(&expiring_key.key()); diff --git a/src/servers/apis/v1/context/auth_key/forms.rs b/src/servers/apis/v1/context/auth_key/forms.rs index 9c023ab72..5dfea6e80 100644 --- a/src/servers/apis/v1/context/auth_key/forms.rs +++ b/src/servers/apis/v1/context/auth_key/forms.rs @@ -1,8 +1,22 @@ use serde::{Deserialize, Serialize}; +use serde_with::{serde_as, DefaultOnNull}; +/// This type contains the info needed to add a new tracker key. +/// +/// You can upload a pre-generated key or let the app to generate a new one. +/// You can also set an expiration date or leave it empty (`None`) if you want +/// to create permanent key that does not expire. +#[serde_as] #[derive(Serialize, Deserialize, Debug)] pub struct AddKeyForm { + /// The pre-generated key. Use `None` (null in json) to generate a random key. + #[serde_as(deserialize_as = "DefaultOnNull")] #[serde(rename = "key")] pub opt_key: Option, - pub seconds_valid: u64, + + /// How long the key will be valid in seconds. Use `None` (null in json) for + /// permanent keys. + #[serde_as(deserialize_as = "DefaultOnNull")] + #[serde(rename = "seconds_valid")] + pub opt_seconds_valid: Option, } diff --git a/src/servers/apis/v1/context/auth_key/handlers.rs b/src/servers/apis/v1/context/auth_key/handlers.rs index 6d2d99150..fed3ad301 100644 --- a/src/servers/apis/v1/context/auth_key/handlers.rs +++ b/src/servers/apis/v1/context/auth_key/handlers.rs @@ -6,18 +6,16 @@ use std::time::Duration; use axum::extract::{self, Path, State}; use axum::response::Response; use serde::Deserialize; -use torrust_tracker_clock::clock::Time; use super::forms::AddKeyForm; use super::responses::{ - auth_key_response, failed_to_add_key_response, failed_to_delete_key_response, failed_to_generate_key_response, - failed_to_reload_keys_response, invalid_auth_key_duration_response, invalid_auth_key_response, + auth_key_response, failed_to_delete_key_response, failed_to_generate_key_response, failed_to_reload_keys_response, + invalid_auth_key_duration_response, invalid_auth_key_response, }; use crate::core::auth::Key; -use crate::core::Tracker; +use crate::core::{AddKeyRequest, Tracker}; use crate::servers::apis::v1::context::auth_key::resources::AuthKey; use crate::servers::apis::v1::responses::{invalid_auth_key_param_response, ok_response}; -use crate::CurrentClock; /// It handles the request to add a new authentication key. /// @@ -36,31 +34,21 @@ pub async fn add_auth_key_handler( State(tracker): State>, extract::Json(add_key_form): extract::Json, ) -> Response { - match add_key_form.opt_key { - Some(pre_existing_key) => { - let Some(valid_until) = CurrentClock::now_add(&Duration::from_secs(add_key_form.seconds_valid)) else { - return invalid_auth_key_duration_response(add_key_form.seconds_valid); - }; - - let key = pre_existing_key.parse::(); - - match key { - Ok(key) => match tracker.add_auth_key(key, valid_until).await { - Ok(auth_key) => auth_key_response(&AuthKey::from(auth_key)), - Err(e) => failed_to_add_key_response(e), - }, - Err(e) => invalid_auth_key_response(&pre_existing_key, &e), - } - } - None => { - match tracker - .generate_auth_key(Duration::from_secs(add_key_form.seconds_valid)) - .await - { - Ok(auth_key) => auth_key_response(&AuthKey::from(auth_key)), - Err(e) => failed_to_generate_key_response(e), + match tracker + .add_peer_key(AddKeyRequest { + opt_key: add_key_form.opt_key.clone(), + opt_seconds_valid: add_key_form.opt_seconds_valid, + }) + .await + { + Ok(auth_key) => auth_key_response(&AuthKey::from(auth_key)), + Err(err) => match err { + crate::core::error::PeerKeyError::DurationOverflow { seconds_valid } => { + invalid_auth_key_duration_response(seconds_valid) } - } + crate::core::error::PeerKeyError::InvalidKey { key, source } => invalid_auth_key_response(&key, source), + crate::core::error::PeerKeyError::DatabaseError { source } => failed_to_generate_key_response(source), + }, } } @@ -79,7 +67,7 @@ pub async fn add_auth_key_handler( /// This endpoint has been deprecated. Use [`add_auth_key_handler`]. pub async fn generate_auth_key_handler(State(tracker): State>, Path(seconds_valid_or_key): Path) -> Response { let seconds_valid = seconds_valid_or_key; - match tracker.generate_auth_key(Duration::from_secs(seconds_valid)).await { + match tracker.generate_auth_key(Some(Duration::from_secs(seconds_valid))).await { Ok(auth_key) => auth_key_response(&AuthKey::from(auth_key)), Err(e) => failed_to_generate_key_response(e), } diff --git a/src/servers/apis/v1/context/auth_key/mod.rs b/src/servers/apis/v1/context/auth_key/mod.rs index f6762b26e..b4112f21f 100644 --- a/src/servers/apis/v1/context/auth_key/mod.rs +++ b/src/servers/apis/v1/context/auth_key/mod.rs @@ -26,17 +26,15 @@ //! //! It generates a new authentication key or upload a pre-generated key. //! -//! > **NOTICE**: keys expire after a certain amount of time. -//! //! **POST parameters** //! //! Name | Type | Description | Required | Example //! ---|---|---|---|--- -//! `key` | 32-char string (0-9, a-z, A-Z) | The optional pre-generated key. | No | `Xc1L4PbQJSFGlrgSRZl8wxSFAuMa21z7` -//! `seconds_valid` | positive integer | The number of seconds the key will be valid. | Yes | `3600` +//! `key` | 32-char string (0-9, a-z, A-Z) or `null` | The optional pre-generated key. | Yes | `Xc1L4PbQJSFGlrgSRZl8wxSFAuMa21z7` or `null` +//! `seconds_valid` | positive integer or `null` | The number of seconds the key will be valid. | Yes | `3600` or `null` //! -//! > **NOTICE**: the `key` field is optional. If is not provided the tracker -//! > will generated a random one. +//! > **NOTICE**: the `key` and `seconds_valid` fields are optional. If `key` is not provided the tracker +//! > will generated a random one. If `seconds_valid` field is not provided the key will be permanent. You can use the `null` value. //! //! **Example request** //! diff --git a/src/servers/apis/v1/context/auth_key/resources.rs b/src/servers/apis/v1/context/auth_key/resources.rs index 3671438c2..c26b2c4d3 100644 --- a/src/servers/apis/v1/context/auth_key/resources.rs +++ b/src/servers/apis/v1/context/auth_key/resources.rs @@ -12,27 +12,36 @@ pub struct AuthKey { pub key: String, /// The timestamp when the key will expire. #[deprecated(since = "3.0.0", note = "please use `expiry_time` instead")] - pub valid_until: u64, // todo: remove when the torrust-index-backend starts using the `expiry_time` attribute. + pub valid_until: Option, // todo: remove when the torrust-index-backend starts using the `expiry_time` attribute. /// The ISO 8601 timestamp when the key will expire. - pub expiry_time: String, + pub expiry_time: Option, } -impl From for auth::ExpiringKey { +impl From for auth::PeerKey { fn from(auth_key_resource: AuthKey) -> Self { - auth::ExpiringKey { + auth::PeerKey { key: auth_key_resource.key.parse::().unwrap(), - valid_until: convert_from_iso_8601_to_timestamp(&auth_key_resource.expiry_time), + valid_until: auth_key_resource + .expiry_time + .map(|expiry_time| convert_from_iso_8601_to_timestamp(&expiry_time)), } } } #[allow(deprecated)] -impl From for AuthKey { - fn from(auth_key: auth::ExpiringKey) -> Self { - AuthKey { - key: auth_key.key.to_string(), - valid_until: auth_key.valid_until.as_secs(), - expiry_time: auth_key.expiry_time().to_string(), +impl From for AuthKey { + fn from(auth_key: auth::PeerKey) -> Self { + match (auth_key.valid_until, auth_key.expiry_time()) { + (Some(valid_until), Some(expiry_time)) => AuthKey { + key: auth_key.key.to_string(), + valid_until: Some(valid_until.as_secs()), + expiry_time: Some(expiry_time.to_string()), + }, + _ => AuthKey { + key: auth_key.key.to_string(), + valid_until: None, + expiry_time: None, + }, } } } @@ -72,15 +81,15 @@ mod tests { let auth_key_resource = AuthKey { key: "IaWDneuFNZi8IB4MPA3qW1CD0M30EZSM".to_string(), // cspell:disable-line - valid_until: one_hour_after_unix_epoch().timestamp, - expiry_time: one_hour_after_unix_epoch().iso_8601_v1, + valid_until: Some(one_hour_after_unix_epoch().timestamp), + expiry_time: Some(one_hour_after_unix_epoch().iso_8601_v1), }; assert_eq!( - auth::ExpiringKey::from(auth_key_resource), - auth::ExpiringKey { + auth::PeerKey::from(auth_key_resource), + auth::PeerKey { key: "IaWDneuFNZi8IB4MPA3qW1CD0M30EZSM".parse::().unwrap(), // cspell:disable-line - valid_until: CurrentClock::now_add(&Duration::new(one_hour_after_unix_epoch().timestamp, 0)).unwrap() + valid_until: Some(CurrentClock::now_add(&Duration::new(one_hour_after_unix_epoch().timestamp, 0)).unwrap()) } ); } @@ -90,17 +99,17 @@ mod tests { fn it_should_be_convertible_from_an_auth_key() { clock::Stopped::local_set_to_unix_epoch(); - let auth_key = auth::ExpiringKey { + let auth_key = auth::PeerKey { key: "IaWDneuFNZi8IB4MPA3qW1CD0M30EZSM".parse::().unwrap(), // cspell:disable-line - valid_until: CurrentClock::now_add(&Duration::new(one_hour_after_unix_epoch().timestamp, 0)).unwrap(), + valid_until: Some(CurrentClock::now_add(&Duration::new(one_hour_after_unix_epoch().timestamp, 0)).unwrap()), }; assert_eq!( AuthKey::from(auth_key), AuthKey { key: "IaWDneuFNZi8IB4MPA3qW1CD0M30EZSM".to_string(), // cspell:disable-line - valid_until: one_hour_after_unix_epoch().timestamp, - expiry_time: one_hour_after_unix_epoch().iso_8601_v2, + valid_until: Some(one_hour_after_unix_epoch().timestamp), + expiry_time: Some(one_hour_after_unix_epoch().iso_8601_v2), } ); } @@ -111,8 +120,8 @@ mod tests { assert_eq!( serde_json::to_string(&AuthKey { key: "IaWDneuFNZi8IB4MPA3qW1CD0M30EZSM".to_string(), // cspell:disable-line - valid_until: one_hour_after_unix_epoch().timestamp, - expiry_time: one_hour_after_unix_epoch().iso_8601_v1, + valid_until: Some(one_hour_after_unix_epoch().timestamp), + expiry_time: Some(one_hour_after_unix_epoch().iso_8601_v1), }) .unwrap(), "{\"key\":\"IaWDneuFNZi8IB4MPA3qW1CD0M30EZSM\",\"valid_until\":60,\"expiry_time\":\"1970-01-01T00:01:00.000Z\"}" // cspell:disable-line diff --git a/src/servers/apis/v1/context/auth_key/responses.rs b/src/servers/apis/v1/context/auth_key/responses.rs index dfe449b46..4905d9adc 100644 --- a/src/servers/apis/v1/context/auth_key/responses.rs +++ b/src/servers/apis/v1/context/auth_key/responses.rs @@ -4,7 +4,6 @@ use std::error::Error; use axum::http::{header, StatusCode}; use axum::response::{IntoResponse, Response}; -use crate::core::auth::ParseKeyError; use crate::servers::apis::v1::context::auth_key::resources::AuthKey; use crate::servers::apis::v1::responses::{bad_request_response, unhandled_rejection_response}; @@ -51,8 +50,8 @@ pub fn failed_to_reload_keys_response(e: E) -> Response { } #[must_use] -pub fn invalid_auth_key_response(auth_key: &str, error: &ParseKeyError) -> Response { - bad_request_response(&format!("Invalid URL: invalid auth key: string \"{auth_key}\", {error}")) +pub fn invalid_auth_key_response(auth_key: &str, e: E) -> Response { + bad_request_response(&format!("Invalid URL: invalid auth key: string \"{auth_key}\", {e}")) } #[must_use] diff --git a/src/shared/bit_torrent/common.rs b/src/shared/bit_torrent/common.rs index 3dd059a6a..46026ac47 100644 --- a/src/shared/bit_torrent/common.rs +++ b/src/shared/bit_torrent/common.rs @@ -17,6 +17,6 @@ pub const MAX_SCRAPE_TORRENTS: u8 = 74; /// HTTP tracker authentication key length. /// -/// See function to [`generate`](crate::core::auth::generate) the -/// [`ExpiringKeys`](crate::core::auth::ExpiringKey) for more information. +/// For more information see function [`generate_key`](crate::core::auth::generate_key) to generate the +/// [`PeerKey`](crate::core::auth::PeerKey). pub const AUTH_KEY_LENGTH: usize = 32; diff --git a/tests/servers/api/v1/client.rs b/tests/servers/api/v1/client.rs index 91f18acac..3d95c10ca 100644 --- a/tests/servers/api/v1/client.rs +++ b/tests/servers/api/v1/client.rs @@ -134,5 +134,5 @@ pub async fn get(path: &str, query: Option) -> Response { pub struct AddKeyForm { #[serde(rename = "key")] pub opt_key: Option, - pub seconds_valid: u64, + pub seconds_valid: Option, } diff --git a/tests/servers/api/v1/contract/context/auth_key.rs b/tests/servers/api/v1/contract/context/auth_key.rs index 3130503d9..cd6d2544f 100644 --- a/tests/servers/api/v1/contract/context/auth_key.rs +++ b/tests/servers/api/v1/contract/context/auth_key.rs @@ -20,7 +20,7 @@ async fn should_allow_generating_a_new_random_auth_key() { let response = Client::new(env.get_connection_info()) .add_auth_key(AddKeyForm { opt_key: None, - seconds_valid: 60, + seconds_valid: Some(60), }) .await; @@ -43,7 +43,7 @@ async fn should_allow_uploading_a_preexisting_auth_key() { let response = Client::new(env.get_connection_info()) .add_auth_key(AddKeyForm { opt_key: Some("Xc1L4PbQJSFGlrgSRZl8wxSFAuMa21z5".to_string()), - seconds_valid: 60, + seconds_valid: Some(60), }) .await; @@ -66,7 +66,7 @@ async fn should_not_allow_generating_a_new_auth_key_for_unauthenticated_users() let response = Client::new(connection_with_invalid_token(env.get_connection_info().bind_address.as_str())) .add_auth_key(AddKeyForm { opt_key: None, - seconds_valid: 60, + seconds_valid: Some(60), }) .await; @@ -75,7 +75,7 @@ async fn should_not_allow_generating_a_new_auth_key_for_unauthenticated_users() let response = Client::new(connection_with_no_token(env.get_connection_info().bind_address.as_str())) .add_auth_key(AddKeyForm { opt_key: None, - seconds_valid: 60, + seconds_valid: Some(60), }) .await; @@ -93,7 +93,7 @@ async fn should_fail_when_the_auth_key_cannot_be_generated() { let response = Client::new(env.get_connection_info()) .add_auth_key(AddKeyForm { opt_key: None, - seconds_valid: 60, + seconds_valid: Some(60), }) .await; @@ -109,7 +109,7 @@ async fn should_allow_deleting_an_auth_key() { let seconds_valid = 60; let auth_key = env .tracker - .generate_auth_key(Duration::from_secs(seconds_valid)) + .generate_auth_key(Some(Duration::from_secs(seconds_valid))) .await .unwrap(); @@ -223,7 +223,7 @@ async fn should_fail_when_the_auth_key_cannot_be_deleted() { let seconds_valid = 60; let auth_key = env .tracker - .generate_auth_key(Duration::from_secs(seconds_valid)) + .generate_auth_key(Some(Duration::from_secs(seconds_valid))) .await .unwrap(); @@ -247,7 +247,7 @@ async fn should_not_allow_deleting_an_auth_key_for_unauthenticated_users() { // Generate new auth key let auth_key = env .tracker - .generate_auth_key(Duration::from_secs(seconds_valid)) + .generate_auth_key(Some(Duration::from_secs(seconds_valid))) .await .unwrap(); @@ -260,7 +260,7 @@ async fn should_not_allow_deleting_an_auth_key_for_unauthenticated_users() { // Generate new auth key let auth_key = env .tracker - .generate_auth_key(Duration::from_secs(seconds_valid)) + .generate_auth_key(Some(Duration::from_secs(seconds_valid))) .await .unwrap(); @@ -279,7 +279,7 @@ async fn should_allow_reloading_keys() { let seconds_valid = 60; env.tracker - .generate_auth_key(Duration::from_secs(seconds_valid)) + .generate_auth_key(Some(Duration::from_secs(seconds_valid))) .await .unwrap(); @@ -296,7 +296,7 @@ async fn should_fail_when_keys_cannot_be_reloaded() { let seconds_valid = 60; env.tracker - .generate_auth_key(Duration::from_secs(seconds_valid)) + .generate_auth_key(Some(Duration::from_secs(seconds_valid))) .await .unwrap(); @@ -315,7 +315,7 @@ async fn should_not_allow_reloading_keys_for_unauthenticated_users() { let seconds_valid = 60; env.tracker - .generate_auth_key(Duration::from_secs(seconds_valid)) + .generate_auth_key(Some(Duration::from_secs(seconds_valid))) .await .unwrap(); diff --git a/tests/servers/http/v1/contract.rs b/tests/servers/http/v1/contract.rs index e4a35d0c5..14c237984 100644 --- a/tests/servers/http/v1/contract.rs +++ b/tests/servers/http/v1/contract.rs @@ -1261,7 +1261,7 @@ mod configured_as_private { async fn should_respond_to_authenticated_peers() { let env = Started::new(&configuration::ephemeral_private().into()).await; - let expiring_key = env.tracker.generate_auth_key(Duration::from_secs(60)).await.unwrap(); + let expiring_key = env.tracker.generate_auth_key(Some(Duration::from_secs(60))).await.unwrap(); let response = Client::authenticated(*env.bind_address(), expiring_key.key()) .announce(&QueryBuilder::default().query()) @@ -1393,7 +1393,7 @@ mod configured_as_private { .build(), ); - let expiring_key = env.tracker.generate_auth_key(Duration::from_secs(60)).await.unwrap(); + let expiring_key = env.tracker.generate_auth_key(Some(Duration::from_secs(60))).await.unwrap(); let response = Client::authenticated(*env.bind_address(), expiring_key.key()) .scrape(