Switched from warp to tide
This commit is contained in:
parent
9baa8c1a0f
commit
6c0af083d5
File diff suppressed because it is too large
Load Diff
|
@ -5,18 +5,16 @@ authors = ["Pascal Engélibert <tuxmain@zettascript.org>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
async-std = { version = "1.6.0", features = ["attributes"] }
|
||||||
bincode = "1.3.1"
|
bincode = "1.3.1"
|
||||||
bs58 = "0.4.0"
|
bs58 = "0.4.0"
|
||||||
dirs = "3.0.1"
|
dirs = "3.0.1"
|
||||||
handlebars = "3.5.1"
|
handlebars = "3.5.1"
|
||||||
hex = "0.4.2"
|
hex = "0.4.2"
|
||||||
http = "0.2.1"
|
|
||||||
rand = "0.7.3"
|
rand = "0.7.3"
|
||||||
serde = { version = "1.0.118", features = ["derive"] }
|
serde = { version = "1.0.118", features = ["derive"] }
|
||||||
serde_json = "1.0.60"
|
serde_json = "1.0.60"
|
||||||
sha2 = "0.9.2"
|
sha2 = "0.9.2"
|
||||||
sled = "0.34.6"
|
sled = "0.34.6"
|
||||||
structopt = "0.3.21"
|
structopt = "0.3.21"
|
||||||
tokio = { version = "0.2.24", features = ["macros", "rt-threaded"] }
|
tide = { version = "0.15.0", default-features = false, features = ["h1-server", "cookies", "logger"] }
|
||||||
urlencoding = "1.1.1"
|
|
||||||
warp = { version = "0.2.5", default-features = false }
|
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
mod cli;
|
mod cli;
|
||||||
mod config;
|
mod config;
|
||||||
mod db;
|
mod db;
|
||||||
|
mod queries;
|
||||||
mod server;
|
mod server;
|
||||||
mod static_files;
|
mod static_files;
|
||||||
mod templates;
|
mod templates;
|
||||||
|
@ -10,7 +11,7 @@ mod utils;
|
||||||
|
|
||||||
use structopt::StructOpt;
|
use structopt::StructOpt;
|
||||||
|
|
||||||
#[tokio::main]
|
#[async_std::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
let opt = cli::MainOpt::from_args();
|
let opt = cli::MainOpt::from_args();
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
|
pub struct NewAdQuery {
|
||||||
|
pub author: String,
|
||||||
|
pub price: String,
|
||||||
|
pub psw: String,
|
||||||
|
pub pubkey: String,
|
||||||
|
pub quantity: String,
|
||||||
|
pub title: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
|
pub struct RmAdQuery {
|
||||||
|
pub ad: String,
|
||||||
|
pub psw: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
|
pub struct AdminRmAdQuery {
|
||||||
|
pub ad: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
|
pub struct AdminLoginQuery {
|
||||||
|
pub psw: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
|
#[serde(tag = "a")]
|
||||||
|
pub enum Query {
|
||||||
|
#[serde(rename = "new_ad")]
|
||||||
|
NewAdQuery(NewAdQuery),
|
||||||
|
#[serde(rename = "rm_ad")]
|
||||||
|
RmAdQuery(RmAdQuery),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
|
#[serde(tag = "a")]
|
||||||
|
pub enum AdminQuery {
|
||||||
|
#[serde(rename = "login")]
|
||||||
|
LoginQuery(AdminLoginQuery),
|
||||||
|
#[serde(rename = "rm_ad")]
|
||||||
|
RmAdQuery(AdminRmAdQuery),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct SelectAdQuery {
|
||||||
|
pub ad: String,
|
||||||
|
}
|
698
src/server.rs
698
src/server.rs
|
@ -1,430 +1,350 @@
|
||||||
use super::{cli, config, db, static_files, templates, utils::*};
|
use super::{cli, config, db, queries::*, static_files, templates::*, utils::*};
|
||||||
|
|
||||||
use handlebars::to_json;
|
use handlebars::to_json;
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use serde_json::value::Value as Json;
|
|
||||||
use sha2::{Digest, Sha512Trunc256};
|
use sha2::{Digest, Sha512Trunc256};
|
||||||
use std::{
|
use std::{
|
||||||
convert::{TryFrom, TryInto},
|
convert::{TryFrom, TryInto},
|
||||||
sync::{Arc, RwLock},
|
sync::Arc,
|
||||||
};
|
};
|
||||||
use warp::Filter;
|
|
||||||
use warp::Reply;
|
|
||||||
|
|
||||||
type PasswordHash = [u8; 32];
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize)]
|
|
||||||
struct AdId([u8; 16]);
|
|
||||||
|
|
||||||
impl AdId {
|
|
||||||
fn random() -> Self {
|
|
||||||
Self(rand::random())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsRef<[u8]> for AdId {
|
|
||||||
fn as_ref(&self) -> &[u8] {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<&[u8]> for AdId {
|
|
||||||
type Error = std::array::TryFromSliceError;
|
|
||||||
|
|
||||||
fn try_from(v: &[u8]) -> Result<Self, Self::Error> {
|
|
||||||
Ok(Self(v.try_into()?))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn start_server(
|
pub async fn start_server(
|
||||||
config: config::Config,
|
config: config::Config,
|
||||||
dbs: db::Dbs,
|
dbs: db::Dbs,
|
||||||
templates: templates::Templates<'static>,
|
templates: Templates<'static>,
|
||||||
opt: cli::MainOpt,
|
opt: cli::MainOpt,
|
||||||
) {
|
) {
|
||||||
let templates = Arc::new(templates);
|
let templates = Arc::new(templates);
|
||||||
|
|
||||||
let cache_ads = Arc::new(RwLock::new(to_json(
|
|
||||||
dbs.ads
|
|
||||||
.iter()
|
|
||||||
.filter_map(|x| {
|
|
||||||
let (ad_id, ad) = x.ok()?;
|
|
||||||
Some(AdWithId {
|
|
||||||
id: hex::encode(ad_id.as_ref()),
|
|
||||||
ad: bincode::deserialize::<Ad>(&ad).ok()?,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.collect::<Vec<AdWithId>>(),
|
|
||||||
)));
|
|
||||||
|
|
||||||
let config = Arc::new(config);
|
let config = Arc::new(config);
|
||||||
let dbs = Arc::new(dbs);
|
let dbs = Arc::new(dbs);
|
||||||
|
|
||||||
let handle_index = {
|
let mut app = tide::new();
|
||||||
let cache_ads = cache_ads.clone();
|
app.at("/static")
|
||||||
|
.serve_dir(opt.dir.0.join(static_files::STATIC_DIR))
|
||||||
|
.unwrap();
|
||||||
|
app.at("/").get({
|
||||||
let templates = templates.clone();
|
let templates = templates.clone();
|
||||||
move |errors: &[ErrorTemplate], new_ad_form_refill| {
|
let dbs = dbs.clone();
|
||||||
warp::reply::html(
|
move |req: tide::Request<()>| serve_index(req, templates.clone(), dbs.clone(), &[], None)
|
||||||
templates
|
});
|
||||||
.hb
|
app.at("/").post({
|
||||||
.render(
|
|
||||||
"index.html",
|
|
||||||
&IndexTemplate {
|
|
||||||
lang: "fr",
|
|
||||||
errors,
|
|
||||||
ads: &*cache_ads.read().unwrap(),
|
|
||||||
new_ad_form_refill,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.unwrap_or_else(|e| e.to_string()),
|
|
||||||
)
|
|
||||||
.into_response()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let handle_admin_login = {
|
|
||||||
let templates = templates.clone();
|
let templates = templates.clone();
|
||||||
move |errors: &[ErrorTemplate]| {
|
|
||||||
warp::reply::html(
|
|
||||||
templates
|
|
||||||
.hb
|
|
||||||
.render(
|
|
||||||
"admin_login.html",
|
|
||||||
&AdminLoginTemplate { lang: "fr", errors },
|
|
||||||
)
|
|
||||||
.unwrap_or_else(|e| e.to_string()),
|
|
||||||
)
|
|
||||||
.into_response()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let handle_admin = {
|
|
||||||
let cache_ads = cache_ads.clone();
|
|
||||||
move |errors: &[ErrorTemplate]| {
|
|
||||||
warp::reply::html(
|
|
||||||
templates
|
|
||||||
.hb
|
|
||||||
.render(
|
|
||||||
"admin.html",
|
|
||||||
&AdminTemplate {
|
|
||||||
lang: "fr",
|
|
||||||
errors,
|
|
||||||
ads: &*cache_ads.read().unwrap(),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.unwrap_or_else(|e| e.to_string()),
|
|
||||||
)
|
|
||||||
.into_response()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let route_static = warp::path("static")
|
|
||||||
.and(warp::get())
|
|
||||||
.and(warp::fs::dir(opt.dir.0.join(static_files::STATIC_DIR)));
|
|
||||||
|
|
||||||
let handle_new_ad = {
|
|
||||||
let handle_index = handle_index.clone();
|
|
||||||
let dbs = dbs.clone();
|
let dbs = dbs.clone();
|
||||||
let cache_ads = cache_ads.clone();
|
move |req: tide::Request<()>| handle_post_index(req, templates.clone(), dbs.clone())
|
||||||
move |query: NewAdQuery| {
|
});
|
||||||
let mut hasher = Sha512Trunc256::new();
|
app.at("/ad/:ad").get({
|
||||||
hasher.update(&query.psw);
|
let templates = templates.clone();
|
||||||
dbs.ads
|
|
||||||
.insert(
|
|
||||||
AdId::random(),
|
|
||||||
bincode::serialize(&Ad {
|
|
||||||
pubkey: if query.pubkey.is_empty() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(match format_pubkey(&query.pubkey) {
|
|
||||||
Ok(pubkey) => pubkey,
|
|
||||||
Err(e) => return handle_index(&[ErrorTemplate{text: match e {
|
|
||||||
PubkeyDecodeError::BadChecksum => "La somme de contrôle de la clé publique est incorrecte.",
|
|
||||||
PubkeyDecodeError::BadFormat => "Le format de la clé publique est incorrect.",
|
|
||||||
}}], Some(query))
|
|
||||||
})
|
|
||||||
},
|
|
||||||
author: query.author,
|
|
||||||
password: hasher.finalize()[..].try_into().unwrap(),
|
|
||||||
price: query.price,
|
|
||||||
quantity: query.quantity,
|
|
||||||
time: 0,
|
|
||||||
title: query.title,
|
|
||||||
})
|
|
||||||
.unwrap(),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
dbs.ads.flush().unwrap();
|
|
||||||
let mut cache_ads = cache_ads.write().unwrap();
|
|
||||||
*cache_ads = to_json(
|
|
||||||
dbs.ads
|
|
||||||
.iter()
|
|
||||||
.filter_map(|x| {
|
|
||||||
let (ad_id, ad) = x.ok()?;
|
|
||||||
Some(AdWithId {
|
|
||||||
id: hex::encode(ad_id.as_ref()),
|
|
||||||
ad: bincode::deserialize::<Ad>(&ad).ok()?,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.collect::<Vec<AdWithId>>(),
|
|
||||||
);
|
|
||||||
drop(cache_ads);
|
|
||||||
redirect_302("/").into_response()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let handle_rm_ad = {
|
|
||||||
let handle_index = handle_index.clone();
|
|
||||||
let dbs = dbs.clone();
|
let dbs = dbs.clone();
|
||||||
let cache_ads = cache_ads.clone();
|
move |req: tide::Request<()>| serve_index(req, templates.clone(), dbs.clone(), &[], None)
|
||||||
move |query: RmAdQuery| {
|
});
|
||||||
let mut hasher = Sha512Trunc256::new();
|
app.at("/admin").get({
|
||||||
hasher.update(query.psw);
|
let config = config.clone();
|
||||||
let password: PasswordHash = hasher.finalize()[..].try_into().unwrap();
|
let templates = templates.clone();
|
||||||
/*query
|
let dbs = dbs.clone();
|
||||||
.ads
|
move |req: tide::Request<()>| {
|
||||||
.into_iter()
|
handle_admin(req, config.clone(), templates.clone(), dbs.clone())
|
||||||
.filter_map(|x| Some(AdId::try_from(hex::decode(x).ok()?.as_ref()).ok()?))
|
|
||||||
.for_each(|ad_id| {
|
|
||||||
if let Some(raw) = dbs.ads.get(&ad_id).unwrap() {
|
|
||||||
if let Ok(ad) = bincode::deserialize::<Ad>(&raw) {
|
|
||||||
if ad.password == password {
|
|
||||||
dbs.ads.remove(&ad_id).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});*/
|
|
||||||
if let Ok(ad_id) = hex::decode(query.ad) {
|
|
||||||
if let Ok(ad_id) = AdId::try_from(ad_id.as_ref()) {
|
|
||||||
if let Some(raw) = dbs.ads.get(&ad_id).unwrap() {
|
|
||||||
if let Ok(ad) = bincode::deserialize::<Ad>(&raw) {
|
|
||||||
if ad.password == password {
|
|
||||||
dbs.ads.remove(&ad_id).unwrap();
|
|
||||||
|
|
||||||
dbs.ads.flush().unwrap();
|
|
||||||
let mut cache_ads = cache_ads.write().unwrap();
|
|
||||||
*cache_ads = to_json(
|
|
||||||
dbs.ads
|
|
||||||
.iter()
|
|
||||||
.filter_map(|x| {
|
|
||||||
let (ad_id, ad) = x.ok()?;
|
|
||||||
Some(AdWithId {
|
|
||||||
id: hex::encode(ad_id.as_ref()),
|
|
||||||
ad: bincode::deserialize::<Ad>(&ad).ok()?,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.collect::<Vec<AdWithId>>(),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return handle_index(
|
|
||||||
&[ErrorTemplate {
|
|
||||||
text: "Le mot de passe de l'annonce est incorrect.",
|
|
||||||
}],
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
redirect_302("/").into_response()
|
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
app.at("/admin").post({
|
||||||
|
let config = config.clone();
|
||||||
|
move |req: tide::Request<()>| {
|
||||||
|
handle_post_admin(req, config.clone(), templates.clone(), dbs.clone())
|
||||||
|
}
|
||||||
|
});
|
||||||
|
app.at("/admin/logout").get(handle_admin_logout);
|
||||||
|
app.listen(config.listen).await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
let handle_admin_rm_ad = {
|
async fn serve_index<'a>(
|
||||||
move |query: AdminRmAdQuery| {
|
req: tide::Request<()>,
|
||||||
if let Ok(ad_id) = hex::decode(query.ad) {
|
templates: Arc<Templates<'static>>,
|
||||||
if let Ok(ad_id) = AdId::try_from(ad_id.as_ref()) {
|
dbs: Arc<db::Dbs>,
|
||||||
if dbs.ads.remove(&ad_id).unwrap().is_some() {
|
errors: &[ErrorTemplate<'a>],
|
||||||
dbs.ads.flush().unwrap();
|
new_ad_form_refill: Option<NewAdQuery>,
|
||||||
let mut cache_ads = cache_ads.write().unwrap();
|
) -> tide::Result<tide::Response> {
|
||||||
*cache_ads = to_json(
|
let selected_ad = req.param("ad").ok();
|
||||||
|
Ok(tide::Response::builder(200)
|
||||||
|
.content_type(tide::http::mime::HTML)
|
||||||
|
.body(
|
||||||
|
templates
|
||||||
|
.hb
|
||||||
|
.render(
|
||||||
|
"index.html",
|
||||||
|
&IndexTemplate {
|
||||||
|
lang: "fr",
|
||||||
|
errors,
|
||||||
|
ads: &to_json(
|
||||||
dbs.ads
|
dbs.ads
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|x| {
|
.filter_map(|x| {
|
||||||
let (ad_id, ad) = x.ok()?;
|
let (ad_id, ad) = x.ok()?;
|
||||||
|
let ad_id = hex::encode(ad_id.as_ref());
|
||||||
Some(AdWithId {
|
Some(AdWithId {
|
||||||
id: hex::encode(ad_id.as_ref()),
|
|
||||||
ad: bincode::deserialize::<Ad>(&ad).ok()?,
|
ad: bincode::deserialize::<Ad>(&ad).ok()?,
|
||||||
|
selected: selected_ad.map_or(false, |i| i == ad_id),
|
||||||
|
id: ad_id,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.collect::<Vec<AdWithId>>(),
|
.collect::<Vec<AdWithId>>(),
|
||||||
);
|
),
|
||||||
|
new_ad_form_refill,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap_or_else(|e| e.to_string()),
|
||||||
|
)
|
||||||
|
.build())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn serve_admin<'a>(
|
||||||
|
req: tide::Request<()>,
|
||||||
|
templates: Arc<Templates<'static>>,
|
||||||
|
dbs: Arc<db::Dbs>,
|
||||||
|
errors: &[ErrorTemplate<'a>],
|
||||||
|
) -> tide::Result<tide::Response> {
|
||||||
|
let selected_ad = req.param("ad").ok();
|
||||||
|
Ok(tide::Response::builder(200)
|
||||||
|
.content_type(tide::http::mime::HTML)
|
||||||
|
.body(
|
||||||
|
templates
|
||||||
|
.hb
|
||||||
|
.render(
|
||||||
|
"admin.html",
|
||||||
|
&AdminTemplate {
|
||||||
|
lang: "fr",
|
||||||
|
errors,
|
||||||
|
ads: &to_json(
|
||||||
|
dbs.ads
|
||||||
|
.iter()
|
||||||
|
.filter_map(|x| {
|
||||||
|
let (ad_id, ad) = x.ok()?;
|
||||||
|
let ad_id = hex::encode(ad_id.as_ref());
|
||||||
|
Some(AdWithId {
|
||||||
|
ad: bincode::deserialize::<Ad>(&ad).ok()?,
|
||||||
|
selected: selected_ad.map_or(false, |i| i == ad_id),
|
||||||
|
id: ad_id,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<Vec<AdWithId>>(),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap_or_else(|e| e.to_string()),
|
||||||
|
)
|
||||||
|
.build())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn serve_admin_login<'a>(
|
||||||
|
_req: tide::Request<()>,
|
||||||
|
templates: Arc<Templates<'static>>,
|
||||||
|
errors: &[ErrorTemplate<'a>],
|
||||||
|
) -> tide::Result<tide::Response> {
|
||||||
|
Ok(tide::Response::builder(200)
|
||||||
|
.content_type(tide::http::mime::HTML)
|
||||||
|
.body(
|
||||||
|
templates
|
||||||
|
.hb
|
||||||
|
.render(
|
||||||
|
"admin_login.html",
|
||||||
|
&AdminLoginTemplate { lang: "fr", errors },
|
||||||
|
)
|
||||||
|
.unwrap_or_else(|e| e.to_string()),
|
||||||
|
)
|
||||||
|
.build())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_post_index(
|
||||||
|
mut req: tide::Request<()>,
|
||||||
|
templates: Arc<Templates<'static>>,
|
||||||
|
dbs: Arc<db::Dbs>,
|
||||||
|
) -> tide::Result<tide::Response> {
|
||||||
|
match req.body_form::<Query>().await? {
|
||||||
|
Query::NewAdQuery(query) => handle_new_ad(req, templates.clone(), dbs.clone(), query).await,
|
||||||
|
Query::RmAdQuery(query) => handle_rm_ad(req, templates.clone(), dbs.clone(), query).await,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_new_ad(
|
||||||
|
req: tide::Request<()>,
|
||||||
|
templates: Arc<Templates<'static>>,
|
||||||
|
dbs: Arc<db::Dbs>,
|
||||||
|
query: NewAdQuery,
|
||||||
|
) -> tide::Result<tide::Response> {
|
||||||
|
let mut hasher = Sha512Trunc256::new();
|
||||||
|
hasher.update(&query.psw);
|
||||||
|
dbs.ads
|
||||||
|
.insert(
|
||||||
|
AdId::random(),
|
||||||
|
bincode::serialize(&Ad {
|
||||||
|
pubkey: if query.pubkey.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(match format_pubkey(&query.pubkey) {
|
||||||
|
Ok(pubkey) => pubkey,
|
||||||
|
Err(e) => return serve_index(
|
||||||
|
req,
|
||||||
|
templates,
|
||||||
|
dbs,
|
||||||
|
&[ErrorTemplate {
|
||||||
|
text: match e {
|
||||||
|
PubkeyDecodeError::BadChecksum => {
|
||||||
|
"La somme de contrôle de la clé publique est incorrecte."
|
||||||
|
}
|
||||||
|
PubkeyDecodeError::BadFormat => {
|
||||||
|
"Le format de la clé publique est incorrect."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
Some(query),
|
||||||
|
)
|
||||||
|
.await,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
author: query.author,
|
||||||
|
password: hasher.finalize()[..].try_into().unwrap(),
|
||||||
|
price: query.price,
|
||||||
|
quantity: query.quantity,
|
||||||
|
time: 0,
|
||||||
|
title: query.title,
|
||||||
|
})
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
dbs.ads.flush().unwrap();
|
||||||
|
Ok(tide::Redirect::new("/").into())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_rm_ad(
|
||||||
|
req: tide::Request<()>,
|
||||||
|
templates: Arc<Templates<'static>>,
|
||||||
|
dbs: Arc<db::Dbs>,
|
||||||
|
query: RmAdQuery,
|
||||||
|
) -> tide::Result<tide::Response> {
|
||||||
|
let mut hasher = Sha512Trunc256::new();
|
||||||
|
hasher.update(query.psw);
|
||||||
|
let password: PasswordHash = hasher.finalize()[..].try_into().unwrap();
|
||||||
|
/*query
|
||||||
|
.ads
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|x| Some(AdId::try_from(hex::decode(x).ok()?.as_ref()).ok()?))
|
||||||
|
.for_each(|ad_id| {
|
||||||
|
if let Some(raw) = dbs.ads.get(&ad_id).unwrap() {
|
||||||
|
if let Ok(ad) = bincode::deserialize::<Ad>(&raw) {
|
||||||
|
if ad.password == password {
|
||||||
|
dbs.ads.remove(&ad_id).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});*/
|
||||||
|
if let Ok(ad_id) = hex::decode(query.ad) {
|
||||||
|
if let Ok(ad_id) = AdId::try_from(ad_id.as_ref()) {
|
||||||
|
if let Some(raw) = dbs.ads.get(&ad_id).unwrap() {
|
||||||
|
if let Ok(ad) = bincode::deserialize::<Ad>(&raw) {
|
||||||
|
if ad.password == password {
|
||||||
|
dbs.ads.remove(&ad_id).unwrap();
|
||||||
|
|
||||||
|
dbs.ads.flush().unwrap();
|
||||||
|
} else {
|
||||||
|
return serve_index(
|
||||||
|
req,
|
||||||
|
templates,
|
||||||
|
dbs,
|
||||||
|
&[ErrorTemplate {
|
||||||
|
text: "Le mot de passe de l'annonce est incorrect.",
|
||||||
|
}],
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
redirect_302("/admin").into_response()
|
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
Ok(tide::Redirect::new("/").into())
|
||||||
|
}
|
||||||
|
|
||||||
let route_index = warp::path::end().and(warp::get().map(move || handle_index(&[], None)).or(
|
async fn handle_admin(
|
||||||
warp::post().and(
|
req: tide::Request<()>,
|
||||||
warp::body::form::<Query>().map(move |query: Query| match query {
|
config: Arc<config::Config>,
|
||||||
Query::NewAdQuery(query) => handle_new_ad(query),
|
templates: Arc<Templates<'static>>,
|
||||||
Query::RmAdQuery(query) => handle_rm_ad(query),
|
dbs: Arc<db::Dbs>,
|
||||||
}),
|
) -> tide::Result<tide::Response> {
|
||||||
),
|
if let Some(psw) = req.cookie("admin") {
|
||||||
));
|
if config.admin_passwords.contains(&String::from(psw.value())) {
|
||||||
|
serve_admin(req, templates, dbs, &[]).await
|
||||||
let route_admin = warp::path("admin").and(
|
} else {
|
||||||
warp::path::end()
|
serve_admin_login(
|
||||||
.and(
|
req,
|
||||||
warp::cookie("admin")
|
templates,
|
||||||
.and(warp::body::form::<AdminQuery>())
|
&[ErrorTemplate {
|
||||||
.map({
|
text: "Mot de passe administrateur invalide",
|
||||||
let handle_admin_login = handle_admin_login.clone();
|
}],
|
||||||
let config = config.clone();
|
|
||||||
move |psw, query| {
|
|
||||||
if config.admin_passwords.contains(&psw) {
|
|
||||||
let AdminQuery::RmAdQuery(query) = query;
|
|
||||||
handle_admin_rm_ad(query)
|
|
||||||
} else {
|
|
||||||
handle_admin_login(&[ErrorTemplate {
|
|
||||||
text: "Mot de passe administrateur invalide",
|
|
||||||
}])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.or(warp::cookie("admin").map({
|
|
||||||
let handle_admin = handle_admin.clone();
|
|
||||||
let handle_admin_login = handle_admin_login.clone();
|
|
||||||
let config = config.clone();
|
|
||||||
move |psw| {
|
|
||||||
if config.admin_passwords.contains(&psw) {
|
|
||||||
handle_admin(&[])
|
|
||||||
} else {
|
|
||||||
handle_admin_login(&[ErrorTemplate {
|
|
||||||
text: "Mot de passe administrateur invalide",
|
|
||||||
}])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
.or(warp::path::end().map({
|
|
||||||
let handle_admin_login = handle_admin_login.clone();
|
|
||||||
move || handle_admin_login(&[])
|
|
||||||
})),
|
|
||||||
)
|
)
|
||||||
.or(warp::path("login").and(warp::path::end()).and(
|
.await
|
||||||
warp::post()
|
}
|
||||||
.and(warp::get().map(move || handle_admin(&[])))
|
} else {
|
||||||
.or(warp::body::form::<AdminLoginQuery>().map({
|
serve_admin_login(req, templates, &[]).await
|
||||||
let config = config.clone();
|
}
|
||||||
move |query: AdminLoginQuery| {
|
}
|
||||||
if config.admin_passwords.contains(&query.psw) {
|
|
||||||
warp::reply::with_header(
|
async fn handle_post_admin(
|
||||||
redirect_302("/admin").into_response(),
|
mut req: tide::Request<()>,
|
||||||
"Set-Cookie",
|
config: Arc<config::Config>,
|
||||||
format!(
|
templates: Arc<Templates<'static>>,
|
||||||
"admin={}; path=/; HttpOnly",
|
dbs: Arc<db::Dbs>,
|
||||||
urlencoding::encode(&query.psw)
|
) -> tide::Result<tide::Response> {
|
||||||
),
|
if let Some(psw) = req.cookie("admin") {
|
||||||
)
|
if config.admin_passwords.contains(&String::from(psw.value())) {
|
||||||
.into_response()
|
match req.body_form::<AdminQuery>().await? {
|
||||||
} else {
|
AdminQuery::RmAdQuery(query) => {
|
||||||
handle_admin_login(&[ErrorTemplate {
|
if let Ok(ad_id) = hex::decode(query.ad) {
|
||||||
text: "Mot de passe administrateur invalide",
|
if let Ok(ad_id) = AdId::try_from(ad_id.as_ref()) {
|
||||||
}])
|
dbs.ads.remove(&ad_id).unwrap();
|
||||||
.into_response()
|
|
||||||
}
|
dbs.ads.flush().unwrap();
|
||||||
}
|
}
|
||||||
})),
|
}
|
||||||
))
|
Ok(tide::Redirect::new("/admin").into())
|
||||||
.or(warp::path("logout").and(warp::path::end()).map(|| {
|
}
|
||||||
warp::reply::with_header(
|
_ => serve_admin(req, templates, dbs, &[]).await,
|
||||||
redirect_302("/").into_response(),
|
}
|
||||||
"Set-Cookie",
|
} else {
|
||||||
"admin=; HttpOnly; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT",
|
serve_admin_login(
|
||||||
)
|
req,
|
||||||
.into_response()
|
templates,
|
||||||
})),
|
&[ErrorTemplate {
|
||||||
);
|
text: "Mot de passe administrateur invalide",
|
||||||
|
}],
|
||||||
warp::serve(route_static.or(route_index).or(route_admin))
|
)
|
||||||
.run(config.listen)
|
.await
|
||||||
.await;
|
}
|
||||||
|
} else if let AdminQuery::LoginQuery(query) = req.body_form::<AdminQuery>().await? {
|
||||||
|
if config.admin_passwords.contains(&query.psw) {
|
||||||
|
serve_admin(req, templates, dbs, &[]).await.map(|mut r| {
|
||||||
|
let mut cookie = tide::http::Cookie::new("admin", query.psw);
|
||||||
|
cookie.set_http_only(Some(true));
|
||||||
|
cookie.set_path("/");
|
||||||
|
r.insert_cookie(cookie);
|
||||||
|
r
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
serve_admin_login(
|
||||||
|
req,
|
||||||
|
templates,
|
||||||
|
&[ErrorTemplate {
|
||||||
|
text: "Mot de passe administrateur invalide",
|
||||||
|
}],
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
serve_admin_login(req, templates, &[]).await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
async fn handle_admin_logout(req: tide::Request<()>) -> tide::Result<tide::Response> {
|
||||||
struct IndexTemplate<'a> {
|
let mut r: tide::Response = tide::Redirect::new("/").into();
|
||||||
lang: &'a str,
|
if let Some(mut cookie) = req.cookie("admin") {
|
||||||
errors: &'a [ErrorTemplate<'a>],
|
cookie.set_path("/");
|
||||||
ads: &'a Json,
|
r.remove_cookie(cookie);
|
||||||
new_ad_form_refill: Option<NewAdQuery>,
|
}
|
||||||
}
|
Ok(r)
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
struct AdminLoginTemplate<'a> {
|
|
||||||
lang: &'a str,
|
|
||||||
errors: &'a [ErrorTemplate<'a>],
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
struct AdminTemplate<'a> {
|
|
||||||
lang: &'a str,
|
|
||||||
errors: &'a [ErrorTemplate<'a>],
|
|
||||||
ads: &'a Json,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
struct ErrorTemplate<'a> {
|
|
||||||
text: &'a str,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
|
||||||
struct NewAdQuery {
|
|
||||||
author: String,
|
|
||||||
price: String,
|
|
||||||
psw: String,
|
|
||||||
pubkey: String,
|
|
||||||
quantity: String,
|
|
||||||
title: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
struct RmAdQuery {
|
|
||||||
ad: String,
|
|
||||||
psw: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
struct AdminRmAdQuery {
|
|
||||||
ad: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
struct AdminLoginQuery {
|
|
||||||
psw: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(tag = "a")]
|
|
||||||
enum Query {
|
|
||||||
#[serde(rename = "new_ad")]
|
|
||||||
NewAdQuery(NewAdQuery),
|
|
||||||
#[serde(rename = "rm_ad")]
|
|
||||||
RmAdQuery(RmAdQuery),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(tag = "a")]
|
|
||||||
enum AdminQuery {
|
|
||||||
#[serde(rename = "rm_ad")]
|
|
||||||
RmAdQuery(AdminRmAdQuery),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
|
||||||
struct Ad {
|
|
||||||
author: String,
|
|
||||||
password: PasswordHash,
|
|
||||||
price: String,
|
|
||||||
pubkey: Option<String>,
|
|
||||||
quantity: String,
|
|
||||||
time: u64,
|
|
||||||
title: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize)]
|
|
||||||
struct AdWithId {
|
|
||||||
id: String,
|
|
||||||
ad: Ad,
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
|
use super::queries::*;
|
||||||
|
|
||||||
use handlebars::Handlebars;
|
use handlebars::Handlebars;
|
||||||
|
use serde::Serialize;
|
||||||
|
use serde_json::value::Value as Json;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
const TEMPLATES_DIR: &str = "templates";
|
const TEMPLATES_DIR: &str = "templates";
|
||||||
|
@ -41,3 +45,29 @@ pub fn load_templates<'reg>(dir: &Path) -> Templates<'reg> {
|
||||||
}
|
}
|
||||||
Templates { hb }
|
Templates { hb }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct IndexTemplate<'a> {
|
||||||
|
pub lang: &'a str,
|
||||||
|
pub errors: &'a [ErrorTemplate<'a>],
|
||||||
|
pub ads: &'a Json,
|
||||||
|
pub new_ad_form_refill: Option<NewAdQuery>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct AdminLoginTemplate<'a> {
|
||||||
|
pub lang: &'a str,
|
||||||
|
pub errors: &'a [ErrorTemplate<'a>],
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct AdminTemplate<'a> {
|
||||||
|
pub lang: &'a str,
|
||||||
|
pub errors: &'a [ErrorTemplate<'a>],
|
||||||
|
pub ads: &'a Json,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct ErrorTemplate<'a> {
|
||||||
|
pub text: &'a str,
|
||||||
|
}
|
||||||
|
|
52
src/utils.rs
52
src/utils.rs
|
@ -1,4 +1,49 @@
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
|
use std::convert::{TryFrom, TryInto};
|
||||||
|
|
||||||
|
pub type PasswordHash = [u8; 32];
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize)]
|
||||||
|
pub struct AdId([u8; 16]);
|
||||||
|
|
||||||
|
impl AdId {
|
||||||
|
pub fn random() -> Self {
|
||||||
|
Self(rand::random())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<[u8]> for AdId {
|
||||||
|
fn as_ref(&self) -> &[u8] {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&[u8]> for AdId {
|
||||||
|
type Error = std::array::TryFromSliceError;
|
||||||
|
|
||||||
|
fn try_from(v: &[u8]) -> Result<Self, Self::Error> {
|
||||||
|
Ok(Self(v.try_into()?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
|
pub struct Ad {
|
||||||
|
pub author: String,
|
||||||
|
pub password: PasswordHash,
|
||||||
|
pub price: String,
|
||||||
|
pub pubkey: Option<String>,
|
||||||
|
pub quantity: String,
|
||||||
|
pub time: u64,
|
||||||
|
pub title: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize)]
|
||||||
|
pub struct AdWithId {
|
||||||
|
pub id: String,
|
||||||
|
pub ad: Ad,
|
||||||
|
pub selected: bool,
|
||||||
|
}
|
||||||
|
|
||||||
pub enum PubkeyDecodeError {
|
pub enum PubkeyDecodeError {
|
||||||
BadChecksum,
|
BadChecksum,
|
||||||
|
@ -39,10 +84,3 @@ pub fn format_pubkey(raw: &str) -> Result<String, PubkeyDecodeError> {
|
||||||
&checksum[..3]
|
&checksum[..3]
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn redirect_302(url: &str) -> warp::reply::WithStatus<warp::reply::WithHeader<&str>> {
|
|
||||||
warp::reply::with_status(
|
|
||||||
warp::reply::with_header("", "Location", url),
|
|
||||||
http::status::StatusCode::FOUND,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
|
@ -38,7 +38,7 @@
|
||||||
{{#each ads}}
|
{{#each ads}}
|
||||||
<tr>
|
<tr>
|
||||||
<td><input type="radio" name="ad" value="{{this.id}}" aria-label="Sélectionner l'annonce" required/></td>
|
<td><input type="radio" name="ad" value="{{this.id}}" aria-label="Sélectionner l'annonce" required/></td>
|
||||||
<td onclick="ad_detail(event,'{{this.id}}')" title="Afficher le détail"><a href="ad/{{this.id}}">{{this.ad.title}}</a></td>
|
<td onclick="ad_detail(event,'{{this.id}}')" title="Afficher le détail"><a href="/admin/ad/{{this.id}}">{{this.ad.title}}</a></td>
|
||||||
<td>{{this.ad.quantity}}</td>
|
<td>{{this.ad.quantity}}</td>
|
||||||
<td>{{this.ad.author}}</td>
|
<td>{{this.ad.author}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -28,9 +28,10 @@
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
<form action="/admin/login" method="post">
|
<form action="/admin" method="post">
|
||||||
|
<input type="hidden" name="a" value="login" autocomplete="off"/>
|
||||||
<label for="f_psw">Mot de passe :</label>
|
<label for="f_psw">Mot de passe :</label>
|
||||||
<input type="password" id="f_psw" name="psw"/>
|
<input type="password" id="f_psw" name="psw" autofocus/>
|
||||||
<input type="submit" value="S'authentifier"/>
|
<input type="submit" value="S'authentifier"/>
|
||||||
</form>
|
</form>
|
||||||
</main>
|
</main>
|
||||||
|
|
|
@ -42,11 +42,11 @@
|
||||||
{{#each ads}}
|
{{#each ads}}
|
||||||
<tr>
|
<tr>
|
||||||
<td><input type="radio" name="ad" value="{{this.id}}" aria-label="Sélectionner l'annonce" required/></td>
|
<td><input type="radio" name="ad" value="{{this.id}}" aria-label="Sélectionner l'annonce" required/></td>
|
||||||
<td onclick="ad_detail(event,'{{this.id}}')" title="Afficher le détail"><a href="ad/{{this.id}}">{{this.ad.title}}</a></td>
|
<td onclick="ad_detail(event,'{{this.id}}')" title="Afficher le détail"><a href="/ad/{{this.id}}">{{this.ad.title}}</a></td>
|
||||||
<td>{{this.ad.quantity}}</td>
|
<td>{{this.ad.quantity}}</td>
|
||||||
<td>{{this.ad.author}}</td>
|
<td>{{this.ad.author}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr id="ad-detail-{{this.id}}" class="ad-detail ad-detail-no">
|
<tr id="ad-detail-{{this.id}}" class="ad-detail{{#unless this.selected}} ad-detail-no{{/unless}}">
|
||||||
<td colspan="4"><p>
|
<td colspan="4"><p>
|
||||||
{{#if this.ad.pubkey}}Clé publique : {{this.ad.pubkey}}<br/>{{/if}}
|
{{#if this.ad.pubkey}}Clé publique : {{this.ad.pubkey}}<br/>{{/if}}
|
||||||
Prix : {{this.ad.price}}
|
Prix : {{this.ad.price}}
|
||||||
|
|
Loading…
Reference in New Issue