|
| 1 | +use std::sync::Arc; |
| 2 | +use std::time::Duration; |
| 3 | + |
| 4 | +use torrust_tracker_clock::clock::Time; |
| 5 | +use torrust_tracker_located_error::Located; |
| 6 | +use torrust_tracker_primitives::DurationSinceUnixEpoch; |
| 7 | + |
| 8 | +use super::key::repository::in_memory::InMemoryKeyRepository; |
| 9 | +use super::key::repository::persisted::DatabaseKeyRepository; |
| 10 | +use super::{key, CurrentClock, Key, PeerKey}; |
| 11 | +use crate::core::databases; |
| 12 | +use crate::core::error::PeerKeyError; |
| 13 | + |
| 14 | +/// This type contains the info needed to add a new tracker key. |
| 15 | +/// |
| 16 | +/// You can upload a pre-generated key or let the app to generate a new one. |
| 17 | +/// You can also set an expiration date or leave it empty (`None`) if you want |
| 18 | +/// to create a permanent key that does not expire. |
| 19 | +#[derive(Debug)] |
| 20 | +pub struct AddKeyRequest { |
| 21 | + /// The pre-generated key. Use `None` to generate a random key. |
| 22 | + pub opt_key: Option<String>, |
| 23 | + |
| 24 | + /// How long the key will be valid in seconds. Use `None` for permanent keys. |
| 25 | + pub opt_seconds_valid: Option<u64>, |
| 26 | +} |
| 27 | + |
| 28 | +pub struct KeysHandler { |
| 29 | + /// The database repository for the authentication keys. |
| 30 | + db_key_repository: Arc<DatabaseKeyRepository>, |
| 31 | + |
| 32 | + /// In-memory implementation of the authentication key repository. |
| 33 | + in_memory_key_repository: Arc<InMemoryKeyRepository>, |
| 34 | +} |
| 35 | + |
| 36 | +impl KeysHandler { |
| 37 | + #[must_use] |
| 38 | + pub fn new(db_key_repository: &Arc<DatabaseKeyRepository>, in_memory_key_repository: &Arc<InMemoryKeyRepository>) -> Self { |
| 39 | + Self { |
| 40 | + db_key_repository: db_key_repository.clone(), |
| 41 | + in_memory_key_repository: in_memory_key_repository.clone(), |
| 42 | + } |
| 43 | + } |
| 44 | + |
| 45 | + /// Adds new peer keys to the tracker. |
| 46 | + /// |
| 47 | + /// Keys can be pre-generated or randomly created. They can also be permanent or expire. |
| 48 | + /// |
| 49 | + /// # Errors |
| 50 | + /// |
| 51 | + /// Will return an error if: |
| 52 | + /// |
| 53 | + /// - The key duration overflows the duration type maximum value. |
| 54 | + /// - The provided pre-generated key is invalid. |
| 55 | + /// - The key could not been persisted due to database issues. |
| 56 | + pub async fn add_peer_key(&self, add_key_req: AddKeyRequest) -> Result<PeerKey, PeerKeyError> { |
| 57 | + // code-review: all methods related to keys should be moved to a new independent "keys" service. |
| 58 | + |
| 59 | + match add_key_req.opt_key { |
| 60 | + // Upload pre-generated key |
| 61 | + Some(pre_existing_key) => { |
| 62 | + if let Some(seconds_valid) = add_key_req.opt_seconds_valid { |
| 63 | + // Expiring key |
| 64 | + let Some(valid_until) = CurrentClock::now_add(&Duration::from_secs(seconds_valid)) else { |
| 65 | + return Err(PeerKeyError::DurationOverflow { seconds_valid }); |
| 66 | + }; |
| 67 | + |
| 68 | + let key = pre_existing_key.parse::<Key>(); |
| 69 | + |
| 70 | + match key { |
| 71 | + Ok(key) => match self.add_auth_key(key, Some(valid_until)).await { |
| 72 | + Ok(auth_key) => Ok(auth_key), |
| 73 | + Err(err) => Err(PeerKeyError::DatabaseError { |
| 74 | + source: Located(err).into(), |
| 75 | + }), |
| 76 | + }, |
| 77 | + Err(err) => Err(PeerKeyError::InvalidKey { |
| 78 | + key: pre_existing_key, |
| 79 | + source: Located(err).into(), |
| 80 | + }), |
| 81 | + } |
| 82 | + } else { |
| 83 | + // Permanent key |
| 84 | + let key = pre_existing_key.parse::<Key>(); |
| 85 | + |
| 86 | + match key { |
| 87 | + Ok(key) => match self.add_permanent_auth_key(key).await { |
| 88 | + Ok(auth_key) => Ok(auth_key), |
| 89 | + Err(err) => Err(PeerKeyError::DatabaseError { |
| 90 | + source: Located(err).into(), |
| 91 | + }), |
| 92 | + }, |
| 93 | + Err(err) => Err(PeerKeyError::InvalidKey { |
| 94 | + key: pre_existing_key, |
| 95 | + source: Located(err).into(), |
| 96 | + }), |
| 97 | + } |
| 98 | + } |
| 99 | + } |
| 100 | + // Generate a new random key |
| 101 | + None => match add_key_req.opt_seconds_valid { |
| 102 | + // Expiring key |
| 103 | + Some(seconds_valid) => match self.generate_auth_key(Some(Duration::from_secs(seconds_valid))).await { |
| 104 | + Ok(auth_key) => Ok(auth_key), |
| 105 | + Err(err) => Err(PeerKeyError::DatabaseError { |
| 106 | + source: Located(err).into(), |
| 107 | + }), |
| 108 | + }, |
| 109 | + // Permanent key |
| 110 | + None => match self.generate_permanent_auth_key().await { |
| 111 | + Ok(auth_key) => Ok(auth_key), |
| 112 | + Err(err) => Err(PeerKeyError::DatabaseError { |
| 113 | + source: Located(err).into(), |
| 114 | + }), |
| 115 | + }, |
| 116 | + }, |
| 117 | + } |
| 118 | + } |
| 119 | + |
| 120 | + /// It generates a new permanent authentication key. |
| 121 | + /// |
| 122 | + /// Authentication keys are used by HTTP trackers. |
| 123 | + /// |
| 124 | + /// # Errors |
| 125 | + /// |
| 126 | + /// Will return a `database::Error` if unable to add the `auth_key` to the database. |
| 127 | + pub async fn generate_permanent_auth_key(&self) -> Result<PeerKey, databases::error::Error> { |
| 128 | + self.generate_auth_key(None).await |
| 129 | + } |
| 130 | + |
| 131 | + /// It generates a new expiring authentication key. |
| 132 | + /// |
| 133 | + /// Authentication keys are used by HTTP trackers. |
| 134 | + /// |
| 135 | + /// # Errors |
| 136 | + /// |
| 137 | + /// Will return a `database::Error` if unable to add the `auth_key` to the database. |
| 138 | + /// |
| 139 | + /// # Arguments |
| 140 | + /// |
| 141 | + /// * `lifetime` - The duration in seconds for the new key. The key will be |
| 142 | + /// no longer valid after `lifetime` seconds. |
| 143 | + pub async fn generate_auth_key(&self, lifetime: Option<Duration>) -> Result<PeerKey, databases::error::Error> { |
| 144 | + let peer_key = key::generate_key(lifetime); |
| 145 | + |
| 146 | + self.db_key_repository.add(&peer_key)?; |
| 147 | + |
| 148 | + self.in_memory_key_repository.insert(&peer_key).await; |
| 149 | + |
| 150 | + Ok(peer_key) |
| 151 | + } |
| 152 | + |
| 153 | + /// It adds a pre-generated permanent authentication key. |
| 154 | + /// |
| 155 | + /// Authentication keys are used by HTTP trackers. |
| 156 | + /// |
| 157 | + /// # Errors |
| 158 | + /// |
| 159 | + /// Will return a `database::Error` if unable to add the `auth_key` to the |
| 160 | + /// database. For example, if the key already exist. |
| 161 | + /// |
| 162 | + /// # Arguments |
| 163 | + /// |
| 164 | + /// * `key` - The pre-generated key. |
| 165 | + pub async fn add_permanent_auth_key(&self, key: Key) -> Result<PeerKey, databases::error::Error> { |
| 166 | + self.add_auth_key(key, None).await |
| 167 | + } |
| 168 | + |
| 169 | + /// It adds a pre-generated authentication key. |
| 170 | + /// |
| 171 | + /// Authentication keys are used by HTTP trackers. |
| 172 | + /// |
| 173 | + /// # Errors |
| 174 | + /// |
| 175 | + /// Will return a `database::Error` if unable to add the `auth_key` to the |
| 176 | + /// database. For example, if the key already exist. |
| 177 | + /// |
| 178 | + /// # Arguments |
| 179 | + /// |
| 180 | + /// * `key` - The pre-generated key. |
| 181 | + /// * `lifetime` - The duration in seconds for the new key. The key will be |
| 182 | + /// no longer valid after `lifetime` seconds. |
| 183 | + pub async fn add_auth_key( |
| 184 | + &self, |
| 185 | + key: Key, |
| 186 | + valid_until: Option<DurationSinceUnixEpoch>, |
| 187 | + ) -> Result<PeerKey, databases::error::Error> { |
| 188 | + let peer_key = PeerKey { key, valid_until }; |
| 189 | + |
| 190 | + // code-review: should we return a friendly error instead of the DB |
| 191 | + // constrain error when the key already exist? For now, it's returning |
| 192 | + // the specif error for each DB driver when a UNIQUE constrain fails. |
| 193 | + self.db_key_repository.add(&peer_key)?; |
| 194 | + |
| 195 | + self.in_memory_key_repository.insert(&peer_key).await; |
| 196 | + |
| 197 | + Ok(peer_key) |
| 198 | + } |
| 199 | + |
| 200 | + /// It removes an authentication key. |
| 201 | + /// |
| 202 | + /// # Errors |
| 203 | + /// |
| 204 | + /// Will return a `database::Error` if unable to remove the `key` to the database. |
| 205 | + pub async fn remove_auth_key(&self, key: &Key) -> Result<(), databases::error::Error> { |
| 206 | + self.db_key_repository.remove(key)?; |
| 207 | + |
| 208 | + self.remove_in_memory_auth_key(key).await; |
| 209 | + |
| 210 | + Ok(()) |
| 211 | + } |
| 212 | + |
| 213 | + /// It removes an authentication key from memory. |
| 214 | + pub async fn remove_in_memory_auth_key(&self, key: &Key) { |
| 215 | + self.in_memory_key_repository.remove(key).await; |
| 216 | + } |
| 217 | + |
| 218 | + /// The `Tracker` stores the authentication keys in memory and in the |
| 219 | + /// database. In case you need to restart the `Tracker` you can load the |
| 220 | + /// keys from the database into memory with this function. Keys are |
| 221 | + /// automatically stored in the database when they are generated. |
| 222 | + /// |
| 223 | + /// # Errors |
| 224 | + /// |
| 225 | + /// Will return a `database::Error` if unable to `load_keys` from the database. |
| 226 | + pub async fn load_keys_from_database(&self) -> Result<(), databases::error::Error> { |
| 227 | + let keys_from_database = self.db_key_repository.load_keys()?; |
| 228 | + |
| 229 | + self.in_memory_key_repository.reset_with(keys_from_database).await; |
| 230 | + |
| 231 | + Ok(()) |
| 232 | + } |
| 233 | +} |
0 commit comments