diff --git a/Cargo.lock b/Cargo.lock index dce88da..edeec04 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1182,6 +1182,7 @@ version = "0.1.0" dependencies = [ "base64 0.12.3", "blake3", + "chrono", "ptth_relay", "ptth_server", "reqwest", diff --git a/Cargo.toml b/Cargo.toml index 896475b..7532e78 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ license = "AGPL-3.0" base64 = "0.12.3" blake3 = "0.3.7" +chrono = {version = "0.4.19", features = ["serde"]} reqwest = { version = "0.10.8", features = ["stream"] } tokio = { version = "0.2.22", features = ["full"] } tracing = "0.1.21" diff --git a/crates/ptth_relay/src/config.rs b/crates/ptth_relay/src/config.rs index b444810..d152293 100644 --- a/crates/ptth_relay/src/config.rs +++ b/crates/ptth_relay/src/config.rs @@ -55,6 +55,7 @@ pub mod file { // Stuff we actually need at runtime pub struct Config { + pub port: Option , pub servers: HashMap , pub iso: file::Isomorphic, } @@ -69,6 +70,7 @@ impl TryFrom for Config { let servers = itertools::process_results (servers, |i| HashMap::from_iter (i))?; Ok (Self { + port: f.port, servers, iso: f.iso, }) diff --git a/crates/ptth_relay/src/key_validity.rs b/crates/ptth_relay/src/key_validity.rs index 2bc2593..c75603e 100644 --- a/crates/ptth_relay/src/key_validity.rs +++ b/crates/ptth_relay/src/key_validity.rs @@ -84,9 +84,9 @@ impl MaxValidDuration for Valid7Days { #[derive (Deserialize)] pub struct ScraperKey { - pub not_before: DateTime , - pub not_after: DateTime , - pub hash: BlakeHashWrapper, + not_before: DateTime , + not_after: DateTime , + hash: BlakeHashWrapper, #[serde (default)] _phantom: std::marker::PhantomData , @@ -103,6 +103,19 @@ pub enum KeyValidity { DurationNegative, } +impl ScraperKey { + pub fn new (input: &[u8]) -> Self { + let now = Utc::now (); + + Self { + not_before: now, + not_after: now + Duration::days (7), + hash: BlakeHashWrapper::from_key (input), + _phantom: Default::default (), + } + } +} + impl ScraperKey { pub fn is_valid (&self, now: DateTime , input: &[u8]) -> KeyValidity { use KeyValidity::*; diff --git a/crates/ptth_relay/src/lib.rs b/crates/ptth_relay/src/lib.rs index 49b1a3c..cf45021 100644 --- a/crates/ptth_relay/src/lib.rs +++ b/crates/ptth_relay/src/lib.rs @@ -471,6 +471,12 @@ async fn handle_scraper_api ( ) -> Result , RequestError> { + { + if ! state.config.read ().await.iso.enable_scraper_auth { + return Ok (error_reply (StatusCode::FORBIDDEN, "Scraper API disabled")?); + } + } + if let Some (rest) = prefix_match ("v1/", path_rest) { handle_scraper_api_v1 (req, state, rest).await } @@ -590,11 +596,6 @@ pub async fn run_relay ( ) -> Result <(), RelayError> { - let addr = SocketAddr::from (( - [0, 0, 0, 0], - 4000, - )); - if let Some (config_reload_path) = config_reload_path { let state_2 = state.clone (); tokio::spawn (async move { @@ -619,6 +620,11 @@ pub async fn run_relay ( } }); + let addr = SocketAddr::from (( + [0, 0, 0, 0], + state.config.read ().await.port.unwrap_or (4000), + )); + let server = Server::bind (&addr) .serve (make_svc); diff --git a/issues/2020-12Dec/auth-route-YNQAQKJS.md b/issues/2020-12Dec/auth-route-YNQAQKJS.md index 12f2e38..05e9464 100644 --- a/issues/2020-12Dec/auth-route-YNQAQKJS.md +++ b/issues/2020-12Dec/auth-route-YNQAQKJS.md @@ -36,7 +36,8 @@ stronger is ready. - (X) Add hash of 1 scraper key to ptth_relay.toml, with 1 week expiration - (X) Accept scraper key for some testing endpoint - (X) (POC) Test with curl -- ( ) Clean up scraper endpoint +- (X) Clean up scraper endpoint +- ( ) Add end-to-end tests for scraper endpoint - ( ) Manually create SQLite DB for scraper keys, add 1 hash - ( ) Impl DB reads - ( ) Remove scraper key from config file diff --git a/src/tests.rs b/src/tests.rs index 5de6e56..55856bb 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1,5 +1,5 @@ use std::{ - convert::TryFrom, + convert::{TryFrom, TryInto}, sync::{ Arc, }, @@ -13,17 +13,17 @@ use tokio::{ time::delay_for, }; +use reqwest::Client; +use tracing::{debug, info}; + #[test] fn end_to_end () { - use reqwest::Client; - use tracing::{debug, info}; - use ptth_relay::key_validity::BlakeHashWrapper; // Prefer this form for tests, since all tests share one process // and we don't care if another test already installed a subscriber. - tracing_subscriber::fmt ().try_init ().ok (); + //tracing_subscriber::fmt ().try_init ().ok (); let mut rt = Runtime::new ().expect ("Can't create runtime for testing"); // Spawn the root task @@ -126,3 +126,52 @@ fn end_to_end () { info! ("Server stopped"); }); } + +#[test] +fn scraper_endpoints () { + let mut rt = Runtime::new ().expect ("Can't create runtime for testing"); + + rt.block_on (async { + use ptth_relay::*; + + let config_file = config::file::Config { + port: Some (4001), + servers: vec! [ + + ], + iso: config::file::Isomorphic { + enable_scraper_auth: true, + dev_mode: Some (config::file::DevMode { + scraper_key: Some (key_validity::ScraperKey::new (b"bogus")), + }), + }, + }; + + let config = config::Config::try_from (config_file).expect ("Can't load config"); + + let relay_state = Arc::new (RelayState::try_from (config).expect ("Can't create relay state")); + let relay_state_2 = relay_state.clone (); + let (stop_relay_tx, stop_relay_rx) = oneshot::channel (); + let task_relay = spawn (async move { + run_relay (relay_state_2, stop_relay_rx, None).await + }); + + let relay_url = "http://127.0.0.1:4001"; + + let mut headers = reqwest::header::HeaderMap::new (); + headers.insert ("X-ApiKey", "bogus".try_into ().unwrap ()); + + let client = Client::builder () + .default_headers (headers) + .timeout (Duration::from_secs (2)) + .build ().expect ("Couldn't build HTTP client"); + + let resp = client.get (&format! ("{}/scraper/api/test", relay_url)) + .send ().await.expect ("Couldn't check if relay is up").bytes ().await.expect ("Couldn't check if relay is up"); + + assert_eq! (resp, "You're valid!\n"); + + stop_relay_tx.send (()).expect ("Couldn't shut down relay"); + task_relay.await.expect ("Couldn't join relay").expect ("Relay error"); + }); +}