Skip to content

Commit bb2f9e0

Browse files
committed
refactor: [#1195] extract core::authentication::handler::KeysHandler
1 parent 23590e7 commit bb2f9e0

File tree

3 files changed

+249
-131
lines changed

3 files changed

+249
-131
lines changed

src/core/authentication/handler.rs

+233
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
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

Comments
 (0)