Compare commits
3 Commits
49c1a2fbd7
...
228a9b4262
Author | SHA1 | Date |
---|---|---|
_ | 228a9b4262 | |
_ | a454585d9c | |
_ | caaed8a5e1 |
|
@ -15,7 +15,6 @@ use crate::{
|
|||
errors::ConfigError,
|
||||
key_validity::{
|
||||
ScraperKey,
|
||||
Valid30Days,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -99,7 +98,6 @@ pub mod file {
|
|||
use crate::key_validity::{
|
||||
BlakeHashWrapper,
|
||||
ScraperKey,
|
||||
Valid30Days,
|
||||
};
|
||||
|
||||
#[derive (Clone, Debug, Deserialize, Serialize)]
|
||||
|
@ -142,7 +140,7 @@ pub mod file {
|
|||
pub servers: Option <Vec <Server>>,
|
||||
|
||||
// Adding a DB will take a while, so I'm moving these out of dev mode.
|
||||
pub scraper_keys: Option <Vec <ScraperKey <Valid30Days>>>,
|
||||
pub scraper_keys: Option <Vec <ScraperKey>>,
|
||||
|
||||
pub news_url: Option <String>,
|
||||
}
|
||||
|
@ -156,7 +154,7 @@ pub struct Config {
|
|||
pub address: IpAddr,
|
||||
pub port: Option <u16>,
|
||||
pub servers: HashMap <String, file::Server>,
|
||||
pub scraper_keys: HashMap <String, ScraperKey <Valid30Days>>,
|
||||
pub scraper_keys: HashMap <String, ScraperKey>,
|
||||
pub news_url: Option <String>,
|
||||
}
|
||||
|
||||
|
|
|
@ -78,36 +78,17 @@ impl Serialize for BlakeHashWrapper {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct Valid7Days;
|
||||
pub struct Valid30Days;
|
||||
//pub struct Valid90Days;
|
||||
|
||||
pub trait MaxValidDuration {
|
||||
fn dur () -> Duration;
|
||||
}
|
||||
|
||||
impl MaxValidDuration for Valid7Days {
|
||||
fn dur () -> Duration {
|
||||
Duration::days (7)
|
||||
}
|
||||
}
|
||||
|
||||
impl MaxValidDuration for Valid30Days {
|
||||
fn dur () -> Duration {
|
||||
Duration::days (30)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive (Deserialize)]
|
||||
pub struct ScraperKey <V: MaxValidDuration> {
|
||||
name: String,
|
||||
pub struct ScraperKey {
|
||||
pub name: String,
|
||||
|
||||
not_before: DateTime <Utc>,
|
||||
not_after: DateTime <Utc>,
|
||||
pub hash: BlakeHashWrapper,
|
||||
|
||||
#[serde (default)]
|
||||
_phantom: std::marker::PhantomData <V>,
|
||||
}
|
||||
|
||||
#[derive (Copy, Clone, Debug, PartialEq)]
|
||||
|
@ -121,21 +102,20 @@ pub enum KeyValidity {
|
|||
DurationNegative,
|
||||
}
|
||||
|
||||
impl <V: MaxValidDuration> ScraperKey <V> {
|
||||
impl ScraperKey {
|
||||
pub fn new_30_day <S: Into <String>> (name: S, input: &[u8]) -> Self {
|
||||
let now = Utc::now ();
|
||||
|
||||
Self {
|
||||
name: name.into (),
|
||||
not_before: now,
|
||||
not_after: now + V::dur (),
|
||||
not_after: now + Duration::days (30),
|
||||
hash: BlakeHashWrapper::from_key (input),
|
||||
_phantom: Default::default (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl <V: MaxValidDuration> ScraperKey <V> {
|
||||
impl ScraperKey {
|
||||
#[must_use]
|
||||
pub fn is_valid (&self, now: DateTime <Utc>, input: &[u8]) -> KeyValidity {
|
||||
use KeyValidity::*;
|
||||
|
@ -152,13 +132,6 @@ impl <V: MaxValidDuration> ScraperKey <V> {
|
|||
return DurationNegative;
|
||||
}
|
||||
|
||||
let max_dur = V::dur ();
|
||||
let actual_dur = self.not_after - self.not_before;
|
||||
|
||||
if actual_dur > max_dur {
|
||||
return DurationTooLong (max_dur);
|
||||
}
|
||||
|
||||
if now >= self.not_after {
|
||||
return Expired;
|
||||
}
|
||||
|
@ -196,12 +169,11 @@ mod tests {
|
|||
fn duration_negative () {
|
||||
let zero_time = Utc::now ();
|
||||
|
||||
let key = ScraperKey::<Valid30Days> {
|
||||
let key = ScraperKey {
|
||||
name: "automated testing".to_string (),
|
||||
not_before: zero_time + Duration::days (1 + 2),
|
||||
not_after: zero_time + Duration::days (1),
|
||||
hash: BlakeHashWrapper::from_key ("bad_password".as_bytes ()),
|
||||
_phantom: Default::default (),
|
||||
};
|
||||
|
||||
let err = DurationNegative;
|
||||
|
@ -215,46 +187,22 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn key_valid_too_long () {
|
||||
let zero_time = Utc::now ();
|
||||
|
||||
let key = ScraperKey::<Valid30Days> {
|
||||
name: "automated testing".to_string (),
|
||||
not_before: zero_time + Duration::days (1),
|
||||
not_after: zero_time + Duration::days (1 + 31),
|
||||
hash: BlakeHashWrapper::from_key ("bad_password".as_bytes ()),
|
||||
_phantom: Default::default (),
|
||||
};
|
||||
|
||||
let err = DurationTooLong (Duration::days (30));
|
||||
|
||||
for (input, expected) in &[
|
||||
(zero_time + Duration::days (0), err),
|
||||
(zero_time + Duration::days (2), err),
|
||||
(zero_time + Duration::days (100), err),
|
||||
] {
|
||||
assert_eq! (key.is_valid (*input, "bad_password".as_bytes ()), *expected);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normal_key () {
|
||||
let zero_time = Utc::now ();
|
||||
|
||||
let key = ScraperKey::<Valid30Days> {
|
||||
let key = ScraperKey {
|
||||
name: "automated testing".to_string (),
|
||||
not_before: zero_time + Duration::days (1),
|
||||
not_after: zero_time + Duration::days (1 + 30),
|
||||
not_after: zero_time + Duration::days (1 + 60),
|
||||
hash: BlakeHashWrapper::from_key ("bad_password".as_bytes ()),
|
||||
_phantom: Default::default (),
|
||||
};
|
||||
|
||||
for (input, expected) in &[
|
||||
(zero_time + Duration::days (0), ClockIsBehind),
|
||||
(zero_time + Duration::days (2), Valid),
|
||||
(zero_time + Duration::days (29), Valid),
|
||||
(zero_time + Duration::days (1 + 30), Expired),
|
||||
(zero_time + Duration::days (60 - 1), Valid),
|
||||
(zero_time + Duration::days (60 + 1), Expired),
|
||||
(zero_time + Duration::days (100), Expired),
|
||||
] {
|
||||
assert_eq! (key.is_valid (*input, "bad_password".as_bytes ()), *expected);
|
||||
|
@ -265,12 +213,11 @@ mod tests {
|
|||
fn wrong_key () {
|
||||
let zero_time = Utc::now ();
|
||||
|
||||
let key = ScraperKey::<Valid30Days> {
|
||||
let key = ScraperKey {
|
||||
name: "automated testing".to_string (),
|
||||
not_before: zero_time + Duration::days (1),
|
||||
not_after: zero_time + Duration::days (1 + 30),
|
||||
hash: BlakeHashWrapper::from_key ("bad_password".as_bytes ()),
|
||||
_phantom: Default::default (),
|
||||
};
|
||||
|
||||
for input in &[
|
||||
|
|
|
@ -127,8 +127,6 @@ async fn handle_http_request (
|
|||
return Err (UnknownServer);
|
||||
}
|
||||
|
||||
let user = get_user_name (&req);
|
||||
|
||||
let req = http_serde::RequestParts::from_hyper (req.method, uri.clone (), req.headers)
|
||||
.map_err (|_| BadRequest)?;
|
||||
|
||||
|
@ -136,11 +134,6 @@ async fn handle_http_request (
|
|||
|
||||
let req_id = rusty_ulid::generate_ulid_string ();
|
||||
|
||||
state.audit_log.push (AuditEvent::new (AuditData::WebClientGet {
|
||||
user,
|
||||
server_name: server_name.to_string (),
|
||||
uri,
|
||||
})).await;
|
||||
trace! ("Created request {}", req_id);
|
||||
|
||||
{
|
||||
|
@ -610,6 +603,13 @@ async fn handle_all (
|
|||
} => {
|
||||
let (parts, _) = req.into_parts ();
|
||||
|
||||
let user = get_user_name (&parts);
|
||||
state.audit_log.push (AuditEvent::new (AuditData::WebClientGet {
|
||||
user,
|
||||
server_name: listen_code.to_string (),
|
||||
uri: path.to_string (),
|
||||
})).await;
|
||||
|
||||
handle_http_request (parts, path.to_string (), &state, listen_code).await?
|
||||
},
|
||||
ClientServerList => handle_server_list (state, handlebars).await?,
|
||||
|
|
|
@ -121,6 +121,10 @@ pub enum AuditData {
|
|||
server: crate::config::file::Server,
|
||||
},
|
||||
RelayStart,
|
||||
ScraperGet {
|
||||
key_name: String,
|
||||
path: String,
|
||||
},
|
||||
WebClientGet {
|
||||
user: Option <String>,
|
||||
server_name: String,
|
||||
|
@ -312,7 +316,7 @@ impl Builder {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn scraper_key (mut self, key: crate::key_validity::ScraperKey <crate::key_validity::Valid30Days>)
|
||||
pub fn scraper_key (mut self, key: crate::key_validity::ScraperKey)
|
||||
-> Self
|
||||
{
|
||||
self.config.scraper_keys.insert (key.hash.encode_base64 (), key);
|
||||
|
|
|
@ -127,6 +127,11 @@ async fn api_v1 (
|
|||
)
|
||||
-> Result <Response <Body>, RequestError>
|
||||
{
|
||||
use crate::{
|
||||
AuditData,
|
||||
AuditEvent,
|
||||
};
|
||||
|
||||
let api_key = req.headers ().get ("X-ApiKey");
|
||||
|
||||
let api_key = match api_key {
|
||||
|
@ -138,6 +143,8 @@ async fn api_v1 (
|
|||
|
||||
let bad_key = || error_reply (StatusCode::FORBIDDEN, strings::FORBIDDEN);
|
||||
|
||||
let key_name;
|
||||
|
||||
{
|
||||
let config = state.config.read ().await;
|
||||
|
||||
|
@ -160,8 +167,15 @@ async fn api_v1 (
|
|||
return Ok (bad_key ()?);
|
||||
},
|
||||
}
|
||||
|
||||
key_name = expected_key.name.to_string ();
|
||||
}
|
||||
|
||||
state.audit_log.push (AuditEvent::new (AuditData::ScraperGet {
|
||||
key_name,
|
||||
path: path_rest.to_string (),
|
||||
})).await;
|
||||
|
||||
if path_rest == "test" {
|
||||
Ok (error_reply (StatusCode::OK, "You're valid!")?)
|
||||
}
|
||||
|
@ -224,7 +238,6 @@ mod tests {
|
|||
|
||||
use tokio::runtime::Runtime;
|
||||
use crate::{
|
||||
config,
|
||||
key_validity,
|
||||
};
|
||||
use super::*;
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
# How scraper keys work
|
||||
|
||||
Come up with a random passphrase:
|
||||
|
||||
`not this, this is a bogus passphrase for documentation`
|
||||
|
||||
Run that through the `hash-api-key` subcommand of any `ptth_relay` instance:
|
||||
|
||||
`ptth_relay hash-api-key`
|
||||
|
||||
You'll get a hash like this:
|
||||
|
||||
`RUWt1hQQuHIRjftOdgeZf0PG/DtAmIaMqot/nwBAZXQ=`
|
||||
|
||||
Make sure that gets into the relay's config file, `ptth_relay.toml`:
|
||||
|
||||
```
|
||||
[[scraper_keys]]
|
||||
name = "shudder_mummy"
|
||||
not_before = "2021-08-27T19:20:25-05:00"
|
||||
not_after = "2031-08-27T19:20:25-05:00"
|
||||
hash = "RUWt1hQQuHIRjftOdgeZf0PG/DtAmIaMqot/nwBAZXQ="
|
||||
```
|
||||
|
||||
Use curl to like, try it out:
|
||||
|
||||
```
|
||||
curl \
|
||||
--header "X-ApiKey: not this, this is a bogus passphrase for documentation" \
|
||||
http://localhost:4000/scraper/v1/test
|
||||
```
|
Loading…
Reference in New Issue