X-Git-Url: http://git.euphorik.ch/?a=blobdiff_plain;f=backend%2Fsrc%2Fservices.rs;h=afa20a0b3b0cba6d120c0381eaaa49ac9d2f9670;hb=0a1631e66c861de2799cd98fc93686ff121c9fce;hp=b4d39f9e6ca7f303606c855435f7037148e1b3e9;hpb=cbe276fc0601041b13087a6ffd80c5b126dfbe59;p=recipes.git diff --git a/backend/src/services.rs b/backend/src/services.rs index b4d39f9..afa20a0 100644 --- a/backend/src/services.rs +++ b/backend/src/services.rs @@ -1,59 +1,78 @@ use std::collections::HashMap; -use actix_web::{http::{header, header::ContentType, StatusCode}, get, post, web, Responder, HttpRequest, HttpResponse, cookie::Cookie}; +use actix_web::{ + cookie::Cookie, + get, + http::{header, header::ContentType, StatusCode}, + post, web, HttpRequest, HttpResponse, Responder, +}; use askama_actix::{Template, TemplateToResponse}; use chrono::Duration; +use log::{debug, error, info, log_enabled, Level}; use serde::Deserialize; -use log::{debug, error, log_enabled, info, Level}; -use crate::utils; -use crate::email; -use crate::consts; use crate::config::Config; -use crate::user::User; +use crate::consts; +use crate::data::{asynchronous, db}; +use crate::email; use crate::model; -use crate::data::{db, asynchronous}; +use crate::user::User; +use crate::utils; mod api; ///// UTILS ///// fn get_ip_and_user_agent(req: &HttpRequest) -> (String, String) { - let ip = - match req.headers().get(consts::REVERSE_PROXY_IP_HTTP_FIELD) { - Some(v) => v.to_str().unwrap_or_default().to_string(), - None => req.peer_addr().map(|addr| addr.ip().to_string()).unwrap_or_default() - }; + let ip = match req.headers().get(consts::REVERSE_PROXY_IP_HTTP_FIELD) { + Some(v) => v.to_str().unwrap_or_default().to_string(), + None => req + .peer_addr() + .map(|addr| addr.ip().to_string()) + .unwrap_or_default(), + }; - let user_agent = req.headers().get(header::USER_AGENT).map(|v| v.to_str().unwrap_or_default()).unwrap_or_default().to_string(); + let user_agent = req + .headers() + .get(header::USER_AGENT) + .map(|v| v.to_str().unwrap_or_default()) + .unwrap_or_default() + .to_string(); (ip, user_agent) } -async fn get_current_user(req: &HttpRequest, connection: web::Data) -> Option { +async fn get_current_user( + req: &HttpRequest, + connection: web::Data, +) -> Option { let (client_ip, client_user_agent) = get_ip_and_user_agent(req); match req.cookie(consts::COOKIE_AUTH_TOKEN_NAME) { - Some(token_cookie) => - match connection.authentication_async(token_cookie.value(), &client_ip, &client_user_agent).await { - Ok(db::AuthenticationResult::NotValidToken) => - // TODO: remove cookie? - None, - Ok(db::AuthenticationResult::Ok(user_id)) => - match connection.load_user_async(user_id).await { - Ok(user) => - Some(user), - Err(error) => { - error!("Error during authentication: {}", error); - None - } - }, - Err(error) => { - error!("Error during authentication: {}", error); - None - }, - }, - None => None + Some(token_cookie) => match connection + .authentication_async(token_cookie.value(), &client_ip, &client_user_agent) + .await + { + Ok(db::AuthenticationResult::NotValidToken) => + // TODO: remove cookie? + { + None + } + Ok(db::AuthenticationResult::Ok(user_id)) => { + match connection.load_user_async(user_id).await { + Ok(user) => Some(user), + Err(error) => { + error!("Error during authentication: {}", error); + None + } + } + } + Err(error) => { + error!("Error during authentication: {}", error); + None + } + }, + None => None, } } @@ -67,7 +86,7 @@ pub struct ServiceError { message: Option, } -impl From for ServiceError { +impl From for ServiceError { fn from(error: asynchronous::DBAsyncError) -> Self { ServiceError { status_code: StatusCode::INTERNAL_SERVER_ERROR, @@ -76,7 +95,7 @@ impl From for ServiceError { } } -impl From for ServiceError { +impl From for ServiceError { fn from(error: email::Error) -> Self { ServiceError { status_code: StatusCode::INTERNAL_SERVER_ERROR, @@ -85,7 +104,7 @@ impl From for ServiceError { } } -impl From for ServiceError { +impl From for ServiceError { fn from(error: actix_web::error::BlockingError) -> Self { ServiceError { status_code: StatusCode::INTERNAL_SERVER_ERROR, @@ -94,7 +113,7 @@ impl From for ServiceError { } } -impl From for ServiceError { +impl From for ServiceError { fn from(error: ron::error::SpannedError) -> Self { ServiceError { status_code: StatusCode::INTERNAL_SERVER_ERROR, @@ -116,7 +135,8 @@ impl actix_web::error::ResponseError for ServiceError { fn error_response(&self) -> HttpResponse { MessageBaseTemplate { message: &self.to_string(), - }.to_response() + } + .to_response() } fn status_code(&self) -> StatusCode { @@ -135,11 +155,19 @@ struct HomeTemplate { } #[get("/")] -pub async fn home_page(req: HttpRequest, connection: web::Data) -> Result { +pub async fn home_page( + req: HttpRequest, + connection: web::Data, +) -> Result { let user = get_current_user(&req, connection.clone()).await; let recipes = connection.get_all_recipe_titles_async().await?; - Ok(HomeTemplate { user, current_recipe_id: None, recipes }.to_response()) + Ok(HomeTemplate { + user, + current_recipe_id: None, + recipes, + } + .to_response()) } ///// VIEW RECIPE ///// @@ -154,8 +182,12 @@ struct ViewRecipeTemplate { } #[get("/recipe/view/{id}")] -pub async fn view_recipe(req: HttpRequest, path: web::Path<(i64,)>, connection: web::Data) -> Result { - let (id,)= path.into_inner(); +pub async fn view_recipe( + req: HttpRequest, + path: web::Path<(i64,)>, + connection: web::Data, +) -> Result { + let (id,) = path.into_inner(); let user = get_current_user(&req, connection.clone()).await; let recipes = connection.get_all_recipe_titles_async().await?; let recipe = connection.get_recipe_async(id).await?; @@ -165,7 +197,8 @@ pub async fn view_recipe(req: HttpRequest, path: web::Path<(i64,)>, connection: current_recipe_id: Some(recipe.id), recipes, current_recipe: recipe, - }.to_response()) + } + .to_response()) } ///// EDIT RECIPE ///// @@ -180,8 +213,12 @@ struct EditRecipeTemplate { } #[get("/recipe/edit/{id}")] -pub async fn edit_recipe(req: HttpRequest, path: web::Path<(i64,)>, connection: web::Data) -> Result { - let (id,)= path.into_inner(); +pub async fn edit_recipe( + req: HttpRequest, + path: web::Path<(i64,)>, + connection: web::Data, +) -> Result { + let (id,) = path.into_inner(); let user = get_current_user(&req, connection.clone()).await; let recipes = connection.get_all_recipe_titles_async().await?; let recipe = connection.get_recipe_async(id).await?; @@ -191,7 +228,8 @@ pub async fn edit_recipe(req: HttpRequest, path: web::Path<(i64,)>, connection: current_recipe_id: Some(recipe.id), recipes, current_recipe: recipe, - }.to_response()) + } + .to_response()) } ///// MESSAGE ///// @@ -222,9 +260,18 @@ struct SignUpFormTemplate { } #[get("/signup")] -pub async fn sign_up_get(req: HttpRequest, connection: web::Data) -> impl Responder { +pub async fn sign_up_get( + req: HttpRequest, + connection: web::Data, +) -> impl Responder { let user = get_current_user(&req, connection.clone()).await; - SignUpFormTemplate { user, email: String::new(), message: String::new(), message_email: String::new(), message_password: String::new() } + SignUpFormTemplate { + user, + email: String::new(), + message: String::new(), + message_email: String::new(), + message_password: String::new(), + } } #[derive(Deserialize)] @@ -244,30 +291,40 @@ enum SignUpError { } #[post("/signup")] -pub async fn sign_up_post(req: HttpRequest, form: web::Form, connection: web::Data, config: web::Data) -> Result { - fn error_response(error: SignUpError, form: &web::Form, user: Option) -> Result { +pub async fn sign_up_post( + req: HttpRequest, + form: web::Form, + connection: web::Data, + config: web::Data, +) -> Result { + fn error_response( + error: SignUpError, + form: &web::Form, + user: Option, + ) -> Result { Ok(SignUpFormTemplate { user, email: form.email.clone(), - message_email: - match error { - SignUpError::InvalidEmail => "Invalid email", - _ => "", - }.to_string(), - message_password: - match error { - SignUpError::PasswordsNotEqual => "Passwords don't match", - SignUpError::InvalidPassword => "Password must have at least eight characters", - _ => "", - }.to_string(), - message: - match error { - SignUpError::UserAlreadyExists => "This email is already taken", - SignUpError::DatabaseError => "Database error", - SignUpError::UnableSendEmail => "Unable to send the validation email", - _ => "", - }.to_string(), - }.to_response()) + message_email: match error { + SignUpError::InvalidEmail => "Invalid email", + _ => "", + } + .to_string(), + message_password: match error { + SignUpError::PasswordsNotEqual => "Passwords don't match", + SignUpError::InvalidPassword => "Password must have at least eight characters", + _ => "", + } + .to_string(), + message: match error { + SignUpError::UserAlreadyExists => "This email is already taken", + SignUpError::DatabaseError => "Database error", + SignUpError::UnableSendEmail => "Unable to send the validation email", + _ => "", + } + .to_string(), + } + .to_response()) } let user = get_current_user(&req, connection.clone()).await; @@ -281,51 +338,80 @@ pub async fn sign_up_post(req: HttpRequest, form: web::Form, con return error_response(SignUpError::PasswordsNotEqual, &form, user); } - if let common::utils::PasswordValidation::TooShort = common::utils::validate_password(&form.password_1) { + if let common::utils::PasswordValidation::TooShort = + common::utils::validate_password(&form.password_1) + { return error_response(SignUpError::InvalidPassword, &form, user); } - match connection.sign_up_async(&form.email, &form.password_1).await { + match connection + .sign_up_async(&form.email, &form.password_1) + .await + { Ok(db::SignUpResult::UserAlreadyExists) => { error_response(SignUpError::UserAlreadyExists, &form, user) - }, + } Ok(db::SignUpResult::UserCreatedWaitingForValidation(token)) => { let url = { - let host = req.headers().get(header::HOST).map(|v| v.to_str().unwrap_or_default()).unwrap_or_default(); + let host = req + .headers() + .get(header::HOST) + .map(|v| v.to_str().unwrap_or_default()) + .unwrap_or_default(); let port: Option = 'p: { let split_port: Vec<&str> = host.split(':').collect(); if split_port.len() == 2 { if let Ok(p) = split_port[1].parse::() { - break 'p Some(p) + break 'p Some(p); } } None }; - format!("http{}://{}", if port.is_some() && port.unwrap() != 443 { "" } else { "s" }, host) + format!( + "http{}://{}", + if port.is_some() && port.unwrap() != 443 { + "" + } else { + "s" + }, + host + ) }; let email = form.email.clone(); - match web::block(move || { email::send_validation(&url, &email, &token, &config.smtp_login, &config.smtp_password) }).await? { - Ok(()) => - Ok(HttpResponse::Found() - .insert_header((header::LOCATION, "/signup_check_email")) - .finish()), + match web::block(move || { + email::send_validation( + &url, + &email, + &token, + &config.smtp_login, + &config.smtp_password, + ) + }) + .await? + { + Ok(()) => Ok(HttpResponse::Found() + .insert_header((header::LOCATION, "/signup_check_email")) + .finish()), Err(error) => { error!("Email validation error: {}", error); error_response(SignUpError::UnableSendEmail, &form, user) - }, + } } - }, + } Err(error) => { error!("Signup database error: {}", error); error_response(SignUpError::DatabaseError, &form, user) - }, + } } } #[get("/signup_check_email")] -pub async fn sign_up_check_email(req: HttpRequest, connection: web::Data) -> impl Responder { +pub async fn sign_up_check_email( + req: HttpRequest, + connection: web::Data, +) -> impl Responder { let user = get_current_user(&req, connection.clone()).await; MessageTemplate { user, @@ -334,55 +420,64 @@ pub async fn sign_up_check_email(req: HttpRequest, connection: web::Data>, connection: web::Data) -> Result { +pub async fn sign_up_validation( + req: HttpRequest, + query: web::Query>, + connection: web::Data, +) -> Result { let (client_ip, client_user_agent) = get_ip_and_user_agent(&req); let user = get_current_user(&req, connection.clone()).await; match query.get("token") { Some(token) => { - match connection.validation_async(token, Duration::seconds(consts::VALIDATION_TOKEN_DURATION), &client_ip, &client_user_agent).await? { + match connection + .validation_async( + token, + Duration::seconds(consts::VALIDATION_TOKEN_DURATION), + &client_ip, + &client_user_agent, + ) + .await? + { db::ValidationResult::Ok(token, user_id) => { let cookie = Cookie::new(consts::COOKIE_AUTH_TOKEN_NAME, token); - let user = - match connection.load_user(user_id) { - Ok(user) => - Some(user), - Err(error) => { - error!("Error retrieving user by id: {}", error); - None - } - }; - - let mut response = - MessageTemplate { - user, - message: "Email validation successful, your account has been created", - }.to_response(); + let user = match connection.load_user(user_id) { + Ok(user) => Some(user), + Err(error) => { + error!("Error retrieving user by id: {}", error); + None + } + }; + + let mut response = MessageTemplate { + user, + message: "Email validation successful, your account has been created", + } + .to_response(); if let Err(error) = response.add_cookie(&cookie) { error!("Unable to set cookie after validation: {}", error); }; Ok(response) - }, - db::ValidationResult::ValidationExpired => - Ok(MessageTemplate { - user, - message: "The validation has expired. Try to sign up again.", - }.to_response()), - db::ValidationResult::UnknownUser => - Ok(MessageTemplate { - user, - message: "Validation error.", - }.to_response()), + } + db::ValidationResult::ValidationExpired => Ok(MessageTemplate { + user, + message: "The validation has expired. Try to sign up again.", + } + .to_response()), + db::ValidationResult::UnknownUser => Ok(MessageTemplate { + user, + message: "Validation error.", + } + .to_response()), } - }, - None => { - Ok(MessageTemplate { - user, - message: &format!("No token provided"), - }.to_response()) - }, + } + None => Ok(MessageTemplate { + user, + message: &format!("No token provided"), + } + .to_response()), } } @@ -397,7 +492,10 @@ struct SignInFormTemplate { } #[get("/signin")] -pub async fn sign_in_get(req: HttpRequest, connection: web::Data) -> impl Responder { +pub async fn sign_in_get( + req: HttpRequest, + connection: web::Data, +) -> impl Responder { let user = get_current_user(&req, connection.clone()).await; SignInFormTemplate { user, @@ -418,62 +516,74 @@ enum SignInError { } #[post("/signin")] -pub async fn sign_in_post(req: HttpRequest, form: web::Form, connection: web::Data) -> Result { - fn error_response(error: SignInError, form: &web::Form, user: Option) -> Result { +pub async fn sign_in_post( + req: HttpRequest, + form: web::Form, + connection: web::Data, +) -> Result { + fn error_response( + error: SignInError, + form: &web::Form, + user: Option, + ) -> Result { Ok(SignInFormTemplate { user, email: form.email.clone(), - message: - match error { - SignInError::AccountNotValidated => "This account must be validated first", - SignInError::AuthenticationFailed => "Wrong email or password", - }.to_string(), - }.to_response()) + message: match error { + SignInError::AccountNotValidated => "This account must be validated first", + SignInError::AuthenticationFailed => "Wrong email or password", + } + .to_string(), + } + .to_response()) } let user = get_current_user(&req, connection.clone()).await; let (client_ip, client_user_agent) = get_ip_and_user_agent(&req); - match connection.sign_in_async(&form.email, &form.password, &client_ip, &client_user_agent).await { - Ok(db::SignInResult::AccountNotValidated) => - error_response(SignInError::AccountNotValidated, &form, user), + match connection + .sign_in_async(&form.email, &form.password, &client_ip, &client_user_agent) + .await + { + Ok(db::SignInResult::AccountNotValidated) => { + error_response(SignInError::AccountNotValidated, &form, user) + } Ok(db::SignInResult::UserNotFound) | Ok(db::SignInResult::WrongPassword) => { error_response(SignInError::AuthenticationFailed, &form, user) - }, + } Ok(db::SignInResult::Ok(token, user_id)) => { let cookie = Cookie::new(consts::COOKIE_AUTH_TOKEN_NAME, token); - let mut response = - HttpResponse::Found() - .insert_header((header::LOCATION, "/")) - .finish(); + let mut response = HttpResponse::Found() + .insert_header((header::LOCATION, "/")) + .finish(); if let Err(error) = response.add_cookie(&cookie) { error!("Unable to set cookie after sign in: {}", error); }; Ok(response) - }, + } Err(error) => { error!("Signin error: {}", error); error_response(SignInError::AuthenticationFailed, &form, user) - }, + } } } - ///// SIGN OUT ///// #[get("/signout")] pub async fn sign_out(req: HttpRequest, connection: web::Data) -> impl Responder { - let mut response = - HttpResponse::Found() - .insert_header((header::LOCATION, "/")) - .finish(); + let mut response = HttpResponse::Found() + .insert_header((header::LOCATION, "/")) + .finish(); if let Some(token_cookie) = req.cookie(consts::COOKIE_AUTH_TOKEN_NAME) { if let Err(error) = connection.sign_out_async(token_cookie.value()).await { error!("Unable to sign out: {}", error); }; - if let Err(error) = response.add_removal_cookie(&Cookie::new(consts::COOKIE_AUTH_TOKEN_NAME, "")) { + if let Err(error) = + response.add_removal_cookie(&Cookie::new(consts::COOKIE_AUTH_TOKEN_NAME, "")) + { error!("Unable to set a removal cookie after sign out: {}", error); }; };