use std::{future::{ready, Ready}, env}; use actix_web::{FromRequest, Error as ActixError, HttpRequest, dev::Payload, http}; use diesel::prelude::*; use log::error; use redis::Commands; use serde::{Serialize, Deserialize}; use siren::ServiceError; use crate::storage::{schema::users, connection}; use super::{hash_password, verify_token}; #[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 { let hash = hash_password(self.password.as_bytes())?; Ok(InsertUser { email: self.email.to_lowercase(), hash, 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(), 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 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 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) } } #[derive(Debug, Serialize, Deserialize)] pub struct ResponseUser { pub email: String, pub role: String, pub first_name: String, pub last_name: String, } 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, } } } #[derive(Debug, Serialize, Deserialize)] pub struct JwtAuth { pub token: uuid::Uuid, pub user: ResponseUser } impl FromRequest for JwtAuth { type Error = ActixError; type Future = Ready>; fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { let access_token = match req .cookie("access_token") .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 public_key = env::var("ACCESS_TOKEN_PUBLIC_KEY") .expect("ACCESS_TOKEN_PUBLIC_KEY must be set"); let access_token_details = match verify_token(&access_token, &public_key) { Ok(token_details) => token_details, Err(err) => { error!("Failed to verify access token: {}", err); return ready(Err(ActixError::from(ServiceError { status: 401, message: format!("Failed to verify access token: {}", err) }))) } }; let access_token_uuid = uuid::Uuid::parse_str(&access_token_details.token_uuid.to_string()).unwrap(); let mut conn = match crate::storage::redis_connection() { Ok(conn) => conn, Err(err) => { error!("Failed to get redis connection: {}", err); return ready(Err(ActixError::from(ServiceError { status: 500, message: format!("Failed to get redis connection: {}", err) }))) } }; let user_email = match conn.get::<_, String>(access_token_uuid.clone().to_string()) { Ok(result) => result, Err(_) => { return ready(Err(ActixError::from(ServiceError { status: 401, message: format!("Access token was not found") }))) } }; match QueryUser::get_by_email(&user_email) { Ok(user) => { ready(Ok(JwtAuth { token: access_token_uuid, user: user.into() })) } Err(_) => return ready(Err(ActixError::from(ServiceError { status: 401, message: format!("User was not found") }))) } } } pub fn verify_role(auth: &JwtAuth, role: &str) -> Result<(), ServiceError> { if auth.user.role == role { Ok(()) } else { Err(ServiceError { status: 403, message: "Forbidden".to_string() }) } }