gmarche-rs/src/server.rs

728 lines
18 KiB
Rust

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/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.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>(&ad).ok()?,
selected: selected_ad.map_or(false, |i| i == ad_id),
id: ad_id,
})
})
.collect::<Vec<AdWithId>>(),
),
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::<Vec<Group>>(),
),
new_ad_form_refill,
},
)
.unwrap_or_else(|e| e.to_string()),
)
.build())
}
async fn serve_group<'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> {
if let Ok(group_name) = req.param("group") {
if let Some(group_id) = dbs.group_by_name.get(group_name).unwrap() {
let selected_ad = req.param("ad").ok();
Ok(tide::Response::builder(200)
.content_type(tide::http::mime::HTML)
.body(
templates
.hb
.render(
"group.html",
&GroupTemplate {
common: CommonTemplate {
lang: "fr",
root_url: &config.root_url,
title: &config.title,
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>(&ad).ok()?,
selected: selected_ad.map_or(false, |i| i == ad_id),
id: ad_id,
})
})
.collect::<Vec<AdWithId>>(),
),
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::<Vec<Group>>(),
),
new_ad_form_refill,
},
)
.unwrap_or_else(|e| e.to_string()),
)
.build())
} else {
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<Config>,
templates: Arc<Templates<'static>>,
dbs: Dbs,
errors: &[ErrorTemplate<'a>],
new_group_form_refill: Option<AdminNewGroupQuery>,
) -> tide::Result<tide::Response> {
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>(&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 {
lang: "fr",
root_url: &config.root_url,
title: &config.title,
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>(&ad).ok()?,
selected: selected_ad
.map_or(false, |i| i == ad_id),
id: ad_id,
})
})
.collect::<Vec<AdWithId>>(),
),
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::<Vec<Group>>(),
),
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<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.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>(&ad).ok()?,
selected: selected_ad.map_or(false, |i| i == ad_id),
id: ad_id,
})
})
.collect::<Vec<AdWithId>>(),
),
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::<Vec<Group>>(),
),
},
)
.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::<IndexQuery>().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<Config>,
templates: Arc<Templates<'static>>,
dbs: Dbs,
query: NewAdQuery,
) -> tide::Result<tide::Response> {
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<Config>,
templates: Arc<Templates<'static>>,
dbs: Dbs,
query: RmAdQuery,
) -> tide::Result<tide::Response> {
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::<Ad>(&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<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_admin_group(
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_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<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::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::<GroupId>();
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())
}
_ => 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::<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)
}