Better logging and error handling

This commit is contained in:
Dietrich 2021-04-10 12:30:05 +02:00
parent 76b1f53120
commit 5950fa7370
Signed by: dietrich
GPG Key ID: 9F3C20C0F85DF67C
6 changed files with 396 additions and 316 deletions

539
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -25,7 +25,7 @@ dotenv = "0.15.0"
actix-identity = "0.3" actix-identity = "0.3"
chrono = { version = "0.4", features = ["serde"] } chrono = { version = "0.4", features = ["serde"] }
argonautica = "0.2" argonautica = "0.2"
slog = "2" slog = { version = "2", features = ["max_level_trace", "release_max_level_info"] }
slog-term = "2" slog-term = "2"
slog-async = "2" slog-async = "2"
qrcode = "0.12" qrcode = "0.12"

View File

@ -223,7 +223,7 @@ pub(crate) async fn setup() -> Result<Option<crate::ServerConfig>, ServerError>
// Print launch info // Print launch info
slog_info!(log, "Launching Pslink a 'Private short link generator'"); slog_info!(log, "Launching Pslink a 'Private short link generator'");
slog_info!(log, "logging initialized"); slog_trace!(log, "logging initialized");
let app = generate_cli(); let app = generate_cli();
@ -239,6 +239,7 @@ pub(crate) async fn setup() -> Result<Option<crate::ServerConfig>, ServerError>
.parse::<PathBuf>() .parse::<PathBuf>()
.expect("Failed to parse Database path."); .expect("Failed to parse Database path.");
if !db.exists() { if !db.exists() {
slog_trace!(log, "No database file found {}", db.display());
if config.subcommand_matches("migrate-database").is_none() { if config.subcommand_matches("migrate-database").is_none() {
let msg = format!( let msg = format!(
concat!( concat!(
@ -252,8 +253,9 @@ pub(crate) async fn setup() -> Result<Option<crate::ServerConfig>, ServerError>
eprintln!("{}", msg); eprintln!("{}", msg);
return Ok(None); return Ok(None);
} }
slog_trace!(log, "Creating database: {}", db.display());
// create an empty database file the if above makes sure that this file does not exist. // create an empty database file. The if above makes sure that this file does not exist.
File::create(db)?; File::create(db)?;
}; };
@ -290,8 +292,10 @@ pub(crate) async fn setup() -> Result<Option<crate::ServerConfig>, ServerError>
" Create a user with `pslink create-admin`" " Create a user with `pslink create-admin`"
) )
); );
} else {
slog_trace!(&server_config.log, "At least one admin user is found.");
} }
slog_info!( slog_trace!(
&server_config.log, &server_config.log,
"Initialization finished starting the service." "Initialization finished starting the service."
); );

View File

@ -2,6 +2,7 @@ extern crate sqlx;
#[allow(unused_imports)] #[allow(unused_imports)]
#[macro_use( #[macro_use(
slog_o, slog_o,
slog_trace,
slog_info, slog_info,
slog_warn, slog_warn,
slog_error, slog_error,
@ -82,25 +83,26 @@ fn build_tera() -> Result<Tera> {
Ok(tera) Ok(tera)
} }
#[allow(clippy::future_not_send)] #[allow(clippy::future_not_send, clippy::too_many_lines)]
async fn webservice(server_config: ServerConfig) -> Result<()> { async fn webservice(server_config: ServerConfig) -> Result<()> {
let host_port = format!("{}:{}", &server_config.internal_ip, &server_config.port); let host_port = format!("{}:{}", &server_config.internal_ip, &server_config.port);
let cfg = server_config.clone();
slog_info!( slog_info!(
server_config.log, cfg.log,
"Running on: {}://{}/admin/login/", "Running on: {}://{}/admin/login/",
&server_config.protocol, &server_config.protocol,
host_port host_port
); );
slog_info!( slog_info!(
server_config.log, cfg.log,
"If the public url is set up correctly it should be accessible via: {}://{}/admin/login/", "If the public url is set up correctly it should be accessible via: {}://{}/admin/login/",
&server_config.protocol, &server_config.protocol,
&server_config.public_url &server_config.public_url
); );
let tera = build_tera()?;
slog_trace!(cfg.log, "The tera templates are ready");
HttpServer::new(move || { HttpServer::new(move || {
let tera = build_tera(); //Tera::new("templates/**/*").expect("failed to initialize the templates");
let generated = generate(); let generated = generate();
App::new() App::new()
.data(server_config.clone()) .data(server_config.clone())
@ -112,7 +114,7 @@ async fn webservice(server_config: ServerConfig) -> Result<()> {
.name("auth-cookie") .name("auth-cookie")
.secure(false), .secure(false),
)) ))
.data(tera) .data(tera.clone())
.service(actix_web_static_files::ResourceFiles::new( .service(actix_web_static_files::ResourceFiles::new(
"/static", generated, "/static", generated,
)) ))
@ -187,7 +189,11 @@ async fn webservice(server_config: ServerConfig) -> Result<()> {
.route("/{redirect_id}", web::get().to(views::redirect)) .route("/{redirect_id}", web::get().to(views::redirect))
}) })
.bind(host_port) .bind(host_port)
.context("Failed to bind to port")? .context("Failed to bind to port")
.map_err(|e| {
slog_error!(cfg.log, "Failed to bind to port!");
e
})?
.run() .run()
.await .await
.context("Failed to run the webservice") .context("Failed to run the webservice")

View File

@ -13,6 +13,7 @@ use fluent_langneg::{
use fluent_templates::LanguageIdentifier; use fluent_templates::LanguageIdentifier;
use image::{DynamicImage, ImageOutputFormat, Luma}; use image::{DynamicImage, ImageOutputFormat, Luma};
use qrcode::{render::svg, QrCode}; use qrcode::{render::svg, QrCode};
use queries::{authenticate, Role};
use tera::{Context, Tera}; use tera::{Context, Tera};
use pslink::forms::LinkForm; use pslink::forms::LinkForm;
@ -57,14 +58,13 @@ fn detect_language(request: &HttpRequest) -> Result<String, ServerError> {
let languagecode = supported let languagecode = supported
.get(0) .get(0)
.map_or("en".to_string(), std::string::ToString::to_string); .map_or("en".to_string(), std::string::ToString::to_string);
println!("Detected the language: {}", &languagecode);
Ok(languagecode) Ok(languagecode)
} }
/// Show the list of all available links if a user is authenticated /// Show the list of all available links if a user is authenticated
pub async fn index( pub async fn index(
tera: web::Data<Tera>, tera: web::Data<Tera>,
config: web::Data<crate::ServerConfig>, config: web::Data<pslink::ServerConfig>,
id: Identity, id: Identity,
) -> Result<HttpResponse, ServerError> { ) -> Result<HttpResponse, ServerError> {
if let Ok(links) = queries::list_all_allowed(&id, &config).await { if let Ok(links) = queries::list_all_allowed(&id, &config).await {
@ -82,7 +82,7 @@ pub async fn index(
/// Show the list of all available links if a user is authenticated /// Show the list of all available links if a user is authenticated
pub async fn index_users( pub async fn index_users(
tera: web::Data<Tera>, tera: web::Data<Tera>,
config: web::Data<crate::ServerConfig>, config: web::Data<pslink::ServerConfig>,
id: Identity, id: Identity,
) -> Result<HttpResponse, ServerError> { ) -> Result<HttpResponse, ServerError> {
if let Ok(users) = queries::list_users(&id, &config).await { if let Ok(users) = queries::list_users(&id, &config).await {
@ -99,7 +99,7 @@ pub async fn index_users(
} }
pub async fn view_link_empty( pub async fn view_link_empty(
tera: web::Data<Tera>, tera: web::Data<Tera>,
config: web::Data<crate::ServerConfig>, config: web::Data<pslink::ServerConfig>,
id: Identity, id: Identity,
) -> Result<HttpResponse, ServerError> { ) -> Result<HttpResponse, ServerError> {
view_link(tera, config, id, web::Path::from("".to_owned())).await view_link(tera, config, id, web::Path::from("".to_owned())).await
@ -107,7 +107,7 @@ pub async fn view_link_empty(
pub async fn view_link( pub async fn view_link(
tera: web::Data<Tera>, tera: web::Data<Tera>,
config: web::Data<crate::ServerConfig>, config: web::Data<pslink::ServerConfig>,
id: Identity, id: Identity,
link_id: web::Path<String>, link_id: web::Path<String>,
) -> Result<HttpResponse, ServerError> { ) -> Result<HttpResponse, ServerError> {
@ -146,7 +146,7 @@ pub async fn view_link(
pub async fn view_profile( pub async fn view_profile(
tera: web::Data<Tera>, tera: web::Data<Tera>,
config: web::Data<crate::ServerConfig>, config: web::Data<pslink::ServerConfig>,
id: Identity, id: Identity,
user_id: web::Path<String>, user_id: web::Path<String>,
) -> Result<HttpResponse, ServerError> { ) -> Result<HttpResponse, ServerError> {
@ -173,7 +173,7 @@ pub async fn view_profile(
pub async fn edit_profile( pub async fn edit_profile(
tera: web::Data<Tera>, tera: web::Data<Tera>,
config: web::Data<crate::ServerConfig>, config: web::Data<pslink::ServerConfig>,
id: Identity, id: Identity,
user_id: web::Path<String>, user_id: web::Path<String>,
) -> Result<HttpResponse, ServerError> { ) -> Result<HttpResponse, ServerError> {
@ -199,7 +199,7 @@ pub async fn edit_profile(
pub async fn process_edit_profile( pub async fn process_edit_profile(
data: web::Form<NewUser>, data: web::Form<NewUser>,
config: web::Data<crate::ServerConfig>, config: web::Data<pslink::ServerConfig>,
id: Identity, id: Identity,
user_id: web::Path<String>, user_id: web::Path<String>,
) -> Result<HttpResponse, ServerError> { ) -> Result<HttpResponse, ServerError> {
@ -215,7 +215,7 @@ pub async fn process_edit_profile(
pub async fn download_png( pub async fn download_png(
id: Identity, id: Identity,
config: web::Data<crate::ServerConfig>, config: web::Data<pslink::ServerConfig>,
link_code: web::Path<String>, link_code: web::Path<String>,
) -> Result<HttpResponse, ServerError> { ) -> Result<HttpResponse, ServerError> {
match queries::get_link(&id, &link_code.0, &config).await { match queries::get_link(&id, &link_code.0, &config).await {
@ -239,7 +239,7 @@ pub async fn download_png(
pub async fn signup( pub async fn signup(
tera: web::Data<Tera>, tera: web::Data<Tera>,
config: web::Data<crate::ServerConfig>, config: web::Data<pslink::ServerConfig>,
id: Identity, id: Identity,
) -> Result<HttpResponse, ServerError> { ) -> Result<HttpResponse, ServerError> {
match queries::authenticate(&id, &config).await? { match queries::authenticate(&id, &config).await? {
@ -259,7 +259,7 @@ pub async fn signup(
pub async fn process_signup( pub async fn process_signup(
data: web::Form<NewUser>, data: web::Form<NewUser>,
config: web::Data<crate::ServerConfig>, config: web::Data<pslink::ServerConfig>,
id: Identity, id: Identity,
) -> Result<HttpResponse, ServerError> { ) -> Result<HttpResponse, ServerError> {
slog_info!(config.log, "Creating a User: {:?}", &data); slog_info!(config.log, "Creating a User: {:?}", &data);
@ -272,7 +272,7 @@ pub async fn process_signup(
pub async fn toggle_admin( pub async fn toggle_admin(
data: web::Path<String>, data: web::Path<String>,
config: web::Data<crate::ServerConfig>, config: web::Data<pslink::ServerConfig>,
id: Identity, id: Identity,
) -> Result<HttpResponse, ServerError> { ) -> Result<HttpResponse, ServerError> {
let update = queries::toggle_admin(&id, &data.0, &config).await?; let update = queries::toggle_admin(&id, &data.0, &config).await?;
@ -284,7 +284,7 @@ pub async fn toggle_admin(
pub async fn set_language( pub async fn set_language(
data: web::Path<String>, data: web::Path<String>,
config: web::Data<crate::ServerConfig>, config: web::Data<pslink::ServerConfig>,
id: Identity, id: Identity,
) -> Result<HttpResponse, ServerError> { ) -> Result<HttpResponse, ServerError> {
queries::set_language(&id, &data.0, &config).await?; queries::set_language(&id, &data.0, &config).await?;
@ -294,7 +294,7 @@ pub async fn set_language(
pub async fn login( pub async fn login(
tera: web::Data<Tera>, tera: web::Data<Tera>,
id: Identity, id: Identity,
config: web::Data<crate::ServerConfig>, config: web::Data<pslink::ServerConfig>,
req: HttpRequest, req: HttpRequest,
) -> Result<HttpResponse, ServerError> { ) -> Result<HttpResponse, ServerError> {
let language_code = detect_language(&req)?; let language_code = detect_language(&req)?;
@ -303,8 +303,22 @@ pub async fn login(
data.insert("title", "Login"); data.insert("title", "Login");
data.insert("language", &language_code); data.insert("language", &language_code);
if let Some(_id) = id.identity() { if id.identity().is_some() {
return Ok(redirect_builder("/admin/index/")); if let Ok(r) = authenticate(&id, &config).await {
match r {
Role::Admin { user } | Role::Regular { user } => {
slog_trace!(
config.log,
"This user ({}) is already logged in redirecting to /admin/index/",
user.username
);
return Ok(redirect_builder("/admin/index/"));
}
Role::Disabled | Role::NotAuthenticated => (),
}
}
slog_warn!(config.log, "Invalid user session. The user might be deleted or something tampered with the cookies.");
id.forget();
} }
let rendered = tera.render("login.html", &data)?; let rendered = tera.render("login.html", &data)?;
@ -313,7 +327,7 @@ pub async fn login(
pub async fn process_login( pub async fn process_login(
data: web::Form<LoginUser>, data: web::Form<LoginUser>,
config: web::Data<crate::ServerConfig>, config: web::Data<pslink::ServerConfig>,
id: Identity, id: Identity,
) -> Result<HttpResponse, ServerError> { ) -> Result<HttpResponse, ServerError> {
let user = queries::get_user_by_name(&data.username, &config).await; let user = queries::get_user_by_name(&data.username, &config).await;
@ -350,7 +364,7 @@ pub async fn logout(id: Identity) -> Result<HttpResponse, ServerError> {
pub async fn redirect( pub async fn redirect(
tera: web::Data<Tera>, tera: web::Data<Tera>,
config: web::Data<crate::ServerConfig>, config: web::Data<pslink::ServerConfig>,
data: web::Path<String>, data: web::Path<String>,
req: HttpRequest, req: HttpRequest,
) -> Result<HttpResponse, ServerError> { ) -> Result<HttpResponse, ServerError> {
@ -382,14 +396,14 @@ pub async fn redirect(
} }
pub async fn redirect_empty( pub async fn redirect_empty(
config: web::Data<crate::ServerConfig>, config: web::Data<pslink::ServerConfig>,
) -> Result<HttpResponse, ServerError> { ) -> Result<HttpResponse, ServerError> {
Ok(redirect_builder(&config.empty_forward_url)) Ok(redirect_builder(&config.empty_forward_url))
} }
pub async fn create_link( pub async fn create_link(
tera: web::Data<Tera>, tera: web::Data<Tera>,
config: web::Data<crate::ServerConfig>, config: web::Data<pslink::ServerConfig>,
id: Identity, id: Identity,
) -> Result<HttpResponse, ServerError> { ) -> Result<HttpResponse, ServerError> {
match queries::authenticate(&id, &config).await? { match queries::authenticate(&id, &config).await? {
@ -409,7 +423,7 @@ pub async fn create_link(
pub async fn process_link_creation( pub async fn process_link_creation(
data: web::Form<LinkForm>, data: web::Form<LinkForm>,
config: web::Data<crate::ServerConfig>, config: web::Data<pslink::ServerConfig>,
id: Identity, id: Identity,
) -> Result<HttpResponse, ServerError> { ) -> Result<HttpResponse, ServerError> {
let new_link = queries::create_link(&id, data, &config).await?; let new_link = queries::create_link(&id, data, &config).await?;
@ -421,7 +435,7 @@ pub async fn process_link_creation(
pub async fn edit_link( pub async fn edit_link(
tera: web::Data<Tera>, tera: web::Data<Tera>,
config: web::Data<crate::ServerConfig>, config: web::Data<pslink::ServerConfig>,
id: Identity, id: Identity,
link_id: web::Path<String>, link_id: web::Path<String>,
) -> Result<HttpResponse, ServerError> { ) -> Result<HttpResponse, ServerError> {
@ -438,7 +452,7 @@ pub async fn edit_link(
} }
pub async fn process_link_edit( pub async fn process_link_edit(
data: web::Form<LinkForm>, data: web::Form<LinkForm>,
config: web::Data<crate::ServerConfig>, config: web::Data<pslink::ServerConfig>,
id: Identity, id: Identity,
link_code: web::Path<String>, link_code: web::Path<String>,
) -> Result<HttpResponse, ServerError> { ) -> Result<HttpResponse, ServerError> {
@ -453,7 +467,7 @@ pub async fn process_link_edit(
pub async fn process_link_delete( pub async fn process_link_delete(
id: Identity, id: Identity,
config: web::Data<crate::ServerConfig>, config: web::Data<pslink::ServerConfig>,
link_code: web::Path<String>, link_code: web::Path<String>,
) -> Result<HttpResponse, ServerError> { ) -> Result<HttpResponse, ServerError> {
queries::delete_link(&id, &link_code.0, &config).await?; queries::delete_link(&id, &link_code.0, &config).await?;

View File

@ -51,25 +51,86 @@ impl From<argonautica::Error> for ServerError {
} }
} }
impl ServerError {
fn render_error(title: &str, content: &str) -> String {
format!(
"<!DOCTYPE html>
<html lang=\"en\">
<head>
<meta charset=\"utf-8\">
<title>{0}</title>
<meta name=\"author\" content=\"Franz Dietrich\">
<meta http-equiv=\"robots\" content=\"[noindex|nofollow]\">
<link rel=\"stylesheet\" href=\"/static/style.css\">
</head>
<body>
<section class=\"centered\">
<h1>{0}</h1>
<div class=\"container\">
{1}
</div>
</section>
</body>
</html>",
title, content
)
}
}
impl actix_web::error::ResponseError for ServerError { impl actix_web::error::ResponseError for ServerError {
fn error_response(&self) -> HttpResponse { fn error_response(&self) -> HttpResponse {
match self { match self {
Self::Argonautica(_) => HttpResponse::InternalServerError().json("Argonautica Error"), Self::Argonautica(e) => {
Self::Database(e) => { eprintln!("Argonautica Error happened: {:?}", e);
HttpResponse::InternalServerError().json(format!("Database Error: {:?}", e)) HttpResponse::InternalServerError()
.body("Failed to encrypt the password - Aborting!")
} }
Self::DatabaseMigration(_) => { Self::Database(e) => {
eprintln!("Database Error happened: {:?}", e);
HttpResponse::InternalServerError().body(&Self::render_error(
"Server Error",
"Database could not be accessed! - It could be that this value already was in the database! If you are the admin look into the logs for a more detailed error.",
))
}
Self::DatabaseMigration(e) => {
eprintln!("Migration Error happened: {:?}", e);
unimplemented!("A migration error should never be rendered") unimplemented!("A migration error should never be rendered")
} }
Self::Environment(_) => HttpResponse::InternalServerError().json("Environment Error"), Self::Environment(e) => {
eprintln!("Environment Error happened: {:?}", e);
HttpResponse::InternalServerError().body(&Self::render_error(
"Server Error",
"This Server is not properly configured, if you are the admin look into the installation- or update instructions!",
))
}
Self::Template(e) => { Self::Template(e) => {
HttpResponse::InternalServerError().json(format!("Template Error: {:?}", e)) eprintln!("Template Error happened: {:?}", e);
HttpResponse::InternalServerError().body(&Self::render_error(
"Server Error",
"The templates could not be rendered.",
))
} }
Self::Qr(e) => { Self::Qr(e) => {
HttpResponse::InternalServerError().json(format!("Qr Code Error: {:?}", e)) eprintln!("QR Error happened: {:?}", e);
HttpResponse::InternalServerError().body(&Self::render_error(
"Server Error",
"Could not generate the QR-code!",
))
}
Self::Io(e) => {
eprintln!("Io Error happened: {:?}", e);
HttpResponse::InternalServerError().body(&Self::render_error(
"Server Error",
"Some Files could not be read or written. If you are the admin look into the logfiles for more details.",
))
}
Self::User(data) => {
eprintln!("User Error happened: {:?}", data);
HttpResponse::InternalServerError().body(&Self::render_error(
"Server Error",
&format!("An error happened: {}", data),
))
} }
Self::Io(e) => HttpResponse::InternalServerError().json(format!("IO Error: {:?}", e)),
Self::User(data) => HttpResponse::InternalServerError().json(data),
} }
} }
} }