diff --git a/Cargo.lock b/Cargo.lock index 36e5f92..ae97711 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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea5d730647d4fadd988536d06fecce94b7b4f2a7efdae548f1cf4b63205518ab" +dependencies = [ + "memchr", +] + [[package]] name = "anyhow" version = "1.0.71" @@ -42,9 +51,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "axum" -version = "0.6.18" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8175979259124331c1d7bf6586ee7e0da434155e4b2d48ec2c8386281d8df39" +checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" dependencies = [ "async-trait", "axum-core", @@ -412,9 +421,9 @@ checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] name = "httpc-test" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e7d56c0f391b27f6b4186e9ad7dad17c2f80329a8a409312a6e7622a25379f3" +checksum = "81a425cb8352fb5080b3622e8a4265c63e75bedd68a4c19a83f7d4c88f9c9667" dependencies = [ "cookie 0.16.2", "http", @@ -449,7 +458,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2", + "socket2 0.4.9", "tokio", "tower-service", "tracing", @@ -551,6 +560,29 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "lazy-regex" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e723bd417b2df60a0f6a2b6825f297ea04b245d4ba52b5a22cb679bdf58b05fa" +dependencies = [ + "lazy-regex-proc_macros", + "once_cell", + "regex", +] + +[[package]] +name = "lazy-regex-proc_macros" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f0a1d9139f0ee2e862e08a9c5d0ba0470f2aa21cd1e1aa1b1562f83116c725f" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "syn", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -564,6 +596,7 @@ dependencies = [ "anyhow", "axum", "httpc-test", + "lazy-regex", "serde", "serde_json", "tokio", @@ -791,9 +824,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.9" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" [[package]] name = "pin-utils" @@ -850,6 +883,35 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "regex" +version = "1.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12de2eff854e5fa4b1295edd650e227e9d8fb0c9e90b12e7f36d6a6811791a29" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49530408a136e16e5b486e883fbb6ba058e8e4e8ae6621a77b048b314336e629" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" + [[package]] name = "reqwest" version = "0.11.18" @@ -1058,6 +1120,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "socket2" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e" +dependencies = [ + "libc", + "windows-sys 0.48.0", +] + [[package]] name = "syn" version = "2.0.22" @@ -1153,11 +1225,10 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.29.1" +version = "1.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "532826ff75199d5833b9d2c5fe410f29235e25704ee5f0ef599fb51c21f4a4da" +checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" dependencies = [ - "autocfg", "backtrace", "bytes", "libc", @@ -1166,7 +1237,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2", + "socket2 0.5.4", "tokio-macros", "windows-sys 0.48.0", ] @@ -1241,9 +1312,9 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.4.1" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8bd22a874a2d0b70452d5597b12c537331d49060824a95f49f108994f94aa4c" +checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140" dependencies = [ "bitflags 2.3.3", "bytes", diff --git a/Cargo.toml b/Cargo.toml index 752b438..d30d781 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,13 +6,17 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -tokio = { version = "1.29.1", features = ["full"] } -axum = "0.6.18" +tokio = { version = "1.32.0", features = ["full"] } +# Serde / json serde = { version = "1.0", features = ["derive"] } serde_json = "1" -tower-http = { version = "0.4.1", features = ["fs"] } +# Axum +axum = { version = "0.6.20" } +tower-http = { version = "0.4.4", features = ["fs"] } tower-cookies = "0.9" +# Others +lazy-regex = "3" [dev-dependencies] anyhow = "1" -httpc-test = "0.1.1" +httpc-test = "0.1.5" diff --git a/src/error.rs b/src/error.rs index c0ed6e2..e739e57 100644 --- a/src/error.rs +++ b/src/error.rs @@ -6,6 +6,12 @@ pub type Result = core::result::Result; #[derive(Debug)] pub enum Error { LoginFail, + + // -- Auth errors. + AuthFailNoAuthTokenCookie, + AuthFailTokenWrongFormat, + + // -- Model errors. PropertyDeleteFailIdNotFound { id: u64 }, } diff --git a/src/main.rs b/src/main.rs index 6bb81b8..14f0557 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,7 @@ #![allow(unused)] +use crate::model::ModelController; + pub use self::error::{Error, Result}; use std::net::SocketAddr; @@ -25,10 +27,15 @@ struct HelloParams { } #[tokio::main] -async fn main() { +async fn main() -> Result<()>{ + let mc = ModelController::new().await?; + + let routes_apis = web::routes_properties::routes(mc.clone()).route_layer(middleware::from_fn(web::mw_auth::mw_require_auth)); + let routes_all = Router::new() .merge(routes_hello()) .merge(web::routes_login::routes()) + .nest("/api", routes_apis) .layer(middleware::map_response(main_response_mapper)) .layer(CookieManagerLayer::new()) // must be above? the auth routes // TODO: continue video at 22:15 @@ -40,6 +47,8 @@ async fn main() { .serve(routes_all.into_make_service()) .await .unwrap(); + + Ok(()) } diff --git a/src/web/mod.rs b/src/web/mod.rs index 950e7e6..e414648 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_properties; diff --git a/src/web/mw_auth.rs b/src/web/mw_auth.rs new file mode 100644 index 0000000..9aca7fc --- /dev/null +++ b/src/web/mw_auth.rs @@ -0,0 +1,42 @@ +use axum::http::Request; +use axum::middleware::Next; +use axum::response::Response; +use lazy_regex::regex_captures; +use tower_cookies::Cookies; + +use crate::web::AUTH_TOKEN; +use crate::{Error, Result}; + +pub async fn mw_require_auth( + cookies: Cookies, + 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. + + Ok(next.run(req).await) +} + +/// Parse a 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+)\.(.+)\.(.+)"#, // a literal regex + &token) + .ok_or(Error::AuthFailTokenWrongFormat)?; + + let user_id: u64 = user_id + .parse() + .map_err(|_| Error::AuthFailTokenWrongFormat)?; + + Ok((user_id, exp.to_string(), sign.to_string())) + +} diff --git a/tests/quick_dev.rs b/tests/quick_dev.rs index 54f7ec6..e6f11e3 100644 --- a/tests/quick_dev.rs +++ b/tests/quick_dev.rs @@ -21,7 +21,7 @@ async fn test_quick_dev() -> Result<()> { json!( { "username": "demo1", - "password": "demo1" + "password": "demowrong" } ) ); @@ -31,7 +31,7 @@ async fn test_quick_dev() -> Result<()> { json!( { "username": "demo1", - "password": "demowrong" + "password": "demo1" } ) ); @@ -39,5 +39,34 @@ async fn test_quick_dev() -> Result<()> { hc.do_get("/hello2/mike").await?.print().await?; + let req_create_property = hc.do_post( + "/api/properties", + json!( + { + "address": "Lolilat Street 1", + "contact": "01234 567890" + } + ) + ); + req_create_property.await?.print().await?; + let req_create_property = hc.do_post( + "/api/properties", + json!( + { + "address": "Lolilat Street 2", + "contact": "01243 217890" + } + ) + ); + req_create_property.await?.print().await?; + let req_get_properties = hc.do_get("/api/properties").await?; + req_get_properties.print().await?; + let req_delete_property = hc.do_delete("/api/properties/1").await?; + req_delete_property.print().await?; + let req_get_properties = hc.do_get("/api/properties").await?; + req_get_properties.print().await?; + let req_delete_property = hc.do_delete("/api/properties/0").await?; + req_delete_property.print().await?; + Ok(()) }