diff --git a/src/error.rs b/src/error.rs index e739e57..b46c2ed 100644 --- a/src/error.rs +++ b/src/error.rs @@ -3,13 +3,14 @@ use axum::response::{IntoResponse, Response}; pub type Result = core::result::Result; -#[derive(Debug)] +#[derive(Clone, Debug)] pub enum Error { LoginFail, // -- Auth errors. AuthFailNoAuthTokenCookie, AuthFailTokenWrongFormat, + AuthFailCtxNotInRequestExt, // -- Model errors. PropertyDeleteFailIdNotFound { id: u64 }, diff --git a/src/main.rs b/src/main.rs index faba86f..616010f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -37,6 +37,7 @@ async fn main() -> Result<()>{ .merge(web::routes_login::routes()) .nest("/api", routes_apis) .layer(middleware::map_response(main_response_mapper)) + .layer(middleware::from_fn_with_state(mc.clone(), web::mw_auth::mw_ctx_resolver)) .layer(CookieManagerLayer::new()) // must be above? the auth routes // TODO: continue video at 22:15 .fallback_service(routes_static()); diff --git a/src/model.rs b/src/model.rs index ce7bbdd..c46fae9 100644 --- a/src/model.rs +++ b/src/model.rs @@ -1,7 +1,7 @@ //! Simplistic model layer //! (with mock-store layer) -use crate::{Error, Result}; +use crate::{Error, Result, ctx::Ctx}; use serde::{Deserialize, Serialize}; use std::sync::{Arc, Mutex}; @@ -9,6 +9,7 @@ use std::sync::{Arc, Mutex}; #[derive(Clone, Debug, Serialize)] pub struct Property { pub id: u64, + pub creator_id: u64, pub address: String, pub contact: String, } @@ -37,21 +38,21 @@ impl ModelController { // CRUD implementation impl ModelController { - pub async fn create_property(&self, property: PropertyForCreate) -> Result { + pub async fn create_property(&self, ctx: Ctx, property: PropertyForCreate) -> Result { let mut store = self.property_store.lock().unwrap(); let id = store.len() as u64; - let property = Property { id, address: property.address, contact: property.contact }; + let property = Property { id, creator_id: ctx.user_id(), address: property.address, contact: property.contact }; store.push(Some(property.clone())); Ok(property) } - pub async fn list_properties(&self) -> Result> { + pub async fn list_properties(&self, ctx: Ctx) -> Result> { let store = self.property_store.lock().unwrap(); let properties = store.iter().filter_map(|p| p.clone()).collect(); Ok(properties) } - pub async fn delete_property(&self, id: u64) -> Result { + pub async fn delete_property(&self, ctx: Ctx, id: u64) -> Result { let mut store = self.property_store.lock().unwrap(); let property = store.get_mut(id as usize).and_then(|p| p.take()); property.ok_or(Error::PropertyDeleteFailIdNotFound { id }) diff --git a/src/web/mw_auth.rs b/src/web/mw_auth.rs index 2210bf9..ecc9a68 100644 --- a/src/web/mw_auth.rs +++ b/src/web/mw_auth.rs @@ -6,33 +6,58 @@ use axum::http::request::Parts; use axum::middleware::Next; use axum::response::Response; use lazy_regex::regex_captures; -use tower_cookies::Cookies; +use tower_cookies::{Cookies, Cookie}; use crate::ctx::Ctx; use crate::web::AUTH_TOKEN; use crate::{Error, Result}; -pub async fn mw_require_auth( +pub async fn mw_ctx_resolver( cookies: Cookies, + mut req: Request, + next: Next, + ) -> Result { + println!("->> {:<12} - mw_ctx_resolver", "MIDDLEWARE"); + + let auth_token = cookies.get(AUTH_TOKEN).map(|c| c.value().to_string()); + + // Compute Result. + let result_ctx = match auth_token. + ok_or(Error::AuthFailNoAuthTokenCookie) + .and_then(parse_token) { + Ok((user_id, _exp, _sign)) => { + // TODO: Token components validations. + Ok(Ctx::new(user_id)) + }, + Err(e) => Err(e), + }; + + // Remove the cookie if something went wrong other than NoAuthTokenCookie. + if result_ctx.is_err() && !matches!(result_ctx, Err(Error::AuthFailNoAuthTokenCookie)) { + cookies.remove(Cookie::named(AUTH_TOKEN)) + } + + // Store the ctx_result in the request extension. + req.extensions_mut().insert(result_ctx); + + Ok(next.run(req).await) +} + +/// Middleware to require authentication. +pub async fn mw_require_auth( + ctx: Result, req: Request, next: Next ) -> Result { - println!("->> {:<12} - mw_require_auth", "MIDDLEWARE"); - let auth_token = cookies.get(AUTH_TOKEN).map(|c| c.value().to_string()); - - // Parse token. - let (user_id, exp, sign) = auth_token - .ok_or(Error::AuthFailNoAuthTokenCookie) - .and_then(parse_token)?; - - // TODO: Token components validation. + println!("->> {:<12} - mw_require_auth - {ctx:?}", "MIDDLEWARE"); + + ctx?; Ok(next.run(req).await) } // region: --- Ctx Extractor -// TODO: Find out what all this syntax means #[async_trait] impl FromRequestParts for Ctx { type Rejection = Error; @@ -40,19 +65,25 @@ impl FromRequestParts for Ctx { async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result { println!("->> {:<12} - Ctx", "EXTRACTOR"); - // Use the cookies extractor. - let cookies = parts.extract::().await.unwrap(); + parts + .extensions + .get::>() + .ok_or(Error::AuthFailCtxNotInRequestExt)? + .clone() - let auth_token = cookies.get(AUTH_TOKEN).map(|c| c.value().to_string()); - - // Parse token. - let (user_id, exp, sign) = auth_token - .ok_or(Error::AuthFailNoAuthTokenCookie) - .and_then(parse_token)?; - - // TODO: Token components validation. - - Ok(Ctx::new(user_id)) + // // Use the cookies extractor. + // let cookies = parts.extract::().await.unwrap(); + // + // let auth_token = cookies.get(AUTH_TOKEN).map(|c| c.value().to_string()); + // + // // Parse token. + // let (user_id, exp, sign) = auth_token + // .ok_or(Error::AuthFailNoAuthTokenCookie) + // .and_then(parse_token)?; + // + // // TODO: Token components validation. + // + // Ok(Ctx::new(user_id)) } } diff --git a/src/web/routes_properties.rs b/src/web/routes_properties.rs index 3f0b116..677d9b1 100644 --- a/src/web/routes_properties.rs +++ b/src/web/routes_properties.rs @@ -1,3 +1,4 @@ +use crate::ctx::Ctx; use crate::model::{ModelController, Property, PropertyForCreate}; use crate::Result; use axum::extract::{Path, State}; @@ -13,21 +14,21 @@ pub fn routes(mc: ModelController) -> Router { // region: --- REST Handlers -async fn create_property(State(mc): State, Json(property_fc): Json) -> Result> { +async fn create_property(State(mc): State, ctx: Ctx, Json(property_fc): Json) -> Result> { println!("->> {:<12} - create_property", "HANDLER"); - let property = mc.create_property(property_fc).await?; + let property = mc.create_property(ctx, property_fc).await?; Ok(Json(property)) } -async fn list_properties(State(mc): State) -> Result>> { +async fn list_properties(State(mc): State, ctx: Ctx) -> Result>> { println!("->> {:<12} - list_properties", "HANDLER"); - let properties = mc.list_properties().await?; + let properties = mc.list_properties(ctx).await?; Ok(Json(properties)) } -async fn delete_property(State(mc): State, Path(id): Path) -> Result> { +async fn delete_property(State(mc): State, ctx: Ctx, Path(id): Path) -> Result> { println!("->> {:<12} - delete_property", "HANDLER"); - let property = mc.delete_property(id).await?; + let property = mc.delete_property(ctx, id).await?; Ok(Json(property)) }