use std::future::{ready, Ready}; use actix_web::{FromRequest, Error as ActixError, HttpRequest, dev::Payload, http}; use diesel::prelude::*; use serde::{Serialize, Deserialize}; use siren::ServiceError; use crate::storage::{schema::users, connection}; use super::{hash, Session, SESSION_COOKIE_NAME}; #[derive(Debug, Serialize, Deserialize)] pub struct RegisterUser { pub email: String, pub password: String, pub first_name: String, pub last_name: String, } impl RegisterUser { pub fn convert_to_insert(self) -> Result { Ok(InsertUser { email: self.email.to_lowercase(), hash: hash(&self.password)?, role: "user".to_string(), first_name: self.first_name, last_name: self.last_name, updated_at: chrono::Utc::now().naive_utc(), created_at: chrono::Utc::now().naive_utc(), profile_picture: None, verified: false, }) } } #[derive(Debug, Serialize, Deserialize)] pub struct LoginRequest { pub email: String, pub password: String, } #[derive(Debug, Queryable, QueryableByName, Serialize, Deserialize)] #[diesel(table_name = users)] pub struct QueryUser { pub email: String, pub hash: String, pub role: String, pub first_name: String, pub last_name: String, pub updated_at: chrono::NaiveDateTime, pub created_at: chrono::NaiveDateTime, pub profile_picture: Option, pub verified: bool, } impl QueryUser { pub fn get_by_email(email: &str) -> Result { let mut conn = connection()?; // Check if the user exists by email, case insensitive let user = users::table .filter(users::email.eq(email.to_lowercase())) .first(&mut conn)?; Ok(user) } } #[derive(Debug, Insertable, AsChangeset, Serialize, Deserialize)] #[diesel(table_name = users)] pub struct InsertUser { pub email: String, pub hash: String, pub role: String, pub first_name: String, pub last_name: String, pub updated_at: chrono::NaiveDateTime, pub created_at: chrono::NaiveDateTime, pub profile_picture: Option, pub verified: bool, } impl InsertUser { pub fn insert(user: Self) -> Result { let mut conn = connection()?; let user = diesel::insert_into(users::table) .values(user) .get_result(&mut conn)?; Ok(user) } pub fn update_profile( email: &str, profile_picture: Option<&str>, ) -> Result { let mut conn = connection()?; let user = diesel::update(users::table) .filter(users::email.eq(&email)) .set(users::profile_picture.eq(profile_picture)) .get_result(&mut conn)?; Ok(user) } } #[derive(Debug, Serialize, Deserialize)] pub struct ResponseUser { pub email: String, pub role: String, pub first_name: String, pub last_name: String, pub profile_picture: Option, } impl From for ResponseUser { fn from(user: QueryUser) -> Self { ResponseUser { email: user.email, role: user.role, first_name: user.first_name, last_name: user.last_name, profile_picture: user.profile_picture, } } } #[derive(Debug, Serialize, Deserialize)] pub struct Auth { pub id: String, pub user: ResponseUser, } impl FromRequest for Auth { type Error = ActixError; type Future = Ready>; fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { let session_id = match req .cookie(SESSION_COOKIE_NAME) .map(|c| c.value().to_string()) .or_else(|| { req .headers() .get(http::header::AUTHORIZATION) .map(|h| h.to_str().unwrap().split_at(7).1.to_string()) }) { Some(token) => token, None => { return ready(Err(ActixError::from(ServiceError { status: 401, message: "Unauthorized".to_string(), }))) } }; let ip_address = req.peer_addr().unwrap().ip().to_string(); match Session::verify(&session_id, &ip_address) { Ok(v) => { return ready(Ok(Auth { id: v.0.id, user: v.1.into(), })) } Err(err) => return ready(Err(ActixError::from(err))), } } } pub fn verify_role(auth: &Auth, role: &str) -> Result<(), ServiceError> { if auth.user.role == role { Ok(()) } else { Err(ServiceError { status: 403, message: "Forbidden".to_string(), }) } }