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");
});
}
}