diff --git a/src/auth.rs b/src/auth.rs index 3f233aa..6cd73aa 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -1,42 +1,56 @@ use crate::errors::ServiceError; use crate::models::User; use actix_web::dev::{Payload, ServiceRequest}; -use actix_web_httpauth::extractors::bearer::{BearerAuth, Config}; -use actix_web_httpauth::extractors::AuthenticationError; +use actix_web::{FromRequest, HttpMessage, HttpRequest, HttpResponse}; +use actix_web_httpauth::extractors::bearer::BearerAuth; use chrono::Utc; use jsonwebtoken::{Algorithm, DecodingKey, EncodingKey, Header, Validation}; use serde::{Deserialize, Serialize}; -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)] pub enum Role { - None, - ReadAll, - WriteAll, - Admin, + None = 0, + ReadAll = 1, + WriteAll = 2, + Admin = 3, } #[derive(Debug, Clone, Serialize, Deserialize)] -struct Claims { - exp: usize, - uid: i32, - role: Role, +pub struct Claims { + pub exp: usize, + pub uid: i32, + pub role: Role, +} + +impl FromRequest for Claims { + type Error = actix_web::Error; + type Future = std::future::Ready>; + type Config = (); + + fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { + std::future::ready( + req.extensions() + .get::() + .map(|claims| claims.clone()) + .ok_or( + HttpResponse::InternalServerError() + .json("Could not get claims") + .into(), + ), + ) + } } pub async fn validator( req: ServiceRequest, credentials: BearerAuth, ) -> Result { - let config = req - .app_data::() - .map(|data| data.get_ref().clone()) - .unwrap_or(Default::default()); - match validate_token(credentials.token()) { Ok(claims) => { - //req.extensions_mut().insert(claims); + req.extensions_mut().insert(claims); Ok(req) } - Err(err) => Err(AuthenticationError::from(config).into()), + Err(err) => Err(err.into()), } } @@ -51,7 +65,7 @@ fn validate_token(token: &str) -> Result { .map_err(|_| ServiceError::JWTokenError)? .claims; - if decoded.exp > Utc::now().timestamp() as usize { + if decoded.exp < Utc::now().timestamp() as usize { Err(ServiceError::TokenExpiredError) } else { Ok(decoded) @@ -59,6 +73,10 @@ fn validate_token(token: &str) -> Result { } pub fn create_jwt(user: &User) -> Result { + create_jwt_role(user, Role::ReadAll) +} + +pub fn create_jwt_role(user: &User, role: Role) -> Result { let expiration = Utc::now() .checked_add_signed(chrono::Duration::weeks(10)) .expect("valid timestamp") @@ -67,7 +85,7 @@ pub fn create_jwt(user: &User) -> Result { let claims = Claims { exp: expiration as usize, uid: user.id, - role: Role::ReadAll, + role, }; let secret = std::env::var("JWT_SECRET").expect("JWT_SECRET env var"); diff --git a/src/handlers.rs b/src/handlers.rs index 0222c13..998d832 100644 --- a/src/handlers.rs +++ b/src/handlers.rs @@ -1,11 +1,13 @@ use super::actions; use super::Pool; -use crate::auth::create_jwt; +use crate::auth::{create_jwt, create_jwt_role, Claims, Role}; use crate::models::User; -use actix_web::{web, HttpResponse}; +use actix_web::error::ErrorUnauthorized; +use actix_web::web::Json; +use actix_web::{web, Error, HttpResponse}; use serde::{Deserialize, Serialize}; -type HttpResult = Result; +type HttpResult = Result; #[derive(Debug, Serialize, Deserialize)] pub struct InputUser { @@ -21,19 +23,31 @@ struct UserWithToken { } /// handler for `GET /users` -pub async fn get_users(db: web::Data) -> HttpResult { - Ok(web::block(move || actions::get_all_users(&db)) - .await - .map(|users| HttpResponse::Ok().json(users)) - .map_err(|_| HttpResponse::InternalServerError())?) +pub async fn get_users(db: web::Data, claims: Claims) -> HttpResult { + if claims.role >= Role::ReadAll { + Ok(web::block(move || actions::get_all_users(&db)) + .await + .map(|users| HttpResponse::Ok().json(users)) + .map_err(|_| HttpResponse::InternalServerError())?) + } else { + Err(ErrorUnauthorized("Cannot read other users")) + } } /// handler for `GET /users/{id}` -pub async fn get_user_by_id(db: web::Data, user_id: web::Path) -> HttpResult { - Ok(web::block(move || actions::get_user_by_id(&db, *user_id)) - .await - .map(|user| user.into()) - .map_err(|_| HttpResponse::InternalServerError())?) +pub async fn get_user_by_id( + db: web::Data, + user_id: web::Path, + claims: Claims, +) -> HttpResult { + if claims.uid == *user_id || claims.role >= Role::ReadAll { + Ok(web::block(move || actions::get_user_by_id(&db, *user_id)) + .await + .map(|user| user.into()) + .map_err(|_| HttpResponse::InternalServerError())?) + } else { + Err(ErrorUnauthorized("Cannot read other users")) + } } /// handler for `POST /users` @@ -52,9 +66,40 @@ pub async fn add_user(db: web::Data, item: web::Json) -> HttpRe } /// handler for `DELETE /users/{id}` -pub async fn delete_user(db: web::Data, user_id: web::Path) -> HttpResult { - Ok(web::block(move || actions::delete_user(&db, *user_id)) - .await - .map(|count| HttpResponse::Ok().body(format!("Deleted {} user.", count))) - .map_err(|_| HttpResponse::InternalServerError())?) +pub async fn delete_user( + db: web::Data, + user_id: web::Path, + claims: Claims, +) -> HttpResult { + if claims.uid == *user_id || claims.role >= Role::WriteAll { + Ok(web::block(move || actions::delete_user(&db, *user_id)) + .await + .map(|count| HttpResponse::Ok().body(format!("Deleted {} user.", count))) + .map_err(|_| HttpResponse::InternalServerError())?) + } else { + Err(ErrorUnauthorized("Cannot delete other user")) + } +} + +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] +pub struct LoginData { + name: String, + password: String, +} + +pub async fn admin_login(credentials: Json) -> HttpResult { + if *credentials.password == *"hugo" && *credentials.name == *"hugo" { + Ok(HttpResponse::Ok().body(create_jwt_role( + &User { + id: 0, + first_name: "".to_string(), + last_name: "".to_string(), + email: "".to_string(), + created_at: chrono::Local::now().naive_local(), + }, + Role::Admin, + )?)) + } else { + Err(ErrorUnauthorized("Incorrect credentials")) + } } diff --git a/src/main.rs b/src/main.rs index 3ad257d..c219a6d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,7 @@ extern crate diesel; use crate::auth::validator; -use actix_web::{web, App, Error, HttpServer}; +use actix_web::{web, App, HttpServer}; use actix_web_httpauth::middleware::HttpAuthentication; use diesel::prelude::*; use diesel::r2d2::{self, ConnectionManager}; @@ -32,6 +32,7 @@ async fn main() -> std::io::Result<()> { App::new() .data(pool.clone()) .route("/users", web::post().to(handlers::add_user)) + .route("/admin", web::post().to(handlers::admin_login)) .service( web::scope("/users") .wrap(auth_middleware)