gmarche-rs/src/server.rs

444 lines
11 KiB
Rust

use super::{cli, config::*, db::*, queries::*, static_files, templates::*, utils::*};
use handlebars::to_json;
use sha2::{Digest, Sha512Trunc256};
use std::{
convert::{TryFrom, TryInto},
sync::Arc,
};
pub async fn start_server(
config: Config,
dbs: Dbs,
templates: Templates<'static>,
opt: cli::MainOpt,
) {
let templates = Arc::new(templates);
let config = Arc::new(config);
let mut app = tide::new();
app.at(&format!("{}static", config.root_url))
.serve_dir(opt.dir.0.join(static_files::STATIC_DIR))
.unwrap();
app.at(&config.root_url).get({
let config = config.clone();
let templates = templates.clone();
let dbs = dbs.clone();
move |req: tide::Request<()>| {
serve_index(
req,
config.clone(),
templates.clone(),
dbs.clone(),
&[],
None,
)
}
});
app.at(&config.root_url).post({
let config = config.clone();
let templates = templates.clone();
let dbs = dbs.clone();
move |req: tide::Request<()>| {
handle_post_index(req, config.clone(), templates.clone(), dbs.clone())
}
});
app.at(&format!("{}ad/:ad", config.root_url)).post({
let config = config.clone();
let templates = templates.clone();
let dbs = dbs.clone();
move |req: tide::Request<()>| {
handle_post_index(req, config.clone(), templates.clone(), dbs.clone())
}
});
app.at(&format!("{}ad/:ad", config.root_url)).get({
let config = config.clone();
let templates = templates.clone();
let dbs = dbs.clone();
move |req: tide::Request<()>| {
serve_index(
req,
config.clone(),
templates.clone(),
dbs.clone(),
&[],
None,
)
}
});
app.at(&format!("{}admin", config.root_url)).get({
let config = config.clone();
let templates = templates.clone();
let dbs = dbs.clone();
move |req: tide::Request<()>| {
handle_admin(req, config.clone(), templates.clone(), dbs.clone())
}
});
app.at(&format!("{}admin/ad/:ad", config.root_url)).get({
let config = config.clone();
let templates = templates.clone();
let dbs = dbs.clone();
move |req: tide::Request<()>| {
handle_admin(req, config.clone(), templates.clone(), dbs.clone())
}
});
app.at(&format!("{}admin", config.root_url)).post({
let config = config.clone();
let templates = templates.clone();
let dbs = dbs.clone();
move |req: tide::Request<()>| {
handle_post_admin(req, config.clone(), templates.clone(), dbs.clone())
}
});
app.at(&format!("{}admin/ad/:ad", config.root_url)).post({
let config = config.clone();
move |req: tide::Request<()>| {
handle_post_admin(req, config.clone(), templates.clone(), dbs.clone())
}
});
app.at(&format!("{}admin/logout", config.root_url)).get({
let config = config.clone();
move |req: tide::Request<()>| handle_admin_logout(req, config.clone())
});
app.listen(config.listen).await.unwrap();
}
async fn serve_index<'a>(
req: tide::Request<()>,
config: Arc<Config>,
templates: Arc<Templates<'static>>,
dbs: Dbs,
errors: &[ErrorTemplate<'a>],
new_ad_form_refill: Option<NewAdQuery>,
) -> 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(
"index.html",
&IndexTemplate {
common: CommonTemplate {
lang: "fr",
root_url: &config.root_url,
title: &config.title,
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>>(),
),
new_ad_form_refill,
},
)
.unwrap_or_else(|e| e.to_string()),
)
.build())
}
async fn serve_admin<'a>(
req: tide::Request<()>,
config: Arc<Config>,
templates: Arc<Templates<'static>>,
dbs: 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 {
common: CommonTemplate {
lang: "fr",
root_url: &config.root_url,
title: &config.title,
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<()>,
config: Arc<Config>,
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 {
common: CommonTemplate {
lang: "fr",
root_url: &config.root_url,
title: &config.title,
errors,
},
},
)
.unwrap_or_else(|e| e.to_string()),
)
.build())
}
async fn handle_post_index(
mut req: tide::Request<()>,
config: Arc<Config>,
templates: Arc<Templates<'static>>,
dbs: Dbs,
) -> tide::Result<tide::Response> {
match req.body_form::<Query>().await? {
Query::NewAdQuery(query) => {
handle_new_ad(req, config.clone(), templates.clone(), dbs.clone(), query).await
}
Query::RmAdQuery(query) => {
handle_rm_ad(req, config.clone(), templates.clone(), dbs.clone(), query).await
}
}
}
async fn handle_new_ad(
req: tide::Request<()>,
config: Arc<Config>,
templates: Arc<Templates<'static>>,
dbs: 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,
config,
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(&config.root_url).into())
}
async fn handle_rm_ad(
req: tide::Request<()>,
config: Arc<Config>,
templates: Arc<Templates<'static>>,
dbs: 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,
config,
templates,
dbs,
&[ErrorTemplate {
text: "Le mot de passe de l'annonce est incorrect.",
}],
None,
)
.await;
}
}
}
}
}
Ok(tide::Redirect::new(&config.root_url).into())
}
async fn handle_admin(
req: tide::Request<()>,
config: Arc<Config>,
templates: Arc<Templates<'static>>,
dbs: Dbs,
) -> tide::Result<tide::Response> {
if let Some(psw) = req.cookie("admin") {
if config.admin_passwords.contains(&String::from(psw.value())) {
serve_admin(req, config, templates, dbs, &[]).await
} else {
serve_admin_login(
req,
config,
templates,
&[ErrorTemplate {
text: "Mot de passe administrateur invalide",
}],
)
.await
}
} else {
serve_admin_login(req, config, templates, &[]).await
}
}
async fn handle_post_admin(
mut req: tide::Request<()>,
config: Arc<Config>,
templates: Arc<Templates<'static>>,
dbs: Dbs,
) -> tide::Result<tide::Response> {
if let Some(psw) = req.cookie("admin") {
if config.admin_passwords.contains(&String::from(psw.value())) {
match req.body_form::<AdminQuery>().await? {
AdminQuery::RmAdQuery(query) => {
if let Ok(ad_id) = hex::decode(query.ad) {
if let Ok(ad_id) = AdId::try_from(ad_id.as_ref()) {
dbs.ads.remove(&ad_id).unwrap();
dbs.ads.flush().unwrap();
}
}
Ok(tide::Redirect::new(&format!("{}admin", config.root_url)).into())
}
_ => serve_admin(req, config, templates, dbs, &[]).await,
}
} else {
serve_admin_login(
req,
config,
templates,
&[ErrorTemplate {
text: "Mot de passe administrateur invalide",
}],
)
.await
}
} else if let AdminQuery::LoginQuery(query) = req.body_form::<AdminQuery>().await? {
if config.admin_passwords.contains(&query.psw) {
serve_admin(req, config.clone(), 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(config.root_url.clone());
if let Some(domain) = &config.cookies_domain {
cookie.set_domain(domain.clone());
}
if config.cookies_https_only {
cookie.set_secure(Some(true));
}
r.insert_cookie(cookie);
r
})
} else {
serve_admin_login(
req,
config,
templates,
&[ErrorTemplate {
text: "Mot de passe administrateur invalide",
}],
)
.await
}
} else {
serve_admin_login(req, config, templates, &[]).await
}
}
async fn handle_admin_logout(
req: tide::Request<()>,
config: Arc<Config>,
) -> tide::Result<tide::Response> {
let mut r: tide::Response = tide::Redirect::new("/").into();
if let Some(mut cookie) = req.cookie("admin") {
cookie.set_path(config.root_url.clone());
if let Some(domain) = &config.cookies_domain {
cookie.set_domain(domain.clone());
}
r.remove_cookie(cookie);
}
Ok(r)
}