generated from PlexSheep/rs-base
feat(meta): make datastructures ready for multiple challenges
cargo devel CI / cargo CI (push) Successful in 2m3s
Details
cargo devel CI / cargo CI (push) Successful in 2m3s
Details
This commit is contained in:
parent
515e61dc45
commit
f945eea904
|
@ -29,11 +29,8 @@ tokio = { version = "1.40.0", features = [
|
|||
"rt",
|
||||
"sync",
|
||||
] }
|
||||
tracing = "0.1.40"
|
||||
tracing-appender = "0.2.3"
|
||||
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
||||
warp = { version = "0.3.7", optional = true }
|
||||
|
||||
[features]
|
||||
default = ["admin-interface"]
|
||||
admin-interface = ["dep:warp", "dep:minijinja"]
|
||||
default = ["meta-interface"]
|
||||
meta-interface = ["dep:warp", "dep:minijinja"]
|
||||
|
|
|
@ -1,146 +0,0 @@
|
|||
use std::fmt::Display;
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Result;
|
||||
use libpt::log::error;
|
||||
use libpt::log::info;
|
||||
use libpt::log::tracing;
|
||||
use libpt::log::warn;
|
||||
use minijinja::context;
|
||||
use minijinja::Environment;
|
||||
use thiserror::Error;
|
||||
use warp::http::StatusCode;
|
||||
use warp::reject;
|
||||
use warp::reply::Response;
|
||||
use warp::Filter;
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::vault::VaultRef;
|
||||
|
||||
use super::Descriptions;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Service<'tp> {
|
||||
vault: VaultRef,
|
||||
config: Config,
|
||||
env: Environment<'tp>,
|
||||
text: Descriptions,
|
||||
}
|
||||
impl<'tp> Service<'tp> {
|
||||
fn new(
|
||||
vault: VaultRef,
|
||||
config: Config,
|
||||
env: Environment<'tp>,
|
||||
text: Descriptions,
|
||||
) -> Arc<Self> {
|
||||
Self {
|
||||
vault,
|
||||
config,
|
||||
env,
|
||||
text,
|
||||
}
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn serve(text: Descriptions, vault: VaultRef, config: Config) -> Result<()> {
|
||||
let mut env = Environment::new();
|
||||
env.add_template("index", include_str!("../../data/www/admin.html"))?;
|
||||
|
||||
let service = Service::new(vault, config, env, text);
|
||||
let service2 = service.clone();
|
||||
|
||||
let routes = warp::path::end()
|
||||
.map(move || service2.clone())
|
||||
.and_then(overview)
|
||||
.or(warp::path("styles.css").and_then(styles))
|
||||
.recover(handle_rejection)
|
||||
.with(warp::trace(|info| {
|
||||
// Create a span using tracing macros
|
||||
tracing::info_span!(
|
||||
"admin",
|
||||
method = %info.method(),
|
||||
path = %info.path(),
|
||||
)
|
||||
}));
|
||||
|
||||
warp::serve(routes)
|
||||
.run(service.config.addr_admin.unwrap())
|
||||
.await;
|
||||
|
||||
warn!("exited the admin interface");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
struct TemplateError(#[from] minijinja::Error);
|
||||
|
||||
impl reject::Reject for TemplateError {}
|
||||
impl Display for TemplateError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "something went wrong with producing this page")
|
||||
}
|
||||
}
|
||||
|
||||
async fn overview(serv: Arc<Service<'_>>) -> Result<Box<dyn warp::Reply>, warp::Rejection> {
|
||||
let contestants = serv
|
||||
.vault
|
||||
.contestants()
|
||||
.await
|
||||
.into_iter()
|
||||
.collect::<Vec<_>>();
|
||||
let winners = serv.vault.winners().await.into_iter().collect::<Vec<_>>();
|
||||
let r = Response::new(
|
||||
serv.env
|
||||
.get_template("index")
|
||||
.map_err(TemplateError::from)?
|
||||
.render(context!(
|
||||
title => "Wooly-Vault",
|
||||
author => env!("CARGO_PKG_AUTHORS"),
|
||||
year => "2024",
|
||||
challenge_idx => serv.config.challenge,
|
||||
challenge_title => serv.text.title(),
|
||||
challenge_description => serv.text.description(),
|
||||
challenge_hints => serv.text.hints(),
|
||||
challenge_solution => serv.text.solution(),
|
||||
contestants => serv.vault.contestants().await.iter().collect::<Vec<_>>(),
|
||||
winners => serv.vault.winners().await.iter().collect::<Vec<_>>(),
|
||||
contestants => contestants,
|
||||
winners => winners,
|
||||
contestants_amount => contestants.len(),
|
||||
winners_amount => winners.len(),
|
||||
))
|
||||
.map_err(TemplateError::from)?
|
||||
.into(),
|
||||
);
|
||||
|
||||
Ok(Box::new(r))
|
||||
}
|
||||
|
||||
async fn styles() -> Result<Box<dyn warp::Reply>, warp::Rejection> {
|
||||
let r = Response::new(include_str!("../../data/www/styles.css").to_string().into());
|
||||
Ok(Box::new(r))
|
||||
}
|
||||
|
||||
async fn handle_rejection(
|
||||
err: reject::Rejection,
|
||||
) -> Result<impl warp::reply::Reply, std::convert::Infallible> {
|
||||
let code;
|
||||
let message;
|
||||
info!("rejecting: {err:?}");
|
||||
|
||||
if err.is_not_found() {
|
||||
code = StatusCode::NOT_FOUND;
|
||||
message = "page not found";
|
||||
} else if let Some(e) = err.find::<TemplateError>() {
|
||||
error!("templating error: {e}");
|
||||
code = StatusCode::INTERNAL_SERVER_ERROR;
|
||||
message = "could not process data to make a page";
|
||||
} else {
|
||||
error!("unhandled rejection: {:?}", err);
|
||||
code = StatusCode::INTERNAL_SERVER_ERROR;
|
||||
message = "unhandled rejection";
|
||||
}
|
||||
|
||||
Ok(warp::reply::with_status(message, code))
|
||||
}
|
|
@ -8,12 +8,13 @@ use libpt::log::warn;
|
|||
use tokio::io::AsyncWriteExt;
|
||||
use tokio::net::TcpListener;
|
||||
|
||||
use super::{Challenge, Descriptions};
|
||||
use super::{Challenge, ChallengeDesc};
|
||||
use crate::config::Config;
|
||||
use crate::has_won;
|
||||
use crate::vault::VaultRef;
|
||||
|
||||
/// This struct holds the configuration and vault for the challenge.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct C1 {
|
||||
config: Config,
|
||||
vault: VaultRef,
|
||||
|
@ -31,8 +32,8 @@ impl Challenge for C1 {
|
|||
Self { config, vault }
|
||||
}
|
||||
|
||||
fn text() -> Descriptions {
|
||||
Descriptions {
|
||||
fn text() -> ChallengeDesc {
|
||||
ChallengeDesc {
|
||||
title: "dumb TCP".to_string(),
|
||||
hints: vec![String::from("TCP connect to 1337.")],
|
||||
solution: String::from("Connect by TCP, then the secret will be sent to you."),
|
||||
|
|
|
@ -10,12 +10,13 @@ use libpt::log::{info, warn};
|
|||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
|
||||
use super::{Challenge, Descriptions};
|
||||
use super::{Challenge, ChallengeDesc};
|
||||
use crate::config::Config;
|
||||
use crate::has_won;
|
||||
use crate::vault::VaultRef;
|
||||
|
||||
/// This struct holds the configuration and vault for the challenge.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct C2 {
|
||||
config: Config,
|
||||
vault: VaultRef,
|
||||
|
@ -56,8 +57,8 @@ impl Challenge for C2 {
|
|||
fn new(config: Config, vault: VaultRef) -> Self {
|
||||
Self { config, vault }
|
||||
}
|
||||
fn text() -> Descriptions {
|
||||
Descriptions {
|
||||
fn text() -> ChallengeDesc {
|
||||
ChallengeDesc {
|
||||
title: "TCP dialogue".to_string(),
|
||||
hints: vec![String::from(
|
||||
"TCP connect to 1337 and give me a special u16",
|
||||
|
|
|
@ -29,7 +29,7 @@ use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
|
|||
use tokio::net::{TcpListener, TcpStream};
|
||||
use tokio::time::Instant;
|
||||
|
||||
use super::{Challenge, Descriptions};
|
||||
use super::{Challenge, ChallengeDesc};
|
||||
use crate::config::Config;
|
||||
use crate::has_won;
|
||||
use crate::vault::VaultRef;
|
||||
|
@ -40,7 +40,7 @@ pub const NEEDED_CORRECT: usize = 16;
|
|||
pub const MILLIS_PER_QUESTION: u128 = 200;
|
||||
|
||||
/// Signifies the operations that the randomiser can come up with.
|
||||
#[derive(Copy, Clone)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
enum Operation {
|
||||
Add,
|
||||
Sub,
|
||||
|
@ -90,6 +90,7 @@ impl Distribution<Operation> for Standard {
|
|||
}
|
||||
|
||||
/// This struct holds the configuration and vault for the challenge.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct C3 {
|
||||
config: Config,
|
||||
vault: VaultRef,
|
||||
|
@ -216,8 +217,8 @@ impl Challenge for C3 {
|
|||
fn new(config: Config, vault: VaultRef) -> Self {
|
||||
Self { config, vault }
|
||||
}
|
||||
fn text() -> Descriptions {
|
||||
Descriptions {
|
||||
fn text() -> ChallengeDesc {
|
||||
ChallengeDesc {
|
||||
title: "TCP math exam".to_string(),
|
||||
hints: vec![
|
||||
"TCP connect to 1337 and answer the questions.".to_string(),
|
||||
|
|
|
@ -5,26 +5,24 @@
|
|||
|
||||
use anyhow::anyhow;
|
||||
use async_trait::async_trait;
|
||||
use libpt::log::{error, info};
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::vault::VaultRef;
|
||||
|
||||
#[cfg(feature = "admin-interface")]
|
||||
pub mod admin;
|
||||
|
||||
pub mod c1;
|
||||
pub mod c2;
|
||||
pub mod c3;
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
|
||||
pub struct Descriptions {
|
||||
pub struct ChallengeDesc {
|
||||
title: String,
|
||||
hints: Vec<String>,
|
||||
solution: String,
|
||||
description: String,
|
||||
}
|
||||
|
||||
impl Descriptions {
|
||||
impl ChallengeDesc {
|
||||
/// Returns a list of hints for the challenge.
|
||||
///
|
||||
/// A hint is a short text to be given to the contestants in case the admin thinks they need
|
||||
|
@ -33,7 +31,7 @@ impl Descriptions {
|
|||
/// # Returns
|
||||
///
|
||||
/// A vector of strings containing hints for the challenge.
|
||||
fn hints(&self) -> Vec<&str> {
|
||||
pub fn hints(&self) -> Vec<&str> {
|
||||
self.hints.iter().map(|a| a.as_ref()).collect()
|
||||
}
|
||||
/// Returns the solution to the challenge.
|
||||
|
@ -44,7 +42,7 @@ impl Descriptions {
|
|||
/// # Returns
|
||||
///
|
||||
/// A string containing the solution to the challenge.
|
||||
fn solution(&self) -> &str {
|
||||
pub fn solution(&self) -> &str {
|
||||
&self.solution
|
||||
}
|
||||
/// Returns the description to the challenge.
|
||||
|
@ -55,7 +53,7 @@ impl Descriptions {
|
|||
/// # Returns
|
||||
///
|
||||
/// A string containing the description to the challenge.
|
||||
fn description(&self) -> &str {
|
||||
pub fn description(&self) -> &str {
|
||||
&self.description
|
||||
}
|
||||
/// Returns the title to the challenge.
|
||||
|
@ -65,7 +63,7 @@ impl Descriptions {
|
|||
/// # Returns
|
||||
///
|
||||
/// A string containing the title to the challenge.
|
||||
fn title(&self) -> &str {
|
||||
pub fn title(&self) -> &str {
|
||||
&self.title
|
||||
}
|
||||
}
|
||||
|
@ -76,14 +74,18 @@ impl Descriptions {
|
|||
#[async_trait]
|
||||
pub trait Challenge
|
||||
where
|
||||
Self: Sized,
|
||||
Self: Sized + 'static,
|
||||
Self: Send,
|
||||
Self: Sync,
|
||||
Self: Clone,
|
||||
Self: std::fmt::Debug,
|
||||
{
|
||||
/// Getter for the [vault](VaultRef).
|
||||
fn vault(&self) -> VaultRef;
|
||||
/// Getter for the [Config].
|
||||
fn config(&self) -> Config;
|
||||
/// Get the various texts for this challenge.
|
||||
fn text() -> Descriptions;
|
||||
fn text() -> ChallengeDesc;
|
||||
/// Creates a new instance of the challenge with the given configuration and vault.
|
||||
///
|
||||
/// # Arguments
|
||||
|
@ -122,24 +124,13 @@ where
|
|||
/// # Returns
|
||||
///
|
||||
/// A result indicating whether the challenge and the admin interface were successfully served.
|
||||
async fn setup_and_start(self) -> anyhow::Result<()> {
|
||||
#[cfg(feature = "admin-interface")]
|
||||
if self.config().addr_admin.is_some() {
|
||||
let vault = self.vault();
|
||||
let config = self.config();
|
||||
tokio::spawn(Self::admin_interface(Self::text(), vault, config));
|
||||
}
|
||||
|
||||
let challenge_handle = self.serve();
|
||||
Ok(challenge_handle.await?)
|
||||
}
|
||||
#[cfg(feature = "admin-interface")]
|
||||
async fn admin_interface(
|
||||
text: Descriptions,
|
||||
vault: VaultRef,
|
||||
config: Config,
|
||||
) -> anyhow::Result<()> {
|
||||
admin::serve(text, vault, config).await?;
|
||||
async fn start(&self) -> anyhow::Result<()> {
|
||||
let c = self.clone();
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = c.serve().await {
|
||||
error!("challenge {} has crashed! {e:#?}", Self::text().title());
|
||||
};
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -161,10 +152,11 @@ where
|
|||
/// Returns an error if no challenge with the given index exists, or if the challenge that is being
|
||||
/// served errors.
|
||||
pub async fn select_and_start(index: u16, config: Config, vault: VaultRef) -> anyhow::Result<()> {
|
||||
info!("select+start");
|
||||
match index {
|
||||
1 => c1::C1::new(config, vault).setup_and_start().await?,
|
||||
2 => c2::C2::new(config, vault).setup_and_start().await?,
|
||||
3 => c3::C3::new(config, vault).setup_and_start().await?,
|
||||
1 => c1::C1::new(config, vault).start().await?,
|
||||
2 => c2::C2::new(config, vault).start().await?,
|
||||
3 => c3::C3::new(config, vault).start().await?,
|
||||
_ => {
|
||||
return Err(anyhow!(
|
||||
"no challenge with index {index} does currently exist"
|
||||
|
|
|
@ -18,6 +18,8 @@ use self::vault::VaultRef;
|
|||
|
||||
pub mod challenge;
|
||||
pub mod config;
|
||||
#[cfg(feature = "meta-interface")]
|
||||
pub mod meta;
|
||||
pub mod vault;
|
||||
|
||||
#[inline]
|
||||
|
|
|
@ -23,5 +23,7 @@ async fn main() -> Result<()> {
|
|||
|
||||
select_and_start(conf.challenge, conf, v).await?;
|
||||
|
||||
Ok(())
|
||||
loop {
|
||||
tokio::time::sleep(tokio::time::Duration::from_secs(5)).await
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
use std::fmt::Display;
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Result;
|
||||
use libpt::log::error;
|
||||
use libpt::log::info;
|
||||
use libpt::log::tracing;
|
||||
use libpt::log::warn;
|
||||
use minijinja::context;
|
||||
use minijinja::Environment;
|
||||
use thiserror::Error;
|
||||
use warp::http::StatusCode;
|
||||
use warp::reject;
|
||||
use warp::reject::Rejection;
|
||||
use warp::reply::Reply;
|
||||
use warp::reply::Response;
|
||||
use warp::Filter;
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::vault::VaultRef;
|
||||
|
||||
use super::errors::TemplateError;
|
||||
use super::Service;
|
||||
|
||||
impl<'tp> Service<'tp> {
|
||||
pub fn admin_routes(
|
||||
this: Arc<Self>,
|
||||
) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone + 'tp {
|
||||
let serv = this.clone();
|
||||
warp::path::end()
|
||||
.map(move || serv.clone())
|
||||
.and_then(overview)
|
||||
.with(warp::trace(|info| {
|
||||
// Create a span using tracing macros
|
||||
tracing::info_span!(
|
||||
"admin",
|
||||
method = %info.method(),
|
||||
path = %info.path(),
|
||||
)
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
async fn overview(serv: Arc<Service<'_>>) -> Result<Box<dyn warp::Reply>, warp::Rejection> {
|
||||
let challenge = serv.challenges[0].clone();
|
||||
let contestants = serv
|
||||
.vault
|
||||
.contestants()
|
||||
.await
|
||||
.into_iter()
|
||||
.collect::<Vec<_>>();
|
||||
let winners = serv.vault.winners().await.into_iter().collect::<Vec<_>>();
|
||||
let r = Response::new(
|
||||
serv.env
|
||||
.get_template("index")
|
||||
.map_err(TemplateError::from)?
|
||||
.render(context!(
|
||||
title => "Wooly-Vault",
|
||||
author => env!("CARGO_PKG_AUTHORS"),
|
||||
year => "2024",
|
||||
challenge_idx => serv.config.challenge,
|
||||
challenge_title => challenge.title(),
|
||||
challenge_description => challenge.description(),
|
||||
challenge_hints => challenge.hints(),
|
||||
challenge_solution => challenge.solution(),
|
||||
contestants => serv.vault.contestants().await.iter().collect::<Vec<_>>(),
|
||||
winners => serv.vault.winners().await.iter().collect::<Vec<_>>(),
|
||||
contestants => contestants,
|
||||
winners => winners,
|
||||
contestants_amount => contestants.len(),
|
||||
winners_amount => winners.len(),
|
||||
))
|
||||
.map_err(TemplateError::from)?
|
||||
.into(),
|
||||
);
|
||||
|
||||
Ok(Box::new(r))
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
use std::fmt::Display;
|
||||
|
||||
use libpt::log::{error, info};
|
||||
use thiserror::Error;
|
||||
use warp::http::StatusCode;
|
||||
use warp::reject;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub struct TemplateError(#[from] minijinja::Error);
|
||||
|
||||
impl reject::Reject for TemplateError {}
|
||||
impl Display for TemplateError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "something went wrong with producing this page")
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn handle_rejection(
|
||||
err: reject::Rejection,
|
||||
) -> Result<impl warp::reply::Reply, std::convert::Infallible> {
|
||||
let code;
|
||||
let message;
|
||||
info!("rejecting: {err:?}");
|
||||
|
||||
if err.is_not_found() {
|
||||
code = StatusCode::NOT_FOUND;
|
||||
message = "page not found";
|
||||
} else if let Some(e) = err.find::<TemplateError>() {
|
||||
error!("templating error: {e}");
|
||||
code = StatusCode::INTERNAL_SERVER_ERROR;
|
||||
message = "could not process data to make a page";
|
||||
} else {
|
||||
error!("unhandled rejection: {:?}", err);
|
||||
code = StatusCode::INTERNAL_SERVER_ERROR;
|
||||
message = "unhandled rejection";
|
||||
}
|
||||
|
||||
Ok(warp::reply::with_status(message, code))
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Result;
|
||||
use libpt::log::tracing;
|
||||
use libpt::log::warn;
|
||||
use minijinja::Environment;
|
||||
use warp::reject::Rejection;
|
||||
use warp::reply::Reply;
|
||||
use warp::reply::Response;
|
||||
use warp::Filter;
|
||||
|
||||
use crate::challenge::ChallengeDesc;
|
||||
use crate::config::Config;
|
||||
use crate::vault::VaultRef;
|
||||
|
||||
use self::errors::handle_rejection;
|
||||
|
||||
pub mod admin;
|
||||
pub mod errors;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Service<'tp> {
|
||||
vault: VaultRef,
|
||||
config: Config,
|
||||
env: Environment<'tp>,
|
||||
challenges: Vec<ChallengeDesc>,
|
||||
}
|
||||
impl<'tp> Service<'tp> {
|
||||
fn new(
|
||||
vault: VaultRef,
|
||||
config: Config,
|
||||
env: Environment<'tp>,
|
||||
challenges: Vec<ChallengeDesc>,
|
||||
) -> Arc<Self> {
|
||||
Self {
|
||||
vault,
|
||||
config,
|
||||
env,
|
||||
challenges,
|
||||
}
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn serve(challenges: Vec<ChallengeDesc>, vault: VaultRef, config: Config) -> Result<()> {
|
||||
let mut env = Environment::new();
|
||||
env.add_template("index", include_str!("../../data/www/admin.html"))?;
|
||||
|
||||
let service = Service::new(vault, config, env, challenges);
|
||||
|
||||
let routes = Service::admin_routes(service.clone())
|
||||
.or(Service::ressources_routes())
|
||||
.recover(handle_rejection)
|
||||
.with(warp::trace(|info| {
|
||||
// Create a span using tracing macros
|
||||
tracing::info_span!(
|
||||
"admin",
|
||||
method = %info.method(),
|
||||
path = %info.path(),
|
||||
)
|
||||
}));
|
||||
|
||||
warp::serve(routes)
|
||||
.run(service.config.addr_admin.unwrap())
|
||||
.await;
|
||||
|
||||
warn!("exited the admin interface");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl Service<'_> {
|
||||
fn ressources_routes() -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
|
||||
warp::path("styles.css").and_then(styles)
|
||||
}
|
||||
}
|
||||
|
||||
async fn styles() -> Result<Box<dyn warp::Reply>, warp::Rejection> {
|
||||
let r = Response::new(include_str!("../../data/www/styles.css").to_string().into());
|
||||
Ok(Box::new(r))
|
||||
}
|
Loading…
Reference in New Issue