diff --git a/Cargo.lock b/Cargo.lock index 2ce330b..29fb9ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,7 +23,7 @@ version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" dependencies = [ - "getrandom", + "getrandom 0.2.16", "once_cell", "version_check", ] @@ -354,6 +354,33 @@ dependencies = [ "tracing", ] +[[package]] +name = "axum-extra" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45bf463831f5131b7d3c756525b305d40f1185b688565648a92e1392ca35713d" +dependencies = [ + "axum", + "axum-core", + "axum-macros", + "bytes", + "form_urlencoded", + "futures-util", + "headers", + "http", + "http-body", + "http-body-util", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_html_form", + "tower", + "tower-layer", + "tower-service", +] + [[package]] name = "axum-macros" version = "0.5.0" @@ -380,6 +407,12 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + [[package]] name = "base64" version = "0.22.1" @@ -755,13 +788,6 @@ dependencies = [ "serde", ] -[[package]] -name = "entity" -version = "0.1.0" -dependencies = [ - "sea-orm", -] - [[package]] name = "equivalent" version = "1.0.2" @@ -976,8 +1002,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", + "js-sys", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", ] [[package]] @@ -1033,6 +1073,30 @@ dependencies = [ "hashbrown 0.15.3", ] +[[package]] +name = "headers" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "322106e6bd0cba2d5ead589ddb8150a13d7c4217cf80d7c4f682ca994ccc6aa9" +dependencies = [ + "base64 0.21.7", + "bytes", + "headers-core", + "http", + "httpdate", + "mime", + "sha1", +] + +[[package]] +name = "headers-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4" +dependencies = [ + "http", +] + [[package]] name = "heck" version = "0.4.1" @@ -1050,13 +1114,21 @@ name = "helios-server-rs" version = "0.1.0" dependencies = [ "axum", + "axum-extra", "axum-macros", "clap", "dotenvy", + "jsonwebtoken", + "once_cell", + "pbkdf2", + "rand_core 0.9.3", "sea-orm", "serde", + "serde_json", "tokio", "toml", + "tower", + "tower-http", "tracing", "tracing-subscriber", ] @@ -1361,6 +1433,21 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "jsonwebtoken" +version = "9.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde" +dependencies = [ + "base64 0.22.1", + "js-sys", + "pem", + "ring", + "serde", + "serde_json", + "simple_asn1", +] + [[package]] name = "kv-log-macro" version = "1.0.7" @@ -1493,7 +1580,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.52.0", ] @@ -1659,6 +1746,39 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "password-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +dependencies = [ + "base64ct", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", + "password-hash", + "sha2", +] + +[[package]] +name = "pem" +version = "3.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38af38e8470ac9dee3ce1bae1af9c1671fffc44ddfd8bd1d0a3445bf349a8ef3" +dependencies = [ + "base64 0.22.1", + "serde", +] + [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -1854,6 +1974,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + [[package]] name = "radium" version = "0.7.0" @@ -1868,7 +1994,7 @@ checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", - "rand_core", + "rand_core 0.6.4", ] [[package]] @@ -1878,7 +2004,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", ] [[package]] @@ -1887,7 +2013,16 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.16", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.3", ] [[package]] @@ -1960,7 +2095,7 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom", + "getrandom 0.2.16", "libc", "untrusted", "windows-sys 0.52.0", @@ -2008,7 +2143,7 @@ dependencies = [ "num-traits", "pkcs1", "pkcs8", - "rand_core", + "rand_core 0.6.4", "signature", "spki", "subtle", @@ -2290,6 +2425,19 @@ dependencies = [ "syn 2.0.101", ] +[[package]] +name = "serde_html_form" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d2de91cf02bbc07cde38891769ccd5d4f073d22a40683aa4bc7a95781aaa2c4" +dependencies = [ + "form_urlencoded", + "indexmap", + "itoa", + "ryu", + "serde", +] + [[package]] name = "serde_json" version = "1.0.140" @@ -2386,7 +2534,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest", - "rand_core", + "rand_core 0.6.4", ] [[package]] @@ -2395,6 +2543,18 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" +[[package]] +name = "simple_asn1" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb" +dependencies = [ + "num-bigint", + "num-traits", + "thiserror", + "time", +] + [[package]] name = "slab" version = "0.4.9" @@ -2461,7 +2621,7 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" dependencies = [ - "base64", + "base64 0.22.1", "bigdecimal", "bytes", "chrono", @@ -2541,7 +2701,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" dependencies = [ "atoi", - "base64", + "base64 0.22.1", "bigdecimal", "bitflags", "byteorder", @@ -2588,7 +2748,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" dependencies = [ "atoi", - "base64", + "base64 0.22.1", "bigdecimal", "bitflags", "byteorder", @@ -2920,6 +3080,22 @@ dependencies = [ "tracing", ] +[[package]] +name = "tower-http" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +dependencies = [ + "bitflags", + "bytes", + "http", + "http-body", + "pin-project-lite", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "tower-layer" version = "0.3.3" @@ -3097,6 +3273,15 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + [[package]] name = "wasite" version = "0.1.0" @@ -3450,6 +3635,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] + [[package]] name = "writeable" version = "0.6.1" diff --git a/Cargo.toml b/Cargo.toml index 19c7e7a..b5d5b4a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,10 +5,11 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [workspace] -members = [".", "entity", "migration"] +members = [".", "migration"] [dependencies] axum = { version = "0.8" } +axum-extra = {version = "0.10", features = ["typed-header", "typed-routing"] } axum-macros = "0.5" sea-orm = { version = "1.1.12", features = [ "sqlx-postgres", @@ -17,9 +18,16 @@ sea-orm = { version = "1.1.12", features = [ "chrono", ] } serde = { version = "1", features = ["derive"] } +serde_json = "1.0.140" tokio = { version = "1.0", features = ["full"] } tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } dotenvy = "0.15.7" toml = "0.8.22" -clap = { version = "4.0", features = ["derive"] } \ No newline at end of file +clap = { version = "4.0", features = ["derive"] } +pbkdf2 = { version = "0.12", features = ["simple"] } +rand_core = { version = "0.9.3", features = ["std"] } +jsonwebtoken = "9" +once_cell = "1.21.3" +tower-http = {version= "0.6", features = ["trace", "cors"] } +tower = "0.5.2" \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 69a410a..4b2a53d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,17 +1,26 @@ -use axum::Router; +use axum::{http::Method, Router}; use clap::Parser; use sea_orm::{Database, DatabaseConnection}; use serde::Deserialize; +use tower::ServiceBuilder; +use tower_http::cors::{Any, CorsLayer}; +use tower_http::trace::TraceLayer; +use tracing::info; +use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; // Project modules mod api; +mod middleware; mod model; +mod util; #[tokio::main] async fn main() { dotenvy::dotenv().unwrap(); // initialize tracing - tracing_subscriber::fmt::init(); + tracing_subscriber::registry() + .with(tracing_subscriber::fmt::layer()) + .init(); let cli = Cli::parse(); match cli.command { Command::Serve { config_path } => { @@ -76,24 +85,30 @@ async fn load_config(path: &str) -> Result> { // ====== Commands ====== - // start http server -async fn start_server(config: &Config){ - // Define the router - // let app = Router.new() - // .nest(); +async fn start_server(config: &Config) { let conn = Database::connect(&config.database.connection) - .await + .await .expect("Database connection failed."); - let state = AppState{conn }; + let state = AppState { conn }; + // Build router + let cors_layer = CorsLayer::new() + .allow_methods([Method::GET, Method::POST]) + .allow_origin(Any); + let global_layer = ServiceBuilder::new() + .layer(TraceLayer::new_for_http()) + .layer(cors_layer); + let app = Router::new() .nest("/api/v1/book", api::book::get_nest_handlers()) - .with_state(state); + .with_state(state) + .layer(global_layer); let host = config.service.host.clone(); let port = config.service.port; let server_url = format!("{host}:{port}"); let listener = tokio::net::TcpListener::bind(&server_url).await.unwrap(); - axum::serve(listener, app).await.expect("Service panic happened"); - + axum::serve(listener, app) + .await + .expect("Service panic happened"); } diff --git a/src/middleware/auth.rs b/src/middleware/auth.rs new file mode 100644 index 0000000..6921cd6 --- /dev/null +++ b/src/middleware/auth.rs @@ -0,0 +1,115 @@ +use axum::{ + extract::FromRequestParts, + http::{ + request::Parts, + StatusCode, + }, + Json, RequestPartsExt, + response::{IntoResponse, Response}, +}; +use axum_extra::{ + headers::{authorization::Bearer, Authorization}, + TypedHeader, +}; +use serde::{Deserialize, Serialize}; +use serde_json::json; +use jsonwebtoken::{decode,encode, DecodingKey, EncodingKey, Header, Validation}; +use std::fmt::Display; +use once_cell::sync::Lazy; +use crate::util; + +#[derive(Debug, Serialize, Deserialize)] +pub struct Claims { + sub: String, + // company: String, + exp: usize, + pub uid: i64, +} + +#[derive(Debug, Serialize)] +struct AuthBody { + access_token: String, + token_type: String, +} + +#[derive(Debug, Deserialize)] +struct AuthPayload { + client_id: String, + client_secret: String, +} + +#[derive(Debug)] +pub enum AuthError { + WrongCredentials, + MissingCredentials, + TokenCreation, + InvalidToken, +} + +static KEYS: Lazy = Lazy::new(|| { + let secret = std::env::var("JWT_SECRET").expect("JWT_SECRET must be set"); + Keys::new(secret.as_bytes()) +}); + +struct Keys { + encoding: EncodingKey, + decoding: DecodingKey, +} + +impl Keys { + fn new(secret: &[u8]) -> Self { + Self { + encoding: EncodingKey::from_secret(secret), + decoding: DecodingKey::from_secret(secret), + } + } +} + +impl Display for Claims { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Email: {}", self.sub) + } +} + +impl AuthBody { + fn new(access_token: String) -> Self { + Self { + access_token, + token_type: "Bearer".to_string(), + } + } +} + +impl FromRequestParts for Claims +where + S: Send + Sync, +{ + type Rejection = AuthError; + + async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result { + // Extract the token from the authorization header + let TypedHeader(Authorization(bearer)) = parts + .extract::>>() + .await + .map_err(|_| AuthError::InvalidToken)?; + // Decode the user data + let token_data = decode::(bearer.token(), &KEYS.decoding, &Validation::default()) + .map_err(|_| AuthError::InvalidToken)?; + + Ok(token_data.claims) + } +} +impl IntoResponse for AuthError { + fn into_response(self) -> Response { + let (status, error_message) = match self { + AuthError::WrongCredentials => (StatusCode::UNAUTHORIZED, "Wrong credentials"), + AuthError::MissingCredentials => (StatusCode::BAD_REQUEST, "Missing credentials"), + AuthError::TokenCreation => (StatusCode::INTERNAL_SERVER_ERROR, "Token creation error"), + AuthError::InvalidToken => (StatusCode::BAD_REQUEST, "Invalid token"), + }; + let body = Json(json!({ + "error": error_message, + })); + (status, body).into_response() + } +} diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs new file mode 100644 index 0000000..5696e21 --- /dev/null +++ b/src/middleware/mod.rs @@ -0,0 +1 @@ +pub mod auth; \ No newline at end of file diff --git a/src/util/mod.rs b/src/util/mod.rs new file mode 100644 index 0000000..c37f2a5 --- /dev/null +++ b/src/util/mod.rs @@ -0,0 +1 @@ +pub mod pass; \ No newline at end of file diff --git a/src/util/pass.rs b/src/util/pass.rs new file mode 100644 index 0000000..305f007 --- /dev/null +++ b/src/util/pass.rs @@ -0,0 +1,16 @@ +use std::error::Error; +use pbkdf2::{ + password_hash::{ + rand_core::OsRng, + PasswordHash,SaltString, + }, + Pbkdf2, +}; +use pbkdf2::password_hash::PasswordHasher; + +pub fn get_pbkdf2_from_psw(password:String) -> Result { + let salt = SaltString::generate(&mut OsRng); + let password_hash = Pbkdf2.hash_password(password.as_bytes(), &salt)?.to_string(); + println!("{}",password_hash); + return Ok(password_hash) +}