add unregistered servers page

main
_ 2021-03-15 19:11:57 +00:00
parent 9648a9853c
commit 699cb671ec
6 changed files with 139 additions and 7 deletions

View File

@ -52,10 +52,10 @@ pub mod file {
pub iso: Isomorphic, pub iso: Isomorphic,
pub port: Option <u16>, pub port: Option <u16>,
pub servers: Vec <Server>, pub servers: Option <Vec <Server>>,
// Adding a DB will take a while, so I'm moving these out of dev mode. // Adding a DB will take a while, so I'm moving these out of dev mode.
pub scraper_keys: Vec <ScraperKey <Valid30Days>>, pub scraper_keys: Option <Vec <ScraperKey <Valid30Days>>>,
} }
} }
@ -73,13 +73,14 @@ impl TryFrom <file::Config> for Config {
type Error = ConfigError; type Error = ConfigError;
fn try_from (f: file::Config) -> Result <Self, Self::Error> { fn try_from (f: file::Config) -> Result <Self, Self::Error> {
let servers = f.servers.into_iter () let servers = f.servers.unwrap_or_else (|| vec! []);
.map (|server| Ok::<_, ConfigError> ((server.name.clone (), server))); let servers = servers.into_iter ().map (|server| Ok::<_, ConfigError> ((server.name.clone (), server)));
let servers = itertools::process_results (servers, |i| HashMap::from_iter (i))?; let servers = itertools::process_results (servers, |i| HashMap::from_iter (i))?;
let scraper_keys = f.scraper_keys.unwrap_or_else (|| vec! []);
let scraper_keys = if f.iso.enable_scraper_api { let scraper_keys = if f.iso.enable_scraper_api {
HashMap::from_iter (f.scraper_keys.into_iter ().map (|key| (key.hash.encode_base64 (), key))) HashMap::from_iter (scraper_keys.into_iter ().map (|key| (key.hash.encode_base64 (), key)))
} }
else { else {
Default::default () Default::default ()

View File

@ -253,6 +253,18 @@ struct ServerListPage <'a> {
servers: Vec <ServerEntry <'a>>, servers: Vec <ServerEntry <'a>>,
} }
#[derive (Serialize)]
struct UnregisteredServer {
name: String,
tripcode: String,
last_seen: String,
}
#[derive (Serialize)]
struct UnregisteredServerListPage {
unregistered_servers: Vec <UnregisteredServer>,
}
async fn handle_server_list_internal (state: &Arc <RelayState>) async fn handle_server_list_internal (state: &Arc <RelayState>)
-> ServerListPage <'static> -> ServerListPage <'static>
{ {
@ -294,6 +306,14 @@ async fn handle_server_list_internal (state: &Arc <RelayState>)
} }
} }
async fn handle_unregistered_servers_internal (state: &Arc <RelayState>)
-> UnregisteredServerListPage
{
UnregisteredServerListPage {
unregistered_servers: vec! [],
}
}
async fn handle_server_list ( async fn handle_server_list (
state: Arc <RelayState>, state: Arc <RelayState>,
handlebars: Arc <Handlebars <'static>> handlebars: Arc <Handlebars <'static>>
@ -305,6 +325,17 @@ async fn handle_server_list (
Ok (ok_reply (s)?) Ok (ok_reply (s)?)
} }
async fn handle_unregistered_servers (
state: Arc <RelayState>,
handlebars: Arc <Handlebars <'static>>
) -> Result <Response <Body>, RequestError>
{
let page = handle_unregistered_servers_internal (&state).await;
let s = handlebars.render ("unregistered_servers", &page)?;
Ok (ok_reply (s)?)
}
async fn handle_endless_sink (req: Request <Body>) -> Result <Response <Body>, http::Error> async fn handle_endless_sink (req: Request <Body>) -> Result <Response <Body>, http::Error>
{ {
let (_parts, mut body) = req.into_parts (); let (_parts, mut body) = req.into_parts ();
@ -431,6 +462,9 @@ async fn handle_all (
Ok (error_reply (StatusCode::BAD_REQUEST, "Bad URI format")?) Ok (error_reply (StatusCode::BAD_REQUEST, "Bad URI format")?)
} }
} }
else if path == "/frontend/unregistered_servers" {
Ok (handle_unregistered_servers (state, handlebars).await?)
}
else if let Some (rest) = prefix_match ("/frontend/debug/", &path) { else if let Some (rest) = prefix_match ("/frontend/debug/", &path) {
if rest == "" { if rest == "" {
let s = handlebars.render ("debug", &())?; let s = handlebars.render ("debug", &())?;
@ -479,6 +513,7 @@ pub fn load_templates (asset_root: &Path)
("debug", "debug.hbs"), ("debug", "debug.hbs"),
("root", "root.hbs"), ("root", "root.hbs"),
("server_list", "server_list.hbs"), ("server_list", "server_list.hbs"),
("unregistered_servers", "unregistered_servers.hbs"),
] { ] {
handlebars.register_template_file (k, &asset_root.join (v))?; handlebars.register_template_file (k, &asset_root.join (v))?;
} }

View File

@ -70,6 +70,12 @@ impl Default for ServerStatus {
} }
} }
pub struct RejectedServer {
pub name: String,
pub tripcode: blake3::Hash,
pub seen: DateTime <Utc>,
}
pub struct RelayState { pub struct RelayState {
pub config: RwLock <Config>, pub config: RwLock <Config>,
@ -82,6 +88,9 @@ pub struct RelayState {
pub shutdown_watch_tx: watch::Sender <bool>, pub shutdown_watch_tx: watch::Sender <bool>,
pub shutdown_watch_rx: watch::Receiver <bool>, pub shutdown_watch_rx: watch::Receiver <bool>,
// List of recently rejected server names (Used to approve servers)
pub unregistered_servers: RwLock <Vec <RejectedServer>>,
} }
impl TryFrom <Config> for RelayState { impl TryFrom <Config> for RelayState {
@ -97,6 +106,7 @@ impl TryFrom <Config> for RelayState {
response_rendezvous: Default::default (), response_rendezvous: Default::default (),
shutdown_watch_tx, shutdown_watch_tx,
shutdown_watch_rx, shutdown_watch_rx,
unregistered_servers: Default::default (),
}) })
} }
} }

View File

@ -54,18 +54,31 @@ pub async fn handle_listen (
let trip_error = || Ok (error_reply (StatusCode::UNAUTHORIZED, "Bad X-ApiKey")?); let trip_error = || Ok (error_reply (StatusCode::UNAUTHORIZED, "Bad X-ApiKey")?);
let now = Utc::now ();
let actual_tripcode = blake3::hash (api_key);
let expected_tripcode = { let expected_tripcode = {
let config = state.config.read ().await; let config = state.config.read ().await;
match config.servers.get (&watcher_code) { match config.servers.get (&watcher_code) {
None => { None => {
error! ("Denied http_listen for non-existent server name {}", watcher_code); error! ("Denied http_listen for non-existent server name {}", watcher_code);
let mut guard = state.unregistered_servers.write ().await;
guard.push (crate::RejectedServer {
name: watcher_code,
tripcode: actual_tripcode,
seen: now,
});
while guard.len () > 20 {
guard.remove (0);
}
return trip_error (); return trip_error ();
}, },
Some (x) => *(*x).tripcode, Some (x) => *(*x).tripcode,
} }
}; };
let actual_tripcode = blake3::hash (api_key);
if expected_tripcode != actual_tripcode { if expected_tripcode != actual_tripcode {
error! ("Denied http_listen for bad tripcode {}", base64::encode (actual_tripcode.as_bytes ())); error! ("Denied http_listen for bad tripcode {}", base64::encode (actual_tripcode.as_bytes ()));
@ -81,7 +94,7 @@ pub async fn handle_listen (
let mut status = server_status.entry (watcher_code.clone ()).or_insert_with (Default::default); let mut status = server_status.entry (watcher_code.clone ()).or_insert_with (Default::default);
status.last_seen = Utc::now (); status.last_seen = now;
} }
let (tx, rx) = oneshot::channel (); let (tx, rx) = oneshot::channel ();

View File

@ -44,6 +44,8 @@ AIABAACAAQAAgAEAAIABAACAAQAAgAEAAIABAACAAQAA" rel="icon" type="image/x-icon" />
<div style="color: red;">Relay is in dev mode. This should never be seen in production!</div> <div style="color: red;">Relay is in dev mode. This should never be seen in production!</div>
{{/if}} {{/if}}
<a href="../unregistered_servers">Unregistered servers</a>
<div style="padding-top: 1em;"> <div style="padding-top: 1em;">
{{#if servers}} {{#if servers}}
<table class="entry_list"> <table class="entry_list">

View File

@ -0,0 +1,71 @@
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link href="data:image/x-icon;base64,AAABAAEAEBAQAAEABAAoAQAAFgAAACgAAAAQAAAAIAAAAAEABAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAlJSUAAGIAAP///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAIiIiIgAAAAAAAAAgAAAAAAAAAAAAADIiIiIi
IiIjMhERERERESMyERERERERIzIREREREREjMhERIRERESMyERIiIiERIzIRESEREREjMhERERER
ESMyERERERERIzIREREREREjMiIiIiIiIiMAAAAAAAAAAAAAAAAAAAAAAAAAAIABAACAAQAAgAEA
AIABAACAAQAAgAEAAIABAACAAQAAgAEAAIABAACAAQAA" rel="icon" type="image/x-icon" />
<style>
body {
font-family: sans-serif;
}
td {
padding: 0px;
}
td > * {
padding: 20px;
display: block;
}
.entry {
}
.grey {
color: #888;
text-align: right;
}
.entry_list {
width: 100%;
border-collapse: collapse;
}
tbody tr:nth-child(odd) {
background-color: #ddd;
}
</style>
<title>Unregistered servers</title>
</head>
<body>
<h1>Unregistered servers</h1>
<div style="padding-top: 1em;">
{{#if unregistered_servers}}
<table class="entry_list">
<thead>
<tr>
<th>Name</th>
<th>Tripcode</th>
<th>Time seen</th>
</tr>
</thead>
<tbody>
{{#each unregistered_servers}}
<tr>
<td>{{this.name}}</td>
<td>{{this.tripcode}}</td>
<td><span class="grey">{{this.last_seen}}</span></td>
</tr>
{{/each}}
</tbody>
</table>
{{else}}
(No unregistered servers have reported yet)
{{/if}}
</div>
</body>
</html>