use super::{cli, config::*, db::*, queries::*, static_files, templates::*, utils::*}; use argon2::{ password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString}, Argon2, }; use handlebars::to_json; use std::{convert::TryFrom, sync::Arc}; pub async fn start_server( config: Config, dbs: Dbs, templates: Templates<'static>, opt: cli::MainOpt, ) { tide::log::start(); 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!("{}g/:group", config.root_url)).get({ let config = config.clone(); let templates = templates.clone(); let dbs = dbs.clone(); move |req: tide::Request<()>| { serve_group( req, config.clone(), templates.clone(), dbs.clone(), &[], None, ) } }); app.at(&format!("{}g/:group/:ad", config.root_url)).get({ let config = config.clone(); let templates = templates.clone(); let dbs = dbs.clone(); move |req: tide::Request<()>| { serve_group( req, config.clone(), templates.clone(), dbs.clone(), &[], None, ) } }); app.at(&format!("{}admin/g/:group", config.root_url)).get({ let config = config.clone(); let templates = templates.clone(); let dbs = dbs.clone(); move |req: tide::Request<()>| { handle_admin_group(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/g/", 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/g/:group", 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, templates: Arc>, dbs: Dbs, errors: &[ErrorTemplate<'a>], new_ad_form_refill: Option, ) -> tide::Result { 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::new(&config, errors), ads: &to_json( dbs.ad_by_group .scan_prefix(ROOT_GROUP_ID) .filter_map(|x| { let (k, ad) = x.ok()?; let ad_id = hex::encode(AdId::try_from(&k.as_ref()[16..32]).ok()?); Some(AdWithId { ad: bincode::deserialize::(&ad).ok()?, selected: selected_ad.map_or(false, |i| i == ad_id), id: ad_id, }) }) .collect::>(), ), groups: &to_json( dbs.group_by_group .scan_prefix(ROOT_GROUP_ID) .keys() .filter_map(|k| GroupId::try_from(&k.ok()?.as_ref()[16..32]).ok()) .filter_map(|subgroup_id| { bincode::deserialize(&dbs.group.get(subgroup_id).ok()??).ok() }) .collect::>(), ), new_ad_form_refill, }, ) .unwrap_or_else(|e| e.to_string()), ) .build()) } async fn serve_group<'a>( req: tide::Request<()>, config: Arc, templates: Arc>, dbs: Dbs, errors: &[ErrorTemplate<'a>], new_ad_form_refill: Option, ) -> tide::Result { if let Ok(group_name) = req.param("group") { if let Some(group_id) = dbs.group_by_name.get(group_name).unwrap() { if let Some(group) = dbs.group.get(&group_id).unwrap() { if let Ok(group) = bincode::deserialize::(&group) { let selected_ad = req.param("ad").ok(); return Ok(tide::Response::builder(200) .content_type(tide::http::mime::HTML) .body( templates .hb .render( "group.html", &GroupTemplate { common: CommonTemplate::new(&config, errors), ads: &to_json( dbs.ad_by_group .scan_prefix(group_id.clone()) .filter_map(|x| { let (k, ad) = x.ok()?; let ad_id = hex::encode( AdId::try_from(&k.as_ref()[16..32]).ok()?, ); Some(AdWithId { ad: bincode::deserialize::(&ad).ok()?, selected: selected_ad .map_or(false, |i| i == ad_id), id: ad_id, }) }) .collect::>(), ), parent_group_name: &dbs .group .get(&group.parent) .unwrap() .map_or_else(String::new, |parent_group| { bincode::deserialize::(&parent_group) .map_or_else( |_| String::new(), |parent_group| parent_group.name, ) }), group: &to_json(group), groups: &to_json( dbs.group_by_group .scan_prefix(group_id) .keys() .filter_map(|k| { GroupId::try_from(&k.ok()?.as_ref()[16..32]) .ok() }) .filter_map(|subgroup_id| { bincode::deserialize( &dbs.group.get(subgroup_id).ok()??, ) .ok() }) .collect::>(), ), new_ad_form_refill, }, ) .unwrap_or_else(|e| e.to_string()), ) .build()); } } } serve_index( req, config, templates, dbs, &[ErrorTemplate { text: "Le groupe demandé n'existe pas.", }], None, ) .await } else { serve_index(req, config, templates, dbs, &[], None).await } } async fn serve_admin_group<'a>( req: tide::Request<()>, config: Arc, templates: Arc>, dbs: Dbs, errors: &[ErrorTemplate<'a>], new_group_form_refill: Option, ) -> tide::Result { if let Ok(group_name) = req.param("group") { if let Some(group_id) = dbs.group_by_name.get(group_name).unwrap() { if let Some(group) = dbs.group.get(&group_id).unwrap() { if let Ok(group) = bincode::deserialize::(&group) { let selected_ad = req.param("ad").ok(); return Ok(tide::Response::builder(200) .content_type(tide::http::mime::HTML) .body( templates .hb .render( "admin_group.html", &AdminGroupTemplate { common: CommonTemplate::new(&config, errors), ads: &to_json( dbs.ad_by_group .scan_prefix(group_id.clone()) .filter_map(|x| { let (k, ad) = x.ok()?; let ad_id = hex::encode( AdId::try_from(&k.as_ref()[16..32]).ok()?, ); Some(AdWithId { ad: bincode::deserialize::(&ad).ok()?, selected: selected_ad .map_or(false, |i| i == ad_id), id: ad_id, }) }) .collect::>(), ), parent_group_name: &dbs .group .get(&group.parent) .unwrap() .map_or_else(String::new, |parent_group| { bincode::deserialize::(&parent_group) .map_or_else( |_| String::new(), |parent_group| parent_group.name, ) }), group: &to_json(group), groups: &to_json( dbs.group_by_group .scan_prefix(group_id) .keys() .filter_map(|k| { GroupId::try_from(&k.ok()?.as_ref()[16..32]) .ok() }) .filter_map(|subgroup_id| { bincode::deserialize( &dbs.group.get(subgroup_id).ok()??, ) .ok() }) .collect::>(), ), new_group_form_refill, }, ) .unwrap_or_else(|e| e.to_string()), ) .build()); } } } serve_admin( req, config, templates, dbs, &[ErrorTemplate { text: "Le groupe demandé n'existe pas.", }], ) .await } else { serve_admin(req, config, templates, dbs, &[]).await } } async fn serve_admin<'a>( req: tide::Request<()>, config: Arc, templates: Arc>, dbs: Dbs, errors: &[ErrorTemplate<'a>], ) -> tide::Result { 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::new(&config, errors), ads: &to_json( dbs.ad_by_group .scan_prefix(ROOT_GROUP_ID) .filter_map(|x| { let (k, ad) = x.ok()?; let ad_id = hex::encode(AdId::try_from(&k.as_ref()[16..32]).ok()?); Some(AdWithId { ad: bincode::deserialize::(&ad).ok()?, selected: selected_ad.map_or(false, |i| i == ad_id), id: ad_id, }) }) .collect::>(), ), groups: &to_json( dbs.group_by_group .scan_prefix(ROOT_GROUP_ID) .keys() .filter_map(|k| GroupId::try_from(&k.ok()?.as_ref()[16..32]).ok()) .filter_map(|subgroup_id| { bincode::deserialize(&dbs.group.get(subgroup_id).ok()??).ok() }) .collect::>(), ), }, ) .unwrap_or_else(|e| e.to_string()), ) .build()) } async fn serve_admin_login<'a>( _req: tide::Request<()>, config: Arc, templates: Arc>, errors: &[ErrorTemplate<'a>], ) -> tide::Result { Ok(tide::Response::builder(200) .content_type(tide::http::mime::HTML) .body( templates .hb .render( "admin_login.html", &AdminLoginTemplate { common: CommonTemplate::new(&config, errors), }, ) .unwrap_or_else(|e| e.to_string()), ) .build()) } async fn handle_post_index( mut req: tide::Request<()>, config: Arc, templates: Arc>, dbs: Dbs, ) -> tide::Result { match req.body_form::().await? { IndexQuery::NewAd(query) => { handle_new_ad(req, config.clone(), templates.clone(), dbs.clone(), query).await } IndexQuery::RmAd(query) => { handle_rm_ad(req, config.clone(), templates.clone(), dbs.clone(), query).await } } } async fn handle_new_ad( req: tide::Request<()>, config: Arc, templates: Arc>, dbs: Dbs, query: NewAdQuery, ) -> tide::Result { dbs.ad .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: Argon2::default() .hash_password(query.psw.as_bytes(), &SaltString::generate(&mut OsRng)) .unwrap() .to_string(), price: query.price, quantity: query.quantity, time: 0, title: query.title, }) .unwrap(), ) .unwrap(); dbs.ad.flush_async().await.unwrap(); Ok(tide::Redirect::new(&config.root_url).into()) } async fn handle_rm_ad( req: tide::Request<()>, config: Arc, templates: Arc>, dbs: Dbs, query: RmAdQuery, ) -> tide::Result { 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.ad.get(&ad_id).unwrap() { if let Ok(ad) = bincode::deserialize::(&raw) { if let Ok(password) = PasswordHash::new(&ad.password) { if Argon2::default() .verify_password(query.psw.as_bytes(), &password) .is_ok() { dbs.ad.remove(&ad_id).unwrap(); dbs.ad.flush_async().await.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, templates: Arc>, dbs: Dbs, ) -> tide::Result { 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_admin_group( req: tide::Request<()>, config: Arc, templates: Arc>, dbs: Dbs, ) -> tide::Result { if let Some(psw) = req.cookie("admin") { if config.admin_passwords.contains(&String::from(psw.value())) { serve_admin_group(req, config, templates, dbs, &[], None).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, templates: Arc>, dbs: Dbs, ) -> tide::Result { if let Some(psw) = req.cookie("admin") { if config.admin_passwords.contains(&String::from(psw.value())) { match req.body_form::().await? { AdminQuery::RmAd(query) => { if let Ok(ad_id) = hex::decode(query.ad) { if let Ok(ad_id) = AdId::try_from(ad_id.as_ref()) { dbs.ad.remove(&ad_id).unwrap(); dbs.ad.flush_async().await.unwrap(); } } Ok(tide::Redirect::new(&format!("{}admin", config.root_url)).into()) } AdminQuery::NewGroup(query) => { if let Some(Ok(parent_group_id)) = if query.parent.is_empty() { Some(Ok(ROOT_GROUP_ID)) } else { dbs.group_by_name .get(&query.parent) .unwrap() .map(|o| GroupId::try_from(o.as_ref())) } { if !dbs.group_by_name.contains_key(&query.name).unwrap() { let group_id = rand::random::(); dbs.group .insert( group_id, bincode::serialize(&Group { parent: parent_group_id, name: query.name.clone(), title: query.title, }) .unwrap(), ) .unwrap(); dbs.group_by_name .insert(query.name.clone(), &group_id) .unwrap(); dbs.group_by_group .insert([parent_group_id, group_id].concat(), &[]) .unwrap(); return Ok(tide::Redirect::new(&format!( "{}admin/g/{}", config.root_url, query.name )) .into()); } return Ok(tide::Redirect::new(&format!( "{}admin/g/{}", config.root_url, query.parent )) .into()); } Ok(tide::Redirect::new(&format!("{}admin", config.root_url)).into()) } AdminQuery::EditGroup(query) => { if let Some(Ok(group_id)) = if query.group.is_empty() { Some(Ok(ROOT_GROUP_ID)) } else { dbs.group_by_name .get(&query.group) .unwrap() .map(|o| GroupId::try_from(o.as_ref())) } { if let Some(group) = dbs.group.get(&group_id).unwrap() { if let Ok(group) = bincode::deserialize::(&group) { if query.group == query.name || !dbs.group_by_name.contains_key(&query.name).unwrap() { dbs.group .insert( group_id, bincode::serialize(&Group { parent: group.parent, name: query.name.clone(), title: query.title, }) .unwrap(), ) .unwrap(); if group.name != query.name { dbs.group_by_name.remove(&group.name).unwrap(); dbs.group_by_name .insert(query.name.clone(), &group_id) .unwrap(); } return Ok(tide::Redirect::new(&format!( "{}admin/g/{}", config.root_url, query.name )) .into()); } } } return Ok(tide::Redirect::new(&format!( "{}admin/g/{}", config.root_url, query.name )) .into()); } 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::Login(query) = req.body_form::().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, ) -> tide::Result { 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) }