From 79621f7c2dd2d2e1db595c3a53e7cd94dc09dada Mon Sep 17 00:00:00 2001 From: root Date: Mon, 5 Feb 2024 10:19:46 -0300 Subject: [PATCH] Starting token middleware impl --- Cargo.lock | 63 ++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 2 ++ src/ctx.rs | 16 ++++++++++++ src/error.rs | 5 ++++ src/main.rs | 8 ++++-- src/web/mod.rs | 1 + src/web/mw_auth.rs | 49 ++++++++++++++++++++++++++++++++++++ 7 files changed, 142 insertions(+), 2 deletions(-) create mode 100644 src/ctx.rs create mode 100644 src/web/mw_auth.rs diff --git a/Cargo.lock b/Cargo.lock index b0da30d..edf3890 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,15 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + [[package]] name = "anyhow" version = "1.0.79" @@ -100,8 +109,10 @@ name = "axum-web" version = "0.1.0" dependencies = [ "anyhow", + "async-trait", "axum", "httpc-test", + "lazy-regex", "serde", "serde_json", "tokio", @@ -699,6 +710,29 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "lazy-regex" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d12be4595afdf58bd19e4a9f4e24187da2a66700786ff660a418e9059937a4c" +dependencies = [ + "lazy-regex-proc_macros", + "once_cell", + "regex", +] + +[[package]] +name = "lazy-regex-proc_macros" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44bcd58e6c97a7fcbaffcdc95728b393b8d98933bfadad49ed4097845b57ef0b" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "syn", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -1035,6 +1069,35 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "regex" +version = "1.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + [[package]] name = "reqwest" version = "0.11.23" diff --git a/Cargo.toml b/Cargo.toml index 38582c2..560621a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,8 @@ axum = "0.7" tower-http = { version = "0.5.1", features = ["fs", "cors"] } tower = { version = "0.4", features = ["full"] } tower-cookies = "0.10" +lazy-regex = "3.1" +async-trait = "0.1" [dev-dependencies] anyhow = "1" diff --git a/src/ctx.rs b/src/ctx.rs new file mode 100644 index 0000000..709fdbc --- /dev/null +++ b/src/ctx.rs @@ -0,0 +1,16 @@ +#[derive(Clone, Debug)] +pub struct Ctx { + user_id: u64, +} + +impl Ctx { + pub fn new(user_id: u64) -> Self { + Self { user_id } + } +} + +impl Ctx { + pub fn user_id(&self) -> u64 { + self.user_id + } +} diff --git a/src/error.rs b/src/error.rs index f439a14..aeb5588 100644 --- a/src/error.rs +++ b/src/error.rs @@ -9,6 +9,11 @@ pub type Result = core::result::Result; pub enum Error { LoginFail, + // -- Auth Errors -- + AuthFailNoAuthTokenCookie, + AuthFailTokenWrongFormat, + AuthFailCtxNotInRequestExt, + // -- Model Errors -- TicketDeleteFailIdNotFound { id: u64 }, } diff --git a/src/main.rs b/src/main.rs index 99ca347..5b56790 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,15 +9,16 @@ use axum::{ }; use model::ModelController; use tower_cookies::{CookieManager, CookieManagerLayer}; +use web::mw_auth; pub use self::error::{Error, Result}; -use serde::Deserialize; use tokio::net::TcpListener; use tower_http::{ cors::{Any, CorsLayer}, services::ServeDir, }; +mod ctx; mod error; mod model; mod web; @@ -32,9 +33,12 @@ async fn main() -> Result<()> { .allow_headers(vec![HeaderName::from_static("content-type")]) .allow_origin(Any); + let routes_apis = web::routes_tickets::routes(mc.clone()) + .route_layer(middleware::from_fn(web::mw_auth::mw_require_auth)); + let routes_all = Router::new() .merge(web::routes_login::routes()) - .nest("/api", web::routes_tickets::routes(mc.clone())) + .nest("/api", routes_apis) .layer(cors) .layer(middleware::map_response(main_response_mapper)) .layer(CookieManagerLayer::new()) diff --git a/src/web/mod.rs b/src/web/mod.rs index 312baf4..05b2c91 100644 --- a/src/web/mod.rs +++ b/src/web/mod.rs @@ -1,3 +1,4 @@ +pub mod mw_auth; pub mod routes_login; pub mod routes_tickets; diff --git a/src/web/mw_auth.rs b/src/web/mw_auth.rs new file mode 100644 index 0000000..9c38428 --- /dev/null +++ b/src/web/mw_auth.rs @@ -0,0 +1,49 @@ +use crate::ctx::Ctx; +use crate::web::AUTH_TOKEN; +use crate::{Error, Result}; +use async_trait::async_trait; +use axum::extract::FromRequestParts; +use axum::http::request::Parts; +use axum::RequestPartsExt; +use axum::{body::Body, extract::Request, middleware::Next, response::Response}; +use lazy_regex::regex_captures; +use tower_cookies::Cookies; + +pub async fn mw_require_auth(cookies: Cookies, req: Request, next: Next) -> Result { + let auth_token = cookies.get(AUTH_TOKEN).map(|c| c.to_string()); + + let (user_id, exp, sign) = auth_token + .ok_or(Error::AuthFailNoAuthTokenCookie) + .and_then(parse_token)?; + + Ok(next.run(req).await) +} + +#[async_trait] +impl FromRequestParts for Ctx { + type Rejection = Error; + + async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result { + println!("->> {:<12} - Ctx", "EXTRACTOR"); + + parts + .extensions + .get::>() + .ok_or(Error::AuthFailCtxNotInRequestExt)? + .clone() + } +} + +// Parse token of format `user-[user-id]-[expiration].[signature]` +// Returns (user_id, expiration, signature) +fn parse_token(token: String) -> Result<(u64, String, String)> { + let (_whole, user_id, exp, sign) = regex_captures!(r#"^user-(\d+)\.(.+)\.(.+)"#, &token) + .ok_or(Error::AuthFailTokenWrongFormat) + .unwrap(); + + let user_id: u64 = user_id + .parse() + .map_err(|_| Error::AuthFailTokenWrongFormat)?; + + Ok((user_id, exp.to_string(), sign.to_string())) +}