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<db::Connection>) -> Option<User> {
+async fn get_current_user(
+ req: &HttpRequest,
+ connection: web::Data<db::Connection>,
+) -> Option<User> {
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,
}
}
message: Option<String>,
}
-impl From<asynchronous::DBAsyncError> for ServiceError {
+impl From<asynchronous::DBAsyncError> for ServiceError {
fn from(error: asynchronous::DBAsyncError) -> Self {
ServiceError {
status_code: StatusCode::INTERNAL_SERVER_ERROR,
}
}
-impl From<email::Error> for ServiceError {
+impl From<email::Error> for ServiceError {
fn from(error: email::Error) -> Self {
ServiceError {
status_code: StatusCode::INTERNAL_SERVER_ERROR,
}
}
-impl From<actix_web::error::BlockingError> for ServiceError {
+impl From<actix_web::error::BlockingError> for ServiceError {
fn from(error: actix_web::error::BlockingError) -> Self {
ServiceError {
status_code: StatusCode::INTERNAL_SERVER_ERROR,
}
}
-impl From<ron::error::SpannedError> for ServiceError {
+impl From<ron::error::SpannedError> for ServiceError {
fn from(error: ron::error::SpannedError) -> Self {
ServiceError {
status_code: StatusCode::INTERNAL_SERVER_ERROR,
fn error_response(&self) -> HttpResponse {
MessageBaseTemplate {
message: &self.to_string(),
- }.to_response()
+ }
+ .to_response()
}
fn status_code(&self) -> StatusCode {
}
#[get("/")]
-pub async fn home_page(req: HttpRequest, connection: web::Data<db::Connection>) -> Result<HttpResponse> {
+pub async fn home_page(
+ req: HttpRequest,
+ connection: web::Data<db::Connection>,
+) -> Result<HttpResponse> {
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 /////
}
#[get("/recipe/view/{id}")]
-pub async fn view_recipe(req: HttpRequest, path: web::Path<(i64,)>, connection: web::Data<db::Connection>) -> Result<HttpResponse> {
- let (id,)= path.into_inner();
+pub async fn view_recipe(
+ req: HttpRequest,
+ path: web::Path<(i64,)>,
+ connection: web::Data<db::Connection>,
+) -> Result<HttpResponse> {
+ 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?;
current_recipe_id: Some(recipe.id),
recipes,
current_recipe: recipe,
- }.to_response())
+ }
+ .to_response())
}
///// EDIT RECIPE /////
}
#[get("/recipe/edit/{id}")]
-pub async fn edit_recipe(req: HttpRequest, path: web::Path<(i64,)>, connection: web::Data<db::Connection>) -> Result<HttpResponse> {
- let (id,)= path.into_inner();
+pub async fn edit_recipe(
+ req: HttpRequest,
+ path: web::Path<(i64,)>,
+ connection: web::Data<db::Connection>,
+) -> Result<HttpResponse> {
+ 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?;
current_recipe_id: Some(recipe.id),
recipes,
current_recipe: recipe,
- }.to_response())
+ }
+ .to_response())
}
///// MESSAGE /////
}
#[get("/signup")]
-pub async fn sign_up_get(req: HttpRequest, connection: web::Data<db::Connection>) -> impl Responder {
+pub async fn sign_up_get(
+ req: HttpRequest,
+ connection: web::Data<db::Connection>,
+) -> 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)]
}
#[post("/signup")]
-pub async fn sign_up_post(req: HttpRequest, form: web::Form<SignUpFormData>, connection: web::Data<db::Connection>, config: web::Data<Config>) -> Result<HttpResponse> {
- fn error_response(error: SignUpError, form: &web::Form<SignUpFormData>, user: Option<User>) -> Result<HttpResponse> {
+pub async fn sign_up_post(
+ req: HttpRequest,
+ form: web::Form<SignUpFormData>,
+ connection: web::Data<db::Connection>,
+ config: web::Data<Config>,
+) -> Result<HttpResponse> {
+ fn error_response(
+ error: SignUpError,
+ form: &web::Form<SignUpFormData>,
+ user: Option<User>,
+ ) -> Result<HttpResponse> {
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;
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<u16> = 'p: {
let split_port: Vec<&str> = host.split(':').collect();
if split_port.len() == 2 {
if let Ok(p) = split_port[1].parse::<u16>() {
- 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<db::Connection>) -> impl Responder {
+pub async fn sign_up_check_email(
+ req: HttpRequest,
+ connection: web::Data<db::Connection>,
+) -> impl Responder {
let user = get_current_user(&req, connection.clone()).await;
MessageTemplate {
user,
}
#[get("/validation")]
-pub async fn sign_up_validation(req: HttpRequest, query: web::Query<HashMap<String, String>>, connection: web::Data<db::Connection>) -> Result<HttpResponse> {
+pub async fn sign_up_validation(
+ req: HttpRequest,
+ query: web::Query<HashMap<String, String>>,
+ connection: web::Data<db::Connection>,
+) -> Result<HttpResponse> {
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()),
}
}
}
#[get("/signin")]
-pub async fn sign_in_get(req: HttpRequest, connection: web::Data<db::Connection>) -> impl Responder {
+pub async fn sign_in_get(
+ req: HttpRequest,
+ connection: web::Data<db::Connection>,
+) -> impl Responder {
let user = get_current_user(&req, connection.clone()).await;
SignInFormTemplate {
user,
}
#[post("/signin")]
-pub async fn sign_in_post(req: HttpRequest, form: web::Form<SignInFormData>, connection: web::Data<db::Connection>) -> Result<HttpResponse> {
- fn error_response(error: SignInError, form: &web::Form<SignInFormData>, user: Option<User>) -> Result<HttpResponse> {
+pub async fn sign_in_post(
+ req: HttpRequest,
+ form: web::Form<SignInFormData>,
+ connection: web::Data<db::Connection>,
+) -> Result<HttpResponse> {
+ fn error_response(
+ error: SignInError,
+ form: &web::Form<SignInFormData>,
+ user: Option<User>,
+ ) -> Result<HttpResponse> {
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<db::Connection>) -> 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);
};
};