diff --git a/crates/ptth_relay/src/config.rs b/crates/ptth_relay/src/config.rs index 9ff2751..3e2c0d3 100644 --- a/crates/ptth_relay/src/config.rs +++ b/crates/ptth_relay/src/config.rs @@ -2,7 +2,9 @@ #![allow (clippy::redundant_closure)] use std::{ - collections::HashMap, + collections::{ + HashMap, + }, convert::{TryFrom}, net::IpAddr, path::Path, @@ -23,6 +25,7 @@ use crate::{ pub mod machine_editable { use std::{ + collections::BTreeMap, path::Path, }; @@ -30,22 +33,25 @@ pub mod machine_editable { use super::file::Server; - #[derive (Default, Deserialize, Serialize)] - pub struct Config { + #[derive (Deserialize, Serialize)] + pub struct ConfigFile { pub servers: Vec , - pub debug_toggle: bool, } - impl Config { + #[derive (Default)] + pub struct Config { + pub servers: BTreeMap , + } + + impl ConfigFile { pub fn from_file (path: &Path) -> Result { let config_s = std::fs::read_to_string (path)?; - let new_config: Config = toml::from_str (&config_s)?; - - Ok (new_config) + Ok (toml::from_str (&config_s)?) } - pub async fn save (&self, path: &Path) -> Result <(), crate::ConfigError> { + pub async fn save (&self, path: &Path) -> Result <(), crate::ConfigError> + { let s = toml::to_string (self)?; // This is way easier in C++ but also not safe let mut temp_path = path.file_name ().unwrap ().to_os_string (); @@ -56,6 +62,33 @@ pub mod machine_editable { Ok (()) } } + + impl Config { + pub fn from_file (path: &Path) -> Result + { + let c = ConfigFile::from_file (path)?; + + let servers = c.servers.into_iter () + .map (|s| (s.name.clone (), s)) + .collect (); + + Ok (Self { + servers, + }) + } + + pub async fn save (&self, path: &Path) -> Result <(), crate::ConfigError> + { + let servers = self.servers.values () + .cloned ().into_iter ().collect (); + + let c = ConfigFile { + servers, + }; + c.save (path).await?; + Ok (()) + } + } } /// Config fields as they are loaded from the config file @@ -69,7 +102,7 @@ pub mod file { Valid30Days, }; - #[derive (Deserialize, Serialize)] + #[derive (Clone, Debug, Deserialize, Serialize)] pub struct Server { /// This is duplicated in the hashmap, but it's not a problem pub name: String, diff --git a/crates/ptth_relay/src/lib.rs b/crates/ptth_relay/src/lib.rs index e01f6fa..ad9da70 100644 --- a/crates/ptth_relay/src/lib.rs +++ b/crates/ptth_relay/src/lib.rs @@ -114,7 +114,7 @@ fn get_user_name (req: &http::request::Parts) async fn handle_http_request ( req: http::request::Parts, uri: String, - state: Arc , + state: &Relay, server_name: &str ) -> Result , RequestError> @@ -300,7 +300,7 @@ struct AuditEntryPretty { data_pretty: String, } -async fn handle_server_list_internal (state: &Arc ) +async fn handle_server_list_internal (state: &Relay) -> ServerListPage <'static> { use LastSeen::*; @@ -357,7 +357,7 @@ async fn handle_server_list_internal (state: &Arc ) } } -async fn handle_unregistered_servers_internal (state: &Arc ) +async fn handle_unregistered_servers_internal (state: &Relay) -> UnregisteredServerListPage { use LastSeen::*; @@ -366,6 +366,13 @@ async fn handle_unregistered_servers_internal (state: &Arc ) let mut server_list = state.unregistered_servers.to_vec ().await; + { + let me_config = state.me_config.read ().await; + server_list = server_list.into_iter () + .filter (|s| ! me_config.servers.contains_key (&s.name)) + .collect (); + } + server_list.sort_by_key (|s| { (s.name.clone (), s.tripcode.as_bytes ().clone (), now - s.seen) }); @@ -395,7 +402,7 @@ async fn handle_unregistered_servers_internal (state: &Arc ) } } -async fn handle_audit_log_internal (state: &Arc ) +async fn handle_audit_log_internal (state: &Relay) -> AuditLogPage { let utc_now = Utc::now (); @@ -413,7 +420,7 @@ async fn handle_audit_log_internal (state: &Arc ) } async fn handle_server_list ( - state: Arc , + state: &Relay, handlebars: Arc > ) -> Result , RequestError> { @@ -424,7 +431,7 @@ async fn handle_server_list ( } async fn handle_unregistered_servers ( - state: Arc , + state: &Relay, handlebars: Arc > ) -> Result , RequestError> { @@ -435,11 +442,11 @@ async fn handle_unregistered_servers ( } async fn handle_audit_log ( - state: Arc , + state: &Relay, handlebars: Arc > ) -> Result , RequestError> { - let page = handle_audit_log_internal (&state).await; + let page = handle_audit_log_internal (state).await; let s = handlebars.render ("audit_log", &page)?; Ok (ok_reply (s)?) @@ -515,7 +522,7 @@ async fn handle_endless_source (gib: usize, throttle: Option ) .body (Body::wrap_stream (ReceiverStream::new (rx))) } -async fn handle_gen_scraper_key (_state: Arc ) +async fn handle_gen_scraper_key (_state: &Relay) -> Result , http::Error> { let key = ptth_core::gen_key (); @@ -528,24 +535,25 @@ async fn handle_gen_scraper_key (_state: Arc ) .body (Body::from (body)) } -async fn handle_register_server (req: Request , state: Arc ) +async fn handle_register_server (req: Request , state: &Relay) -> Result <(), anyhow::Error> { let (parts, body) = req.into_parts (); let user = get_user_name (&parts); let form_data = read_body_limited (body, 1_024).await?; - let reg: relay_state::Registration = serde_urlencoded::from_bytes (&form_data)?; + let server: crate::config::file::Server = serde_urlencoded::from_bytes (&form_data)?; state.audit_log.push (AuditEvent::new (AuditData::RegisterServer { user, - reg: reg.clone (), + server: server.clone (), })).await; { let mut me_config = state.me_config.write ().await; - //me_config.servers. + me_config.servers.insert (server.name.clone (), server); + me_config.save (Path::new ("data/ptth_relay_me_config.toml")).await?; } Ok (()) @@ -577,6 +585,8 @@ async fn handle_all ( { use routing::Route::*; + let state = &*state; + // The path is cloned here, so it's okay to consume the request // later. let path = req.uri ().path ().to_string (); @@ -592,41 +602,18 @@ async fn handle_all ( } => { let (parts, _) = req.into_parts (); - handle_http_request (parts, path.to_string (), state, listen_code).await? + handle_http_request (parts, path.to_string (), &state, listen_code).await? }, ClientServerList => handle_server_list (state, handlebars).await?, ClientUnregisteredServers => handle_unregistered_servers (state, handlebars).await?, Debug => { - #[derive (Serialize)] - struct DebugPage { - persistent_toggle: bool, - } - - let page; - - { - let guard = state.me_config.read ().await; - page = DebugPage { - persistent_toggle: guard.debug_toggle, - }; - } - - let s = handlebars.render ("debug", &page)?; + let s = handlebars.render ("debug", &())?; ok_reply (s)? }, DebugEndlessSink => handle_endless_sink (req).await?, DebugEndlessSource (throttle) => handle_endless_source (1, throttle).await?, DebugGenKey => handle_gen_scraper_key (state).await?, DebugMysteriousError => return Err (RequestError::Mysterious), - DebugToggle => { - trace! ("Toggling debug toggle"); - { - let mut guard = state.me_config.write ().await; - guard.debug_toggle = ! guard.debug_toggle; - guard.save (Path::new ("data/ptth_relay_me_config.toml")).await.unwrap (); - } - error_reply (StatusCode::OK, "Toggled.")? - }, ErrorBadUriFormat => error_reply (StatusCode::BAD_REQUEST, "Bad URI format")?, ErrorCantPost => { error! ("Can't POST {}", path); @@ -637,7 +624,7 @@ async fn handle_all ( RegisterServer => { match handle_register_server (req, state).await { Ok (_) => Response::builder () - .status (StatusCode::TEMPORARY_REDIRECT) + .status (StatusCode::SEE_OTHER) .header ("location", "unregistered_servers") .body (Body::from ("Success. Redirecting..."))?, Err (e) => error_reply (StatusCode::BAD_REQUEST, &format! ("{:?}", e))?, @@ -671,6 +658,41 @@ async fn handle_all ( Ok (response) } +async fn check_server_api_key (state: &Relay, name: &str, req: &http::request::Parts) +-> Result <(), anyhow::Error> +{ + let api_key = req.headers.get ("X-ApiKey"); + + let api_key = match api_key { + None => bail! ("Can't run server without an API key"), + Some (x) => x, + }; + + let actual_tripcode = blake3::hash (api_key.as_bytes ()); + + let expected_tripcode = { + let config = state.config.read ().await; + + match config.servers.get (name) { + None => { + state.unregistered_servers.push (crate::RejectedServer { + name: name.to_string (), + tripcode: actual_tripcode, + seen: Utc::now (), + }).await; + bail! ("Denied API request for non-existent server name {}", name); + }, + Some (x) => *(*x).tripcode, + } + }; + + if expected_tripcode != actual_tripcode { + bail! ("Denied API request for bad tripcode {}", base64::encode (actual_tripcode.as_bytes ())); + } + + Ok (()) +} + fn load_templates (asset_root: &Path) -> Result , RelayError> { @@ -761,6 +783,7 @@ pub async fn run_relay ( async { Ok::<_, Infallible> (handle_all (req, state, handlebars).await.unwrap_or_else (|e| { + use RequestError::*; error! ("{}", e); let status_code = match &e { diff --git a/crates/ptth_relay/src/relay_state.rs b/crates/ptth_relay/src/relay_state.rs index 90e3925..66107e9 100644 --- a/crates/ptth_relay/src/relay_state.rs +++ b/crates/ptth_relay/src/relay_state.rs @@ -7,7 +7,6 @@ use std::{ use chrono::{DateTime, Utc}; use dashmap::DashMap; -use serde::Deserialize; use tokio::sync::{ Mutex, RwLock, @@ -119,7 +118,7 @@ pub struct AuditEvent { pub enum AuditData { RegisterServer { user: Option , - reg: Registration, + server: crate::config::file::Server, }, RelayStart, WebClientGet { @@ -130,12 +129,6 @@ pub enum AuditData { }, } -#[derive (Clone, Debug, Deserialize)] -pub struct Registration { - name: String, - tripcode: String, -} - impl AuditEvent { pub fn new (data: AuditData) -> Self { Self { diff --git a/crates/ptth_relay/src/routing.rs b/crates/ptth_relay/src/routing.rs index 87f8ce7..ff8ccf7 100644 --- a/crates/ptth_relay/src/routing.rs +++ b/crates/ptth_relay/src/routing.rs @@ -15,7 +15,6 @@ pub enum Route <'a> { DebugEndlessSource (Option ), DebugGenKey, DebugMysteriousError, - DebugToggle, ErrorBadUriFormat, ErrorCantPost, ErrorMethodNotAllowed, @@ -43,9 +42,6 @@ pub fn route_url <'a> (method: &Method, path: &'a str) -> Route <'a> { else if path == "/frontend/debug/endless_sink" { Route::DebugEndlessSink } - else if path == "/frontend/debug/toggle" { - Route::DebugToggle - } else if path == "/frontend/register" { Route::RegisterServer } diff --git a/crates/ptth_relay/src/scraper_api.rs b/crates/ptth_relay/src/scraper_api.rs index ba1ecc5..ef49d9c 100644 --- a/crates/ptth_relay/src/scraper_api.rs +++ b/crates/ptth_relay/src/scraper_api.rs @@ -1,6 +1,5 @@ use std::{ collections::HashMap, - sync::Arc, }; use chrono::{DateTime, Utc}; @@ -65,7 +64,7 @@ pub struct ServerList { pub servers: Vec , } -pub async fn v1_server_list (state: &Arc ) +pub async fn v1_server_list (state: &Relay) -> ServerList { // name --> display_name @@ -112,7 +111,7 @@ pub async fn v1_server_list (state: &Arc ) #[instrument (level = "trace", skip (req, state))] async fn api_v1 ( req: Request , - state: Arc , + state: &Relay, path_rest: &str ) -> Result , RequestError> @@ -170,7 +169,7 @@ async fn api_v1 ( // This is ugly. I don't like having scraper_api know about the // crate root. - Ok (crate::handle_http_request (parts, path, state, &listen_code).await?) + Ok (crate::handle_http_request (parts, path, &state, &listen_code).await?) } else { Ok (error_reply (StatusCode::BAD_REQUEST, "Bad URI format")?) @@ -184,7 +183,7 @@ async fn api_v1 ( #[instrument (level = "trace", skip (req, state))] pub async fn handle ( req: Request , - state: Arc , + state: &Relay, path_rest: &str ) -> Result , RequestError> @@ -298,9 +297,7 @@ mod tests { let relay_state = builder.build ().expect ("Can't create relay state"); - let relay_state = Arc::new (relay_state); - - let actual = super::handle (input, relay_state, self.path_rest).await; + let actual = super::handle (input, &relay_state, self.path_rest).await; let actual = actual.expect ("Relay didn't respond"); let (actual_head, actual_body) = actual.into_parts (); diff --git a/crates/ptth_relay/src/server_endpoint.rs b/crates/ptth_relay/src/server_endpoint.rs index a8cc899..ace791f 100644 --- a/crates/ptth_relay/src/server_endpoint.rs +++ b/crates/ptth_relay/src/server_endpoint.rs @@ -1,5 +1,4 @@ use std::{ - sync::Arc, time::Duration, }; @@ -44,7 +43,7 @@ use super::{ // Step 1 pub async fn handle_listen ( - state: Arc , + state: &Relay, watcher_code: String, api_key: &[u8], ) @@ -140,7 +139,7 @@ pub async fn handle_listen ( pub async fn handle_response ( req: Request , - state: Arc , + state: &Relay, req_id: String, ) -> Result , HandleHttpResponseError> diff --git a/handlebars/relay/unregistered_servers.hbs b/handlebars/relay/unregistered_servers.hbs index a0dec22..b4b585b 100644 --- a/handlebars/relay/unregistered_servers.hbs +++ b/handlebars/relay/unregistered_servers.hbs @@ -29,7 +29,6 @@ AIABAACAAQAAgAEAAIABAACAAQAAgAEAAIABAACAAQAA" rel="icon" type="image/x-icon" /> border-collapse: collapse; } .padded { - display: block; padding: 20px; } .submit { @@ -62,13 +61,11 @@ AIABAACAAQAAgAEAAIABAACAAQAAgAEAAIABAACAAQAA" rel="icon" type="image/x-icon" /> {{this.name}} {{this.tripcode}} - {{this.last_seen}}