use std::{ sync::Arc, }; use hyper::{ Body, Request, Response, StatusCode, }; use tracing::{ error, instrument, }; use crate::{ RequestError, error_reply, key_validity::KeyValidity, prefix_match, relay_state::RelayState, }; #[instrument (level = "trace", skip (req, state))] pub async fn handle_scraper_api_v1 ( req: Request , state: Arc , path_rest: &str ) -> Result , RequestError> { let api_key = req.headers ().get ("X-ApiKey"); let api_key = match api_key { None => return Ok (error_reply (StatusCode::FORBIDDEN, "Can't run scraper without an API key")?), Some (x) => x, }; let bad_key = || error_reply (StatusCode::FORBIDDEN, "403 Forbidden"); { let config = state.config.read ().await; let dev_mode = match &config.iso.dev_mode { None => return Ok (bad_key ()?), Some (x) => x, }; let expected_key = match &dev_mode.scraper_key { None => return Ok (bad_key ()?), Some (x) => x, }; let now = chrono::Utc::now (); match expected_key.is_valid (now, api_key.as_bytes ()) { KeyValidity::Valid => (), KeyValidity::WrongKey (bad_hash) => { error! ("Bad scraper key with hash {:?}", bad_hash); return Ok (bad_key ()?); } err => { error! ("Bad scraper key {:?}", err); return Ok (bad_key ()?); }, } } if path_rest == "test" { Ok (error_reply (StatusCode::OK, "You're valid!")?) } else { Ok (error_reply (StatusCode::NOT_FOUND, "Unknown API endpoint")?) } } #[instrument (level = "trace", skip (req, state))] pub async fn handle_scraper_api ( req: Request , state: Arc , path_rest: &str ) -> 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 } else if let Some (rest) = prefix_match ("api/", path_rest) { handle_scraper_api_v1 (req, state, rest).await } else { Ok (error_reply (StatusCode::NOT_FOUND, "Unknown scraper API version")?) } } #[cfg (test)] mod tests { use std::{ convert::TryFrom, }; use tokio::runtime::Runtime; use crate::{ config, key_validity, }; use super::*; #[test] fn auth () { let input = Request::builder () .method ("GET") .uri ("http://127.0.0.1:4000/scraper/v1/test") .header ("X-ApiKey", "bogus") .body (Body::empty ()) .unwrap (); let config_file = config::file::Config { port: Some (4000), 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 mut rt = Runtime::new ().expect ("Can't create runtime for testing"); rt.block_on (async move { let actual = handle_scraper_api (input, relay_state, "").await; let actual = actual.expect ("Relay didn't respond"); }); } }