use actix_identity::Identity; use actix_web::{ dev::Payload, error::BlockingError, web, Error, FromRequest, HttpRequest, HttpResponse, }; use diesel::prelude::*; use diesel::PgConnection; use futures::Future; use crate::errors::ServiceError; use crate::models::{Pool, SlimUser, User}; use crate::utils::verify; #[derive(Debug, Deserialize)] pub struct AuthData { pub email: String, pub password: String, } // we need the same data // simple aliasing makes the intentions clear and its more readable pub type LoggedUser = SlimUser; impl FromRequest for LoggedUser { type Config = (); type Error = Error; type Future = Result; fn from_request(req: &HttpRequest, pl: &mut Payload) -> Self::Future { if let Some(identity) = Identity::from_request(req, pl)?.identity() { let user: LoggedUser = serde_json::from_str(&identity)?; return Ok(user); } Err(ServiceError::Unauthorized.into()) } } pub fn logout(id: Identity) -> HttpResponse { id.forget(); HttpResponse::Ok().finish() } pub fn login( auth_data: web::Json, id: Identity, pool: web::Data, ) -> impl Future { web::block(move || query(auth_data.into_inner(), pool)).then( move |res: Result>| match res { Ok(user) => { let user_string = serde_json::to_string(&user).unwrap(); id.remember(user_string); Ok(HttpResponse::Ok().finish()) } Err(err) => match err { BlockingError::Error(service_error) => Err(service_error), BlockingError::Canceled => Err(ServiceError::InternalServerError), }, }, ) } pub fn get_me(logged_user: LoggedUser) -> HttpResponse { HttpResponse::Ok().json(logged_user) } /// Diesel query fn query(auth_data: AuthData, pool: web::Data) -> Result { use crate::schema::users::dsl::{email, users}; let conn: &PgConnection = &pool.get().unwrap(); let mut items = users .filter(email.eq(&auth_data.email)) .load::(conn)?; if let Some(user) = items.pop() { if let Ok(matching) = verify(&user.hash, &auth_data.password) { if matching { return Ok(user.into()); } } } Err(ServiceError::Unauthorized) }