0
0
Fork 0
mirror of https://git.verdigado.com/NB-Public/simple-wkd.git synced 2024-10-30 05:05:52 +01:00

Revamp error handling, disable logging for now

This commit is contained in:
Delta1925 2023-04-17 22:33:10 +02:00
parent 9f4c5fbc05
commit 0f5b9ae1d6
No known key found for this signature in database
GPG key ID: 1C21ACE44193CB25
8 changed files with 167 additions and 303 deletions

1
backend/Cargo.lock generated
View file

@ -1966,6 +1966,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"actix-files", "actix-files",
"actix-web", "actix-web",
"anyhow",
"chrono", "chrono",
"flexi_logger", "flexi_logger",
"lettre", "lettre",

View file

@ -6,6 +6,7 @@ edition = "2021"
[dependencies] [dependencies]
actix-files = "0.6.2" actix-files = "0.6.2"
actix-web = "4.3.1" actix-web = "4.3.1"
anyhow = "1.0.70"
chrono = "0.4.24" chrono = "0.4.24"
flexi_logger = "0.25.3" flexi_logger = "0.25.3"
lettre = "0.10.4" lettre = "0.10.4"

View file

@ -1,56 +1,24 @@
use chrono::Utc; use chrono::Utc;
use lettre::message::header::ContentType; use lettre::message::header::ContentType;
use log::{debug, error, trace, warn};
use crate::errors::Error; use crate::errors::SpecialErrors;
use crate::management::{delete_key, Action, Pending}; use crate::management::{delete_key, Action, Pending};
use crate::pending_path; use crate::pending_path;
use crate::settings::{MAILER, ROOT_FOLDER, SETTINGS}; use crate::settings::{MAILER, ROOT_FOLDER, SETTINGS};
use crate::utils::{get_email_from_cert, get_filename, parse_pem}; use crate::utils::{get_email_from_cert, parse_pem, read_file};
use anyhow::{anyhow, bail, Result};
use lettre::{Message, Transport}; use lettre::{Message, Transport};
use std::fs; use std::fs;
use std::path::Path; use std::path::Path;
pub fn confirm_action(token: &str) -> Result<(Action, String), Error> { pub fn confirm_action(token: &str) -> Result<(Action, String)> {
trace!("Handling token {}", token);
let pending_path = pending_path!().join(token); let pending_path = pending_path!().join(token);
let content = if pending_path.is_file() { let content = read_file(&pending_path)?;
match fs::read_to_string(&pending_path) { let key = toml::from_str::<Pending>(&content)?;
Ok(content) => content,
Err(_) => {
warn!(
"Token {} was requested, but can't be read to string!",
token
);
return Err(Error::Inaccessible);
}
}
} else {
trace!("Requested token {} isn't a file", token);
return Err(Error::MissingPending);
};
let key = match toml::from_str::<Pending>(&content) {
Ok(key) => key,
Err(_) => {
warn!("Error while deserializing token {}!", token);
return Err(Error::DeserializeData);
}
};
if Utc::now().timestamp() - key.timestamp() > SETTINGS.max_age { if Utc::now().timestamp() - key.timestamp() > SETTINGS.max_age {
match fs::remove_file(&pending_path) { fs::remove_file(&pending_path)?;
Ok(_) => { Err(SpecialErrors::ExpiredRequest)?
debug!(
"Deleted stale token {}",
get_filename(&pending_path).unwrap()
);
Err(Error::MissingPending)
}
Err(_) => {
warn!("Stale token {} can't be deleted!", token);
Err(Error::Inaccessible)
}
}
} else { } else {
let address = match key.action() { let address = match key.action() {
Action::Add => { Action::Add => {
@ -59,46 +27,24 @@ pub fn confirm_action(token: &str) -> Result<(Action, String), Error> {
let domain = match email.split('@').last() { let domain = match email.split('@').last() {
Some(domain) => domain.to_string(), Some(domain) => domain.to_string(),
None => { None => {
warn!("Error while parsing email's domain in token {}", token); Err(SpecialErrors::MalformedEmail)?
return Err(Error::ParseEmail);
} }
}; };
match sequoia_net::wkd::insert(ROOT_FOLDER, domain, SETTINGS.variant, &cert) { sequoia_net::wkd::insert(ROOT_FOLDER, domain, SETTINGS.variant, &cert)?;
Ok(_) => email, email
Err(_) => {
warn!("Unable to create a wkd entry for token {}", token);
return Err(Error::AddingKey);
} }
Action::Delete => {
delete_key(key.data())?;
key.data().to_owned()
} }
}
Action::Delete => match delete_key(key.data()) {
Ok(_) => key.data().to_owned(),
Err(error) => {
warn!("Unable to delete key for user {}", key.data());
return Err(error);
}
},
}; };
debug!("Token {} was confirmed", token); fs::remove_file(&pending_path)?;
match fs::remove_file(&pending_path) {
Ok(_) => {
trace!(
"Deleted confirmed token {}",
pending_path.file_name().unwrap().to_str().unwrap()
);
Ok((*key.action(), address)) Ok((*key.action(), address))
} }
Err(_) => {
warn!("Unable to delete confirmed token {}", token);
Err(Error::Inaccessible)
}
}
}
} }
pub fn send_confirmation_email(address: &str, action: &Action, token: &str) -> Result<(), Error> { pub fn send_confirmation_email(address: &str, action: &Action, token: &str) -> Result<()> {
debug!("Sending email to {}", address); let template = read_file(&Path::new("assets").join("mail-template.html"))?;
let template = fs::read_to_string(Path::new("assets").join("mail-template.html")).unwrap();
let mut url = SETTINGS let mut url = SETTINGS
.external_url .external_url
.join("api/") .join("api/")
@ -110,17 +56,10 @@ pub fn send_confirmation_email(address: &str, action: &Action, token: &str) -> R
.from(match SETTINGS.mail_settings.mail_from.parse() { .from(match SETTINGS.mail_settings.mail_from.parse() {
Ok(mailbox) => mailbox, Ok(mailbox) => mailbox,
Err(_) => { Err(_) => {
error!("Unable to parse the email in the settings!");
panic!("Unable to parse the email in the settings!") panic!("Unable to parse the email in the settings!")
} }
}) })
.to(match address.parse() { .to(address.parse()?)
Ok(mailbox) => mailbox,
Err(_) => {
warn!("Error while parsing destination email for token {}", token);
return Err(Error::ParseEmail);
}
})
.subject( .subject(
SETTINGS SETTINGS
.mail_settings .mail_settings
@ -132,24 +71,8 @@ pub fn send_confirmation_email(address: &str, action: &Action, token: &str) -> R
template template
.replace("{{%u}}", url.as_ref()) .replace("{{%u}}", url.as_ref())
.replace("{{%a}}", &action.to_string().to_lowercase()), .replace("{{%a}}", &action.to_string().to_lowercase()),
); )?;
let message = match email { MAILER.send(&email)?;
Ok(message) => message,
Err(_) => {
warn!("Unable to build email for token {}", token);
return Err(Error::MailGeneration);
}
};
match MAILER.send(&message) {
Ok(_) => {
debug!("successfully sent email to {}", address);
Ok(()) Ok(())
}
Err(_) => {
warn!("Unable to send email to {}", address);
Err(Error::SendMail)
}
}
} }

View file

@ -1,50 +1,69 @@
use actix_web::{http::StatusCode, HttpResponseBuilder, ResponseError}; use actix_web::{http::StatusCode, HttpResponseBuilder, ResponseError};
use thiserror::Error; use anyhow::Error;
use std::fmt::Display;
use thiserror::Error as DeriveError;
use crate::utils::return_outcome; use crate::utils::return_outcome;
#[derive(Error, Debug, Clone, Copy)] #[derive(Debug, DeriveError)]
pub enum Error { pub enum SpecialErrors {
#[error("(0x01) Cert is invalid")] #[error("The request had expired!")]
InvalidCert, ExpiredRequest,
#[error("(0x02) Error while parsing cert")] #[error("The key for the requested user does not exist!")]
ParseCert, InexistingUser,
#[error("(0x03) Error while parsing an E-Mail address")] #[error("Could not parse user email: malformed email")]
ParseEmail, MalformedEmail,
#[error("(0x04) There is no pending request associated to this token")] #[error("Could not find any primay user email in the keyblock!")]
MissingPending, MalformedCert,
#[error("(0x05) Requested key does not exist")] #[error("The requested file does not exist!")]
MissingKey,
#[error("(0x06) No E-Mail found in the certificate")]
MissingMail,
#[error("(0x07) Error while sending the E-Mail")]
SendMail,
#[error("(0x08) rror while serializing data")]
SerializeData,
#[error("(0x09) Error while deserializing data")]
DeserializeData,
#[error("(0x0A) The file is inaccessible")]
Inaccessible,
#[error("(0x0B) Error while adding a key to the wkd")]
AddingKey,
#[error("(0x0C) Error while generating the wkd path")]
PathGeneration,
#[error("(0x0D) Error while generating the email")]
MailGeneration,
#[error("(0x0E) Wrong email domain")]
WrongDomain,
#[error("(0x0F) The requested file does not exist")]
MissingFile, MissingFile,
#[error("User email rejected: domain not allowed")]
UnallowedDomain,
} }
impl ResponseError for Error { #[derive(Debug)]
pub enum CompatErr {
AnyhowErr(Error),
SpecialErr(SpecialErrors),
}
impl Display for CompatErr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::AnyhowErr(error) => write!(f, "{}", error),
Self::SpecialErr(error) => write!(f, "{}", error),
}
}
}
impl From<SpecialErrors> for CompatErr {
fn from(value: SpecialErrors) -> Self {
CompatErr::SpecialErr(value)
}
}
impl From<Error> for CompatErr {
fn from(value: Error) -> Self {
if value.is::<SpecialErrors>() {
CompatErr::from(value.downcast::<SpecialErrors>().unwrap())
} else {
CompatErr::AnyhowErr(value)
}
}
}
impl ResponseError for CompatErr {
fn status_code(&self) -> actix_web::http::StatusCode { fn status_code(&self) -> actix_web::http::StatusCode {
match self { match self {
Self::MissingPending => StatusCode::from_u16(404).unwrap(), Self::AnyhowErr(_) => StatusCode::INTERNAL_SERVER_ERROR,
Self::MissingKey => StatusCode::from_u16(404).unwrap(), Self::SpecialErr(error) => match error {
Self::MissingFile => StatusCode::from_u16(404).unwrap(), SpecialErrors::ExpiredRequest => StatusCode::BAD_REQUEST,
Self::WrongDomain => StatusCode::from_u16(401).unwrap(), SpecialErrors::InexistingUser => StatusCode::NOT_FOUND,
_ => StatusCode::from_u16(500).unwrap(), SpecialErrors::MalformedCert => StatusCode::BAD_REQUEST,
SpecialErrors::MalformedEmail => StatusCode::BAD_REQUEST,
SpecialErrors::MissingFile => StatusCode::NOT_FOUND,
SpecialErrors::UnallowedDomain => StatusCode::UNAUTHORIZED,
}
} }
} }

View file

@ -5,11 +5,11 @@ mod settings;
mod utils; mod utils;
use crate::confirmation::{confirm_action, send_confirmation_email}; use crate::confirmation::{confirm_action, send_confirmation_email};
use crate::errors::Error; use crate::errors::CompatErr;
use crate::management::{clean_stale, store_pending_addition, store_pending_deletion, Action}; use crate::management::{clean_stale, store_pending_addition, store_pending_deletion, Action};
use crate::settings::{ROOT_FOLDER, SETTINGS}; use crate::settings::{ROOT_FOLDER, SETTINGS};
use crate::utils::{ use crate::utils::{
gen_random_token, get_email_from_cert, is_email_allowed, parse_pem, return_outcome, gen_random_token, get_email_from_cert, is_email_allowed, parse_pem, return_outcome, read_file,
}; };
use actix_files::Files; use actix_files::Files;
@ -18,7 +18,8 @@ use actix_web::http::StatusCode;
use actix_web::{ use actix_web::{
get, post, web, App, HttpRequest, HttpResponse, HttpResponseBuilder, HttpServer, Result, get, post, web, App, HttpRequest, HttpResponse, HttpResponseBuilder, HttpServer, Result,
}; };
use log::{debug, error, info}; use anyhow::anyhow;
use errors::SpecialErrors;
use serde::Deserialize; use serde::Deserialize;
use std::env; use std::env;
use std::fs; use std::fs;
@ -47,7 +48,6 @@ async fn main() -> std::io::Result<()> {
env::set_var("RUST_LOG", format!("simple_wkd={}", value)); env::set_var("RUST_LOG", format!("simple_wkd={}", value));
} }
if init_logger().is_err() { if init_logger().is_err() {
error!("Could not set up logger!");
panic!("Could not set up logger!") panic!("Could not set up logger!")
}; };
fs::create_dir_all(pending_path!())?; fs::create_dir_all(pending_path!())?;
@ -55,15 +55,9 @@ async fn main() -> std::io::Result<()> {
let mut metronome = time::interval(time::Duration::from_secs(SETTINGS.cleanup_interval)); let mut metronome = time::interval(time::Duration::from_secs(SETTINGS.cleanup_interval));
loop { loop {
metronome.tick().await; metronome.tick().await;
info!("Running cleanup...");
clean_stale(SETTINGS.max_age); clean_stale(SETTINGS.max_age);
info!("Cleanup completed!");
} }
}); });
info!(
"Running server on http://{}:{} (External URL: {})",
SETTINGS.bind_host, SETTINGS.port, SETTINGS.external_url
);
HttpServer::new(|| { HttpServer::new(|| {
App::new() App::new()
.service(submit) .service(submit)
@ -80,7 +74,7 @@ async fn main() -> std::io::Result<()> {
.await .await
} }
async fn index(req: HttpRequest) -> Result<HttpResponse, Error> { async fn index(req: HttpRequest) -> Result<HttpResponse, CompatErr> {
let path = webpage_path!().join(req.match_info().query("filename")); let path = webpage_path!().join(req.match_info().query("filename"));
for file in &["", "index.html"] { for file in &["", "index.html"] {
let path = if file.is_empty() { let path = if file.is_empty() {
@ -89,55 +83,44 @@ async fn index(req: HttpRequest) -> Result<HttpResponse, Error> {
path.join(file) path.join(file)
}; };
if path.is_file() { if path.is_file() {
let template = match fs::read_to_string(&path) { let template = read_file(&path)?;
Ok(template) => template,
Err(_) => {
debug!("File {} is inaccessible", path.display());
return Err(Error::Inaccessible);
}
};
let page = template.replace("((%u))", SETTINGS.external_url.as_ref()); let page = template.replace("((%u))", SETTINGS.external_url.as_ref());
return Ok(HttpResponseBuilder::new(StatusCode::OK) return Ok(HttpResponseBuilder::new(StatusCode::OK)
.insert_header(ContentType::html()) .insert_header(ContentType::html())
.body(page)); .body(page));
} }
} }
debug!("File {} does not exist", path.display()); Err(SpecialErrors::MissingFile)?
Err(Error::MissingFile)
} }
#[post("/api/submit")] #[post("/api/submit")]
async fn submit(pem: web::Form<Key>) -> Result<HttpResponse, Error> { async fn submit(pem: web::Form<Key>) -> Result<HttpResponse, CompatErr> {
let cert = parse_pem(&pem.key)?; let cert = parse_pem(&pem.key)?;
let email = get_email_from_cert(&cert)?; let email = get_email_from_cert(&cert)?;
is_email_allowed(&email)?; is_email_allowed(&email)?;
let token = gen_random_token(); let token = gen_random_token();
store_pending_addition(pem.key.clone(), &email, &token)?; store_pending_addition(pem.key.clone(), &email, &token)?;
send_confirmation_email(&email, &Action::Add, &token)?; send_confirmation_email(&email, &Action::Add, &token)?;
info!("User {} submitted a key!", &email); Ok(return_outcome(Ok("You submitted your key successfully!"))?)
return_outcome(Ok("You submitted your key successfully!"))
} }
#[get("/api/confirm")] #[get("/api/confirm")]
async fn confirm(token: web::Query<Token>) -> Result<HttpResponse, Error> { async fn confirm(token: web::Query<Token>) -> Result<HttpResponse, CompatErr> {
let (action, email) = confirm_action(&token.token)?; let (action, _email) = confirm_action(&token.token)?;
match action { match action {
Action::Add => { Action::Add => {
info!("Key for user {} was added successfully!", email); Ok(return_outcome(Ok("Your key was added successfully!"))?)
return_outcome(Ok("Your key was added successfully!"))
} }
Action::Delete => { Action::Delete => {
info!("Key for user {} was deleted successfully!", email); Ok(return_outcome(Ok("Your key was deleted successfully!"))?)
return_outcome(Ok("Your key was deleted successfully!"))
} }
} }
} }
#[get("/api/delete")] #[get("/api/delete")]
async fn delete(email: web::Query<Email>) -> Result<HttpResponse, Error> { async fn delete(email: web::Query<Email>) -> Result<HttpResponse, CompatErr> {
let token = gen_random_token(); let token = gen_random_token();
store_pending_deletion(email.email.clone(), &token)?; store_pending_deletion(email.email.clone(), &token)?;
send_confirmation_email(&email.email, &Action::Delete, &token)?; send_confirmation_email(&email.email, &Action::Delete, &token)?;
info!("User {} requested the deletion of his key!", email.email); Ok(return_outcome(Ok("You requested the deletion of your key successfully!"))?)
return_outcome(Ok("You requested the deletion of your key successfully!"))
} }

View file

@ -1,10 +1,9 @@
use crate::pending_path; use crate::pending_path;
use crate::settings::ROOT_FOLDER; use crate::settings::ROOT_FOLDER;
use crate::utils::{get_user_file_path, key_exists}; use crate::utils::{get_user_file_path, key_exists, read_file};
use crate::{errors::Error, utils::get_filename};
use anyhow::Result;
use chrono::Utc; use chrono::Utc;
use log::{debug, error, warn};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{fmt::Display, fs, path::Path}; use std::{fmt::Display, fs, path::Path};
@ -54,87 +53,49 @@ impl Pending {
} }
} }
fn store_pending(pending: &Pending, token: &str) -> Result<(), Error> { fn store_pending(pending: &Pending, token: &str) -> Result<()> {
let serialized = match toml::to_string(pending) { let serialized = toml::to_string(pending)?;
Ok(serialized) => serialized, fs::write(pending_path!().join(token), serialized)?;
Err(_) => return Err(Error::SerializeData),
};
match fs::write(pending_path!().join(token), serialized) {
Ok(_) => Ok(()),
Err(_) => Err(Error::Inaccessible),
}
}
pub fn store_pending_addition(pem: String, email: &str, token: &str) -> Result<(), Error> {
let pending = Pending::build_add(pem);
store_pending(&pending, token)?;
debug!("Stored submission from {} with token {}", email, token);
Ok(()) Ok(())
} }
pub fn store_pending_deletion(email: String, token: &str) -> Result<(), Error> { pub fn store_pending_addition(pem: String, _email: &str, token: &str) -> Result<()> {
if let Err(error) = key_exists(&email) { let pending = Pending::build_add(pem);
match error { store_pending(&pending, token)?;
Error::PathGeneration => debug!("Error while generating path for user {}", email), Ok(())
Error::MissingKey => debug!("There is no key for user {}", email), }
_ => error!("An unexpected error occoured!"),
} pub fn store_pending_deletion(email: String, token: &str) -> Result<()> {
return Err(error); key_exists(&email)?;
} let pending = Pending::build_delete(email);
let pending = Pending::build_delete(email.clone());
store_pending(&pending, token)?; store_pending(&pending, token)?;
debug!(
"Stored deletion request from {} with token {}",
email, token
);
Ok(()) Ok(())
} }
pub fn clean_stale(max_age: i64) { pub fn clean_stale(max_age: i64) {
for path in fs::read_dir(pending_path!()).unwrap().flatten() { for path in fs::read_dir(pending_path!()).unwrap().flatten() {
let file_path = path.path(); let file_path = path.path();
if file_path.is_file() { let content = match read_file(&file_path) {
let content = match fs::read_to_string(&file_path) {
Ok(content) => content, Ok(content) => content,
Err(_) => { Err(_) => {
warn!(
"Could not read contents of token {} to string",
get_filename(&file_path).unwrap()
);
continue; continue;
} }
}; };
let key = match toml::from_str::<Pending>(&content) { let key = match toml::from_str::<Pending>(&content) {
Ok(key) => key, Ok(key) => key,
Err(_) => { Err(_) => {
warn!(
"Could not deserialize token {}",
get_filename(&file_path).unwrap()
);
continue; continue;
} }
}; };
let now = Utc::now().timestamp(); let now = Utc::now().timestamp();
if now - key.timestamp() > max_age { if now - key.timestamp() > max_age {
if fs::remove_file(&file_path).is_err() { let _ = fs::remove_file(&file_path);
{
warn!(
"Could not delete stale token {}",
get_filename(&file_path).unwrap()
);
continue;
};
}
debug!("Deleted stale token {}", get_filename(&file_path).unwrap())
}
} }
} }
} }
pub fn delete_key(email: &str) -> Result<(), Error> { pub fn delete_key(email: &str) -> Result<()> {
let path = Path::new(&ROOT_FOLDER).join(get_user_file_path(email)?); let path = Path::new(&ROOT_FOLDER).join(get_user_file_path(email)?);
match fs::remove_file(path) { fs::remove_file(path)?;
Ok(_) => Ok(()), Ok(())
Err(_) => Err(Error::Inaccessible),
}
} }

View file

@ -1,11 +1,12 @@
use lettre::{transport::smtp::authentication::Credentials, SmtpTransport}; use lettre::{transport::smtp::authentication::Credentials, SmtpTransport};
use log::{debug, error, warn};
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use sequoia_net::wkd::Variant; use sequoia_net::wkd::Variant;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fs; use std::{path::{PathBuf}};
use url::Url; use url::Url;
use crate::utils::read_file;
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct Settings { pub struct Settings {
#[serde(with = "VariantDef")] #[serde(with = "VariantDef")]
@ -44,27 +45,22 @@ pub enum SMTPEncryption {
} }
fn get_settings() -> Settings { fn get_settings() -> Settings {
debug!("Reading settings..."); let content = match read_file(&PathBuf::from("config.toml")) {
let content = match fs::read_to_string("config.toml") {
Ok(content) => content, Ok(content) => content,
Err(_) => { Err(_) => {
error!("Unable to access settings file!");
panic!("Unable to access settings file!") panic!("Unable to access settings file!")
} }
}; };
let settings = match toml::from_str(&content) { let settings = match toml::from_str(&content) {
Ok(settings) => settings, Ok(settings) => settings,
Err(_) => { Err(_) => {
error!("Unable to parse settings from file!");
panic!("Unable to parse settings from file!") panic!("Unable to parse settings from file!")
} }
}; };
debug!("Successfully read setting!");
settings settings
} }
fn get_mailer() -> SmtpTransport { fn get_mailer() -> SmtpTransport {
debug!("Setting up SMTP...");
let creds = Credentials::new( let creds = Credentials::new(
SETTINGS.mail_settings.smtp_username.to_owned(), SETTINGS.mail_settings.smtp_username.to_owned(),
SETTINGS.mail_settings.smtp_password.to_owned(), SETTINGS.mail_settings.smtp_password.to_owned(),
@ -78,18 +74,13 @@ fn get_mailer() -> SmtpTransport {
let mailer = match builder { let mailer = match builder {
Ok(builder) => builder, Ok(builder) => builder,
Err(_) => { Err(_) => {
error!("Unable to set up smtp");
panic!("Unable to set up smtp") panic!("Unable to set up smtp")
} }
} }
.credentials(creds) .credentials(creds)
.port(SETTINGS.mail_settings.smtp_port) .port(SETTINGS.mail_settings.smtp_port)
.build(); .build();
debug!("Checking connection..."); let _ = mailer.test_connection();
if mailer.test_connection().is_err() {
warn!("Connection test to smtp host failed!");
}
debug!("SMTP setup successful!");
mailer mailer
} }

View file

@ -1,12 +1,15 @@
use crate::errors::SpecialErrors;
use crate::settings::ROOT_FOLDER;
use crate::settings::SETTINGS; use crate::settings::SETTINGS;
use crate::{errors::Error, settings::ROOT_FOLDER};
use actix_web::{ use actix_web::{
http::{header::ContentType, StatusCode}, http::{header::ContentType, StatusCode},
HttpResponse, HttpResponseBuilder, HttpResponse, HttpResponseBuilder,
}; };
use flexi_logger::{style, DeferredNow, FileSpec, FlexiLoggerError, Logger, LoggerHandle, Record}; use anyhow::{anyhow, bail, Result};
use log::debug; use flexi_logger::{
detailed_format, style, DeferredNow, FileSpec, FlexiLoggerError, Logger, LoggerHandle, Record,
};
use rand::{distributions::Alphanumeric, thread_rng, Rng}; use rand::{distributions::Alphanumeric, thread_rng, Rng};
use sequoia_net::wkd::Url; use sequoia_net::wkd::Url;
use sequoia_openpgp::{parse::Parse, policy::StandardPolicy, Cert}; use sequoia_openpgp::{parse::Parse, policy::StandardPolicy, Cert};
@ -29,26 +32,29 @@ macro_rules! webpage_path {
}; };
} }
pub fn is_email_allowed(email: &str) -> Result<(), Error> { pub fn read_file(path: &PathBuf) -> Result<String> {
if path.is_file() {
Ok(fs::read_to_string(path)?)
} else {
Err(SpecialErrors::MissingFile)?
}
}
pub fn is_email_allowed(email: &str) -> Result<()> {
let allowed = match email.split('@').last() { let allowed = match email.split('@').last() {
Some(domain) => SETTINGS.allowed_domains.contains(&domain.to_string()), Some(domain) => SETTINGS.allowed_domains.contains(&domain.to_string()),
None => return Err(Error::ParseEmail), None => Err(SpecialErrors::MalformedEmail)?,
}; };
if !allowed { if !allowed {
return Err(Error::WrongDomain); Err(SpecialErrors::UnallowedDomain)?;
} }
Ok(()) Ok(())
} }
pub fn parse_pem(pemfile: &str) -> Result<Cert, Error> { pub fn parse_pem(pemfile: &str) -> Result<Cert> {
let cert = match sequoia_openpgp::Cert::from_bytes(pemfile.as_bytes()) { let cert = sequoia_openpgp::Cert::from_bytes(pemfile.as_bytes())?;
Ok(cert) => cert,
Err(_) => return Err(Error::ParseCert),
};
let policy = StandardPolicy::new(); let policy = StandardPolicy::new();
if cert.with_policy(&policy, None).is_err() { cert.with_policy(&policy, None)?;
return Err(Error::InvalidCert);
};
Ok(cert) Ok(cert)
} }
@ -57,46 +63,31 @@ pub fn gen_random_token() -> String {
(0..10).map(|_| rng.sample(Alphanumeric) as char).collect() (0..10).map(|_| rng.sample(Alphanumeric) as char).collect()
} }
pub fn get_email_from_cert(cert: &Cert) -> Result<String, Error> { pub fn get_email_from_cert(cert: &Cert) -> Result<String> {
let policy = StandardPolicy::new(); let policy = StandardPolicy::new();
let validcert = match cert.with_policy(&policy, None) { let validcert = cert.with_policy(&policy, None)?;
Ok(validcert) => validcert, let userid_opt = validcert.primary_userid()?;
Err(_) => return Err(Error::InvalidCert), let email_opt = userid_opt.email()?;
};
let userid_opt = match validcert.primary_userid() {
Ok(userid_opt) => userid_opt,
Err(_) => return Err(Error::ParseCert),
};
let email_opt = match userid_opt.email() {
Ok(email_opt) => email_opt,
Err(_) => return Err(Error::ParseCert),
};
match email_opt { match email_opt {
Some(email) => Ok(email), Some(email) => Ok(email),
None => Err(Error::MissingMail), None => Err(SpecialErrors::MalformedCert)?,
} }
} }
pub fn get_user_file_path(email: &str) -> Result<PathBuf, Error> { pub fn get_user_file_path(email: &str) -> Result<PathBuf> {
let wkd_url = match Url::from(email) { let wkd_url = Url::from(email)?;
Ok(wkd_url) => wkd_url, wkd_url.to_file_path(SETTINGS.variant)
Err(_) => return Err(Error::PathGeneration),
};
match wkd_url.to_file_path(SETTINGS.variant) {
Ok(path) => Ok(path),
Err(_) => Err(Error::PathGeneration),
}
} }
pub fn key_exists(email: &str) -> Result<bool, Error> { pub fn key_exists(email: &str) -> Result<bool> {
let path = get_user_file_path(email)?; let path = get_user_file_path(email)?;
if !Path::new(&ROOT_FOLDER).join(path).is_file() { if !Path::new(&ROOT_FOLDER).join(path).is_file() {
return Err(Error::MissingKey); Err(SpecialErrors::InexistingUser)?
} }
Ok(true) Ok(true)
} }
pub fn get_filename(path: &Path) -> Option<&str> { pub fn _get_filename(path: &Path) -> Option<&str> {
path.file_name()?.to_str() path.file_name()?.to_str()
} }
@ -135,7 +126,7 @@ pub fn init_logger() -> Result<LoggerHandle, FlexiLoggerError> {
Logger::try_with_env_or_str("simple_wkd=debug")? Logger::try_with_env_or_str("simple_wkd=debug")?
.log_to_file(FileSpec::default().directory("logs")) .log_to_file(FileSpec::default().directory("logs"))
.duplicate_to_stdout(flexi_logger::Duplicate::All) .duplicate_to_stdout(flexi_logger::Duplicate::All)
.format_for_files(custom_monochrome_format) .format_for_files(detailed_format)
.adaptive_format_for_stdout(flexi_logger::AdaptiveFormat::Custom( .adaptive_format_for_stdout(flexi_logger::AdaptiveFormat::Custom(
custom_monochrome_format, custom_monochrome_format,
custom_color_format, custom_color_format,
@ -144,15 +135,9 @@ pub fn init_logger() -> Result<LoggerHandle, FlexiLoggerError> {
.start() .start()
} }
pub fn return_outcome(data: Result<&str, &str>) -> Result<HttpResponse, Error> { pub fn return_outcome(data: Result<&str, &str>) -> Result<HttpResponse> {
let path = webpage_path!().join("status").join("index.html"); let path = webpage_path!().join("status").join("index.html");
let template = match fs::read_to_string(&path) { let template = read_file(&path)?;
Ok(template) => template,
Err(_) => {
debug!("file {} is inaccessible", path.display());
return Err(Error::Inaccessible);
}
};
let (page, message) = match data { let (page, message) = match data {
Ok(message) => (template.replace("((%s))", "Success!"), message), Ok(message) => (template.replace("((%s))", "Success!"), message),
Err(message) => (template.replace("((%s))", "Failure!"), message), Err(message) => (template.replace("((%s))", "Failure!"), message),