feat: ctx resolver

This commit is contained in:
Sandro Eiler 2023-10-15 22:30:01 +02:00
parent a38438a700
commit 8843134aa6
5 changed files with 71 additions and 36 deletions

View file

@ -3,13 +3,14 @@ use axum::response::{IntoResponse, Response};
pub type Result<T> = core::result::Result<T, Error>; pub type Result<T> = core::result::Result<T, Error>;
#[derive(Debug)] #[derive(Clone, Debug)]
pub enum Error { pub enum Error {
LoginFail, LoginFail,
// -- Auth errors. // -- Auth errors.
AuthFailNoAuthTokenCookie, AuthFailNoAuthTokenCookie,
AuthFailTokenWrongFormat, AuthFailTokenWrongFormat,
AuthFailCtxNotInRequestExt,
// -- Model errors. // -- Model errors.
PropertyDeleteFailIdNotFound { id: u64 }, PropertyDeleteFailIdNotFound { id: u64 },

View file

@ -37,6 +37,7 @@ async fn main() -> Result<()>{
.merge(web::routes_login::routes()) .merge(web::routes_login::routes())
.nest("/api", routes_apis) .nest("/api", routes_apis)
.layer(middleware::map_response(main_response_mapper)) .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 .layer(CookieManagerLayer::new()) // must be above? the auth routes
// TODO: continue video at 22:15 // TODO: continue video at 22:15
.fallback_service(routes_static()); .fallback_service(routes_static());

View file

@ -1,7 +1,7 @@
//! Simplistic model layer //! Simplistic model layer
//! (with mock-store layer) //! (with mock-store layer)
use crate::{Error, Result}; use crate::{Error, Result, ctx::Ctx};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
@ -9,6 +9,7 @@ use std::sync::{Arc, Mutex};
#[derive(Clone, Debug, Serialize)] #[derive(Clone, Debug, Serialize)]
pub struct Property { pub struct Property {
pub id: u64, pub id: u64,
pub creator_id: u64,
pub address: String, pub address: String,
pub contact: String, pub contact: String,
} }
@ -37,21 +38,21 @@ impl ModelController {
// CRUD implementation // CRUD implementation
impl ModelController { impl ModelController {
pub async fn create_property(&self, property: PropertyForCreate) -> Result<Property> { pub async fn create_property(&self, ctx: Ctx, property: PropertyForCreate) -> Result<Property> {
let mut store = self.property_store.lock().unwrap(); let mut store = self.property_store.lock().unwrap();
let id = store.len() as u64; 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())); store.push(Some(property.clone()));
Ok(property) Ok(property)
} }
pub async fn list_properties(&self) -> Result<Vec<Property>> { pub async fn list_properties(&self, ctx: Ctx) -> Result<Vec<Property>> {
let store = self.property_store.lock().unwrap(); let store = self.property_store.lock().unwrap();
let properties = store.iter().filter_map(|p| p.clone()).collect(); let properties = store.iter().filter_map(|p| p.clone()).collect();
Ok(properties) Ok(properties)
} }
pub async fn delete_property(&self, id: u64) -> Result<Property> { pub async fn delete_property(&self, ctx: Ctx, id: u64) -> Result<Property> {
let mut store = self.property_store.lock().unwrap(); let mut store = self.property_store.lock().unwrap();
let property = store.get_mut(id as usize).and_then(|p| p.take()); let property = store.get_mut(id as usize).and_then(|p| p.take());
property.ok_or(Error::PropertyDeleteFailIdNotFound { id }) property.ok_or(Error::PropertyDeleteFailIdNotFound { id })

View file

@ -6,33 +6,58 @@ use axum::http::request::Parts;
use axum::middleware::Next; use axum::middleware::Next;
use axum::response::Response; use axum::response::Response;
use lazy_regex::regex_captures; use lazy_regex::regex_captures;
use tower_cookies::Cookies; use tower_cookies::{Cookies, Cookie};
use crate::ctx::Ctx; use crate::ctx::Ctx;
use crate::web::AUTH_TOKEN; use crate::web::AUTH_TOKEN;
use crate::{Error, Result}; use crate::{Error, Result};
pub async fn mw_require_auth<B>( pub async fn mw_ctx_resolver<B>(
cookies: Cookies, cookies: Cookies,
mut req: Request<B>,
next: Next<B>,
) -> Result<Response> {
println!("->> {:<12} - mw_ctx_resolver", "MIDDLEWARE");
let auth_token = cookies.get(AUTH_TOKEN).map(|c| c.value().to_string());
// Compute Result<Ctx>.
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<B>(
ctx: Result<Ctx>,
req: Request<B>, req: Request<B>,
next: Next<B> next: Next<B>
) -> Result<Response> { ) -> Result<Response> {
println!("->> {:<12} - mw_require_auth", "MIDDLEWARE"); println!("->> {:<12} - mw_require_auth - {ctx:?}", "MIDDLEWARE");
let auth_token = cookies.get(AUTH_TOKEN).map(|c| c.value().to_string());
ctx?;
// Parse token.
let (user_id, exp, sign) = auth_token
.ok_or(Error::AuthFailNoAuthTokenCookie)
.and_then(parse_token)?;
// TODO: Token components validation.
Ok(next.run(req).await) Ok(next.run(req).await)
} }
// region: --- Ctx Extractor // region: --- Ctx Extractor
// TODO: Find out what all this syntax means
#[async_trait] #[async_trait]
impl<S: Send + Sync> FromRequestParts<S> for Ctx { impl<S: Send + Sync> FromRequestParts<S> for Ctx {
type Rejection = Error; type Rejection = Error;
@ -40,19 +65,25 @@ impl<S: Send + Sync> FromRequestParts<S> for Ctx {
async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self> { async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self> {
println!("->> {:<12} - Ctx", "EXTRACTOR"); println!("->> {:<12} - Ctx", "EXTRACTOR");
// Use the cookies extractor. parts
let cookies = parts.extract::<Cookies>().await.unwrap(); .extensions
.get::<Result<Ctx>>()
.ok_or(Error::AuthFailCtxNotInRequestExt)?
.clone()
let auth_token = cookies.get(AUTH_TOKEN).map(|c| c.value().to_string()); // // Use the cookies extractor.
// let cookies = parts.extract::<Cookies>().await.unwrap();
// Parse token. //
let (user_id, exp, sign) = auth_token // let auth_token = cookies.get(AUTH_TOKEN).map(|c| c.value().to_string());
.ok_or(Error::AuthFailNoAuthTokenCookie) //
.and_then(parse_token)?; // // Parse token.
// let (user_id, exp, sign) = auth_token
// TODO: Token components validation. // .ok_or(Error::AuthFailNoAuthTokenCookie)
// .and_then(parse_token)?;
Ok(Ctx::new(user_id)) //
// // TODO: Token components validation.
//
// Ok(Ctx::new(user_id))
} }
} }

View file

@ -1,3 +1,4 @@
use crate::ctx::Ctx;
use crate::model::{ModelController, Property, PropertyForCreate}; use crate::model::{ModelController, Property, PropertyForCreate};
use crate::Result; use crate::Result;
use axum::extract::{Path, State}; use axum::extract::{Path, State};
@ -13,21 +14,21 @@ pub fn routes(mc: ModelController) -> Router {
// region: --- REST Handlers // region: --- REST Handlers
async fn create_property(State(mc): State<ModelController>, Json(property_fc): Json<PropertyForCreate>) -> Result<Json<Property>> { async fn create_property(State(mc): State<ModelController>, ctx: Ctx, Json(property_fc): Json<PropertyForCreate>) -> Result<Json<Property>> {
println!("->> {:<12} - create_property", "HANDLER"); 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)) Ok(Json(property))
} }
async fn list_properties(State(mc): State<ModelController>) -> Result<Json<Vec<Property>>> { async fn list_properties(State(mc): State<ModelController>, ctx: Ctx) -> Result<Json<Vec<Property>>> {
println!("->> {:<12} - list_properties", "HANDLER"); println!("->> {:<12} - list_properties", "HANDLER");
let properties = mc.list_properties().await?; let properties = mc.list_properties(ctx).await?;
Ok(Json(properties)) Ok(Json(properties))
} }
async fn delete_property(State(mc): State<ModelController>, Path(id): Path<u64>) -> Result<Json<Property>> { async fn delete_property(State(mc): State<ModelController>, ctx: Ctx, Path(id): Path<u64>) -> Result<Json<Property>> {
println!("->> {:<12} - delete_property", "HANDLER"); println!("->> {:<12} - delete_property", "HANDLER");
let property = mc.delete_property(id).await?; let property = mc.delete_property(ctx, id).await?;
Ok(Json(property)) Ok(Json(property))
} }