431 lines
9.9 KiB
Rust
431 lines
9.9 KiB
Rust
use super::{cli, config, db, static_files, templates, utils::*};
|
|
|
|
use handlebars::to_json;
|
|
use serde::{Deserialize, Serialize};
|
|
use serde_json::value::Value as Json;
|
|
use sha2::{Digest, Sha512Trunc256};
|
|
use std::{
|
|
convert::{TryFrom, TryInto},
|
|
sync::{Arc, RwLock},
|
|
};
|
|
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(
|
|
config: config::Config,
|
|
dbs: db::Dbs,
|
|
templates: templates::Templates<'static>,
|
|
opt: cli::MainOpt,
|
|
) {
|
|
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 dbs = Arc::new(dbs);
|
|
|
|
let handle_index = {
|
|
let cache_ads = cache_ads.clone();
|
|
let templates = templates.clone();
|
|
move |errors: &[ErrorTemplate], new_ad_form_refill| {
|
|
warp::reply::html(
|
|
templates
|
|
.hb
|
|
.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();
|
|
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 cache_ads = cache_ads.clone();
|
|
move |query: NewAdQuery| {
|
|
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 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 cache_ads = cache_ads.clone();
|
|
move |query: RmAdQuery| {
|
|
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();
|
|
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()
|
|
}
|
|
};
|
|
|
|
let handle_admin_rm_ad = {
|
|
move |query: AdminRmAdQuery| {
|
|
if let Ok(ad_id) = hex::decode(query.ad) {
|
|
if let Ok(ad_id) = AdId::try_from(ad_id.as_ref()) {
|
|
if dbs.ads.remove(&ad_id).unwrap().is_some() {
|
|
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>>(),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
redirect_302("/admin").into_response()
|
|
}
|
|
};
|
|
|
|
let route_index = warp::path::end().and(warp::get().map(move || handle_index(&[], None)).or(
|
|
warp::post().and(
|
|
warp::body::form::<Query>().map(move |query: Query| match query {
|
|
Query::NewAdQuery(query) => handle_new_ad(query),
|
|
Query::RmAdQuery(query) => handle_rm_ad(query),
|
|
}),
|
|
),
|
|
));
|
|
|
|
let route_admin = warp::path("admin").and(
|
|
warp::path::end()
|
|
.and(
|
|
warp::cookie("admin")
|
|
.and(warp::body::form::<AdminQuery>())
|
|
.map({
|
|
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(
|
|
warp::post()
|
|
.and(warp::get().map(move || handle_admin(&[])))
|
|
.or(warp::body::form::<AdminLoginQuery>().map({
|
|
let config = config.clone();
|
|
move |query: AdminLoginQuery| {
|
|
if config.admin_passwords.contains(&query.psw) {
|
|
warp::reply::with_header(
|
|
redirect_302("/admin").into_response(),
|
|
"Set-Cookie",
|
|
format!(
|
|
"admin={}; path=/; HttpOnly",
|
|
urlencoding::encode(&query.psw)
|
|
),
|
|
)
|
|
.into_response()
|
|
} else {
|
|
handle_admin_login(&[ErrorTemplate {
|
|
text: "Mot de passe administrateur invalide",
|
|
}])
|
|
.into_response()
|
|
}
|
|
}
|
|
})),
|
|
))
|
|
.or(warp::path("logout").and(warp::path::end()).map(|| {
|
|
warp::reply::with_header(
|
|
redirect_302("/").into_response(),
|
|
"Set-Cookie",
|
|
"admin=; HttpOnly; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT",
|
|
)
|
|
.into_response()
|
|
})),
|
|
);
|
|
|
|
warp::serve(route_static.or(route_index).or(route_admin))
|
|
.run(config.listen)
|
|
.await;
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
struct IndexTemplate<'a> {
|
|
lang: &'a str,
|
|
errors: &'a [ErrorTemplate<'a>],
|
|
ads: &'a Json,
|
|
new_ad_form_refill: Option<NewAdQuery>,
|
|
}
|
|
|
|
#[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,
|
|
}
|