diff --git a/Cargo.lock b/Cargo.lock index 623a9ed..f796cd2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -375,6 +375,19 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" +[[package]] +name = "bcrypt" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abaf6da45c74385272ddf00e1ac074c7d8a6c1a1dda376902bd6a427522a8b2c" +dependencies = [ + "base64", + "blowfish", + "getrandom 0.3.4", + "subtle", + "zeroize", +] + [[package]] name = "bigdecimal" version = "0.4.9" @@ -441,6 +454,16 @@ dependencies = [ "piper", ] +[[package]] +name = "blowfish" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e412e2cd0f2b2d93e02543ceae7917b3c70331573df19ee046bcbc35e45e87d7" +dependencies = [ + "byteorder", + "cipher", +] + [[package]] name = "borsh" version = "1.6.0" @@ -1137,8 +1160,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -1575,6 +1600,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", + "js-sys", + "pem", + "ring", + "serde", + "serde_json", + "simple_asn1", +] + [[package]] name = "kv-log-macro" version = "1.0.7" @@ -1713,6 +1753,15 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.60.2", +] + [[package]] name = "num-bigint" version = "0.4.6" @@ -1741,9 +1790,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" [[package]] name = "num-integer" @@ -1827,9 +1876,11 @@ dependencies = [ "argon2", "axum", "base64", + "bcrypt", "chrono", "env_logger", "futures-util", + "jsonwebtoken", "log", "migration", "parking_lot", @@ -1842,7 +1893,10 @@ dependencies = [ "ssh-key", "tokio", "toml", + "tower", "tower-http", + "tracing", + "tracing-subscriber", "uuid", "validator", ] @@ -1925,6 +1979,16 @@ dependencies = [ "subtle", ] +[[package]] +name = "pem" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" +dependencies = [ + "base64", + "serde_core", +] + [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -2281,6 +2345,20 @@ dependencies = [ "subtle", ] +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.16", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + [[package]] name = "rkyv" version = "0.7.45" @@ -2720,6 +2798,18 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" +[[package]] +name = "simple_asn1" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d585997b0ac10be3c5ee635f1bab02d512760d14b7c468801ac8a01d9ae5f1d" +dependencies = [ + "num-bigint", + "num-traits", + "thiserror", + "time", +] + [[package]] name = "slab" version = "0.4.11" @@ -3127,30 +3217,30 @@ dependencies = [ [[package]] name = "time" -version = "0.3.44" +version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" dependencies = [ "deranged", "itoa", "num-conv", "powerfmt", - "serde", + "serde_core", "time-core", "time-macros", ] [[package]] name = "time-core" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" [[package]] name = "time-macros" -version = "0.2.24" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" dependencies = [ "num-conv", "time-core", @@ -3307,8 +3397,10 @@ checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ "bitflags", "bytes", + "futures-util", "http", "http-body", + "http-body-util", "pin-project-lite", "tokio", "tower-layer", @@ -3358,6 +3450,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c" dependencies = [ "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", ] [[package]] @@ -3367,12 +3471,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" dependencies = [ "matchers", + "nu-ansi-term", "once_cell", "regex-automata", "sharded-slab", + "smallvec", "thread_local", "tracing", "tracing-core", + "tracing-log", ] [[package]] @@ -3431,6 +3538,12 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.5.7" @@ -3504,6 +3617,12 @@ dependencies = [ "syn 2.0.110", ] +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + [[package]] name = "value-bag" version = "1.12.0" @@ -3689,6 +3808,15 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.60.2" @@ -3722,6 +3850,22 @@ dependencies = [ "windows_x86_64_msvc 0.48.5", ] +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + [[package]] name = "windows-targets" version = "0.53.5" @@ -3732,7 +3876,7 @@ dependencies = [ "windows_aarch64_gnullvm 0.53.1", "windows_aarch64_msvc 0.53.1", "windows_i686_gnu 0.53.1", - "windows_i686_gnullvm", + "windows_i686_gnullvm 0.53.1", "windows_i686_msvc 0.53.1", "windows_x86_64_gnu 0.53.1", "windows_x86_64_gnullvm 0.53.1", @@ -3745,6 +3889,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + [[package]] name = "windows_aarch64_gnullvm" version = "0.53.1" @@ -3757,6 +3907,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + [[package]] name = "windows_aarch64_msvc" version = "0.53.1" @@ -3769,12 +3925,24 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + [[package]] name = "windows_i686_gnu" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + [[package]] name = "windows_i686_gnullvm" version = "0.53.1" @@ -3787,6 +3955,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + [[package]] name = "windows_i686_msvc" version = "0.53.1" @@ -3799,6 +3973,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + [[package]] name = "windows_x86_64_gnu" version = "0.53.1" @@ -3811,6 +3991,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + [[package]] name = "windows_x86_64_gnullvm" version = "0.53.1" @@ -3823,6 +4009,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + [[package]] name = "windows_x86_64_msvc" version = "0.53.1" diff --git a/Cargo.toml b/Cargo.toml index 8deb4f9..cf4cab0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,8 +44,8 @@ tokio = { version = "1.49", features = ["full"] } axum = { version = "0.8.8", features = ["macros", "ws"] } #utoipa = "5.4" #utoipa-swagger-ui = { version = "9.0", features = ["axum"] } -#tower = "0.5" -tower-http = { version = "0.6", features = ["trace", "cors", "timeout"] } +tower = { version = "0.5", features = ["util"] } +tower-http = { version = "0.6", features = ["trace", "cors", "timeout", "catch-panic"] } # UDP socket2 = "0.6" @@ -56,6 +56,8 @@ migration = { path = "migration" } # logs log = "0.4" +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } env_logger = "0.11.8" # utils @@ -71,4 +73,6 @@ futures-util = "0.3" rand = "0.9" ssh-key = { version = "0.6", features = ["default", "crypto"] } base64 = "0.22" -argon2 = "0.5.3" \ No newline at end of file +argon2 = "0.5.3" +jsonwebtoken = "9.3.1" +bcrypt = "0.17.0" \ No newline at end of file diff --git a/src/app/app.rs b/src/app/app.rs index d8669cc..07cb444 100644 --- a/src/app/app.rs +++ b/src/app/app.rs @@ -23,14 +23,17 @@ impl App { pub async fn init(config: Config) -> Self { let event_bus = EventBus::new(1024); - let db = Database::init(&config.database_url()).await.expect("Failed to initialize database"); + let db = Database::init(&config.database_url()) + .await + .expect("Failed to initialize database"); let repositories = Repositories::new(db.get_connection(), event_bus.clone()); - let state = AppState{ + let state = AppState { db: db.clone(), event_bus: event_bus.clone(), repositories: repositories.clone(), - clients: Clients::new() + clients: Clients::new(), + config: config.clone(), }; let udp_server = UDPServer::new(config.bind_addr()); @@ -82,11 +85,9 @@ impl App { println!("Signal SIGTERM reçu (Docker stop) !"); } } - + println!("Nettoyage et fermeture de l'application."); } - async fn shutdown(&self) { - - } -} \ No newline at end of file + async fn shutdown(&self) {} +} diff --git a/src/app/state.rs b/src/app/state.rs index 407ae7d..e64073c 100644 --- a/src/app/state.rs +++ b/src/app/state.rs @@ -1,3 +1,4 @@ +use crate::config::Config; use crate::database::Database; use crate::event_bus::EventBus; use crate::hub::Clients; @@ -9,6 +10,7 @@ pub struct AppState { pub event_bus: EventBus, pub repositories: Repositories, pub clients: Clients, + pub config: Config, } impl AppState { @@ -29,4 +31,4 @@ impl AppState { // pub fn new() -> Self { // Self { } // } -// } \ No newline at end of file +// } diff --git a/src/interfaces/http/dto/channel.rs b/src/interfaces/http/dto/channel.rs index d1050fb..9b0229b 100644 --- a/src/interfaces/http/dto/channel.rs +++ b/src/interfaces/http/dto/channel.rs @@ -1,7 +1,7 @@ use crate::models::channel; use crate::models::channel::ChannelType; use sea_orm::Set; -use serde::Serialize; +use serde::{Deserialize, Serialize}; use uuid::Uuid; #[derive(Debug, Serialize)] @@ -27,11 +27,11 @@ impl From for ChannelResponse { } } -#[derive(Debug, Serialize)] +#[derive(Debug, Deserialize)] pub struct CreateChannelRequest { pub server_id: Option, pub category_id: Option, - pub position: i32, + pub position: Option, pub channel_type: ChannelType, pub name: Option, } @@ -41,10 +41,21 @@ impl From for channel::ActiveModel { Self { server_id: Set(request.server_id), category_id: Set(request.category_id), - position: Set(request.position), + position: Set(request.position.unwrap_or(0)), channel_type: Set(request.channel_type), name: Set(request.name), ..Default::default() } } } + +impl CreateChannelRequest { + pub fn apply_to(self, mut am: channel::ActiveModel) -> channel::ActiveModel { + am.server_id = Set(self.server_id); + am.category_id = Set(self.category_id); + am.position = Set(self.position.unwrap_or(0)); + am.channel_type = Set(self.channel_type); + am.name = Set(self.name); + am + } +} diff --git a/src/interfaces/http/dto/message.rs b/src/interfaces/http/dto/message.rs index f80bea1..50568ff 100644 --- a/src/interfaces/http/dto/message.rs +++ b/src/interfaces/http/dto/message.rs @@ -28,13 +28,19 @@ pub struct CreateMessageRequest { pub content: String, } -impl From for message::ActiveModel { - fn from(request: CreateMessageRequest) -> Self { - Self { - channel_id: Set(request.channel_id), - user_id: Set(Uuid::new_v4()), - content: Set(request.content), +impl CreateMessageRequest { + pub fn into_active_model(self, author_id: Uuid) -> message::ActiveModel { + message::ActiveModel { + channel_id: Set(self.channel_id), + user_id: Set(author_id), + content: Set(self.content), ..Default::default() } } + + pub fn apply_to(self, mut am: message::ActiveModel) -> message::ActiveModel { + am.channel_id = Set(self.channel_id); + am.content = Set(self.content); + am + } } diff --git a/src/interfaces/http/dto/user.rs b/src/interfaces/http/dto/user.rs index bca317e..8b01c4e 100644 --- a/src/interfaces/http/dto/user.rs +++ b/src/interfaces/http/dto/user.rs @@ -7,6 +7,7 @@ use uuid::Uuid; pub struct UserResponse { pub id: Uuid, pub username: String, + pub pub_key: String, } impl From for UserResponse { @@ -14,6 +15,7 @@ impl From for UserResponse { Self { id: model.id, username: model.username, + pub_key: model.pub_key, } } } @@ -31,3 +33,10 @@ impl From for user::ActiveModel { } } } + +impl CreateUserRequest { + pub fn apply_to(self, mut am: user::ActiveModel) -> user::ActiveModel { + am.username = Set(self.username); + am + } +} diff --git a/src/lib.rs b/src/lib.rs index 847ad3f..4654409 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,6 +8,5 @@ pub mod event_bus; pub mod hub; pub mod models; pub mod repositories; -pub mod serializers; pub mod interfaces; diff --git a/src/main.rs b/src/main.rs index d4d88f7..264a4a0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,14 +1,18 @@ use ox_speak_server_lib::app::App; use ox_speak_server_lib::config; - #[tokio::main] async fn main() { - env_logger::init(); + tracing_subscriber::fmt() + .with_env_filter( + tracing_subscriber::EnvFilter::try_from_default_env() + .unwrap_or_else(|_| "ox_speak_server=info,tower_http=info".into()), + ) + .init(); let config = match config::Config::from_file("config.toml") { Ok(config) => config, - Err(e) => panic!("Error loading configuration: {}", e) + Err(e) => panic!("Error loading configuration: {}", e), }; println!("Configuration loaded: {:?}", config); diff --git a/src/network/http/error.rs b/src/network/http/error.rs index d021841..8388962 100644 --- a/src/network/http/error.rs +++ b/src/network/http/error.rs @@ -1,8 +1,8 @@ use axum::http::StatusCode; use axum::response::{IntoResponse, Response}; use axum::Json; -use serde_json::json; use sea_orm::DbErr; +use serde_json::json; #[derive(Debug)] pub enum HTTPError { @@ -10,6 +10,7 @@ pub enum HTTPError { NotFound, BadRequest(String), InternalServerError(String), + Unauthorized, } // Conversion automatique depuis DbErr (erreurs SeaORM) @@ -35,15 +36,20 @@ impl IntoResponse for HTTPError { (StatusCode::INTERNAL_SERVER_ERROR, "Database error") } HTTPError::NotFound => (StatusCode::NOT_FOUND, "Resource not found"), + HTTPError::Unauthorized => (StatusCode::UNAUTHORIZED, "Unauthorized"), HTTPError::BadRequest(msg) => { return (StatusCode::BAD_REQUEST, Json(json!({ "error": msg }))).into_response(); } HTTPError::InternalServerError(msg) => { eprintln!("Internal error: {}", msg); - return (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({ "error": msg }))).into_response(); + return ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(json!({ "error": msg })), + ) + .into_response(); } }; (status, Json(json!({ "error": error_message }))).into_response() } -} \ No newline at end of file +} diff --git a/src/network/http/middleware.rs b/src/network/http/middleware.rs index 81efbac..a33dc43 100644 --- a/src/network/http/middleware.rs +++ b/src/network/http/middleware.rs @@ -1,14 +1,11 @@ -use axum::{ - extract::State, - http::Request, - middleware::Next, - response::Response, -}; +use axum::{extract::State, http::Request, middleware::Next, response::Response}; use std::time::Instant; +use tracing::info; use uuid::Uuid; use crate::app::AppState; use crate::network::http::context::{CurrentUser, RequestContext}; +use crate::utils::auth::verify_jwt; pub async fn context_middleware( State(app_state): State, @@ -22,20 +19,27 @@ pub async fn context_middleware( let method = req.method().clone(); let uri = req.uri().clone(); - // Exemple: récupérer un user depuis un token (pseudo-code) - // Ici je laisse volontairement une logique minimaliste/placeholder. - // Le but: montrer où tu branches ta vraie auth. + // Authentification par JWT let user: Option = { - let _maybe_auth = req - .headers() + req.headers() .get(axum::http::header::AUTHORIZATION) - .and_then(|v| v.to_str().ok()); - - // TODO: vérifier token -> user_id -> charger en DB avec app_state - // Some(CurrentUser { id: ..., username: ... }) - None + .and_then(|v| v.to_str().ok()) + .and_then(|auth_header| { + if auth_header.starts_with("Bearer ") { + Some(&auth_header[7..]) + } else { + None + } + }) + .and_then(|token| verify_jwt(token, &app_state.config.jwt.secret).ok()) + .map(|claims| CurrentUser { + id: claims.user_id, + username: claims.username, + }) }; + let user_id = user.as_ref().map(|u| u.id); + // Injecte le contexte dans la requête req.extensions_mut().insert(RequestContext { request_id, @@ -45,12 +49,14 @@ pub async fn context_middleware( user, }); - println!(">>> Incoming [{}] {} {}", request_id, method, uri); + info!( + request_id = %request_id, + user_id = ?user_id, + method = %method, + uri = %uri, + "Incoming request" + ); // Passe la requête au reste de la stack - let resp = next.run(req).await; - - println!("<<< Response [{}]: {}", request_id, resp.status()); - - resp -} \ No newline at end of file + next.run(req).await +} diff --git a/src/network/http/router.rs b/src/network/http/router.rs index e59e35e..5e1320c 100644 --- a/src/network/http/router.rs +++ b/src/network/http/router.rs @@ -1,14 +1,25 @@ -use std::sync::Arc; use axum::{middleware, Router}; +use tower::ServiceBuilder; +use tower_http::catch_panic::CatchPanicLayer; use tower_http::cors::CorsLayer; +use tower_http::trace::TraceLayer; + use crate::app::AppState; use crate::network::http::middleware::context_middleware; -use crate::network::http::{web, AppRouter}; +use crate::network::http::web; pub fn setup_route(app_state: AppState) -> Router { + let middleware_stack = ServiceBuilder::new() + .layer(CatchPanicLayer::new()) + .layer(TraceLayer::new_for_http()) + .layer(middleware::from_fn_with_state( + app_state.clone(), + context_middleware, + )); + Router::new() .merge(web::setup_route()) - .layer(middleware::from_fn_with_state(app_state.clone(), context_middleware)) + .layer(middleware_stack) .with_state(app_state) .layer(CorsLayer::permissive()) -} \ No newline at end of file +} diff --git a/src/network/http/web/api/auth.rs b/src/network/http/web/api/auth.rs index c25007d..6cdc433 100644 --- a/src/network/http/web/api/auth.rs +++ b/src/network/http/web/api/auth.rs @@ -1,13 +1,53 @@ use crate::app::AppState; use crate::network::http::{AppRouter, HTTPError}; +use crate::utils::auth::create_jwt; use crate::utils::toolbox::ssh_generate_challenge; use axum::extract::State; +use axum::routing::post; use axum::Json; use serde::{Deserialize, Serialize}; -use ssh_key::{Algorithm as SshAlgorithm, PublicKey, Signature}; -fn setup_route() -> AppRouter { +pub fn setup_route() -> AppRouter { AppRouter::new() + .route("/login", post(login)) + .route("/ssh-challenge", post(ssh_challenge)) +} + +#[derive(Deserialize)] +pub struct LoginRequest { + username: String, + password: String, +} + +#[derive(Serialize)] +pub struct LoginResponse { + token: String, + username: String, +} + +pub async fn login( + State(state): State, + Json(payload): Json, +) -> Result, HTTPError> { + let user = state + .repositories + .user + .check_password(payload.username.clone(), payload.password) + .await + .map_err(|_| HTTPError::Unauthorized)?; + + let token = create_jwt( + user.id, + &user.username, + &state.config.jwt.secret, + state.config.jwt.expiration, + ) + .map_err(|e| HTTPError::InternalServerError("Failed to generate token".to_string()))?; + + Ok(Json(LoginResponse { + token, + username: user.username, + })) } #[derive(Deserialize)] @@ -24,82 +64,14 @@ pub async fn ssh_challenge( State(state): State, Json(payload): Json, ) -> Result, HTTPError> { - log::info!( - "POST /auth/ssh-challenge - Challenge request for user: {}", - payload.username - ); - - let user = state + let _user = state .repositories .user .get_by_username(payload.username.clone()) .await? - .ok_or_else(|| { - log::warn!( - "POST /auth/ssh-challenge - User not found: {}", - payload.username - ); - HTTPError::NotFound - })?; + .ok_or_else(|| HTTPError::NotFound)?; let challenge = ssh_generate_challenge(32); - log::info!( - "POST /auth/ssh-challenge - Challenge generated for user: {}", - payload.username - ); - - // todo : stocker le challenge dans AppState - // bien penser à ajouter un délai d'expiration - // songer à mettre une session id ? - // Peut être que l'utilisateur se connectera depuis différent device - // state.store_challenge(&payload.username, challenge.clone()) Ok(Json(SshChallengeResponse { challenge })) } - -#[derive(Deserialize)] -enum SignatureAlgorithm { - #[serde(rename = "rsa")] - Rsa, - #[serde(rename = "ed25519")] - Ed25519, - #[serde(rename = "ecdsa")] - Ecdsa, -} - -impl SignatureAlgorithm { - fn to_ssh_algorithm(&self) -> SshAlgorithm { - match self { - SignatureAlgorithm::Rsa => SshAlgorithm::Rsa { hash: None }, - SignatureAlgorithm::Ed25519 => SshAlgorithm::Ed25519, - SignatureAlgorithm::Ecdsa => SshAlgorithm::Ecdsa { - curve: ssh_key::EcdsaCurve::NistP256, - }, - } - } -} - -#[derive(Deserialize)] -struct SshVerifyRequest { - username: String, - signature: String, - algorithm: SignatureAlgorithm, -} - -#[derive(Serialize)] -struct SshVerifyResponse { - // todo : remplir avec la réponse jwt - à établir après la construction jwt -} - -pub async fn ssh_verify() {} - -#[derive(Deserialize)] -pub struct LoginRequest { - username: String, - password: String, -} - -#[derive(Serialize)] -pub struct SshChallengeResponse { - challenge: String, -} diff --git a/src/network/http/web/api/category.rs b/src/network/http/web/api/category.rs index 0d43d37..98729d0 100644 --- a/src/network/http/web/api/category.rs +++ b/src/network/http/web/api/category.rs @@ -3,9 +3,8 @@ use crate::interfaces::http::dto::category::{CategoryResponse, CreateCategoryReq use crate::models::category; use crate::network::http::RequestContext; use crate::network::http::{AppRouter, HTTPError}; -use crate::serializers::CategorySerializer; use axum::extract::{Path, Query, State}; -use axum::http::{Extensions, StatusCode}; +use axum::http::StatusCode; use axum::routing::{delete, get, post, put}; use axum::{Extension, Json}; use sea_orm::{ActiveModelTrait, EntityTrait, IntoActiveModel}; @@ -31,8 +30,6 @@ pub async fn category_list( Extension(_ctx): Extension, Query(query): Query, ) -> Result>, HTTPError> { - log::info!("GET /categories/ - Query: server_id={:?}", query.server_id); - let categories = if let Some(server_id) = query.server_id { app_state .repositories @@ -43,7 +40,6 @@ pub async fn category_list( app_state.repositories.category.get_all().await? }; - log::info!("GET /categories/ - Found {} categories", categories.len()); Ok(Json( categories.into_iter().map(CategoryResponse::from).collect(), )) @@ -53,19 +49,13 @@ pub async fn category_detail( State(app_state): State, Path(id): Path, ) -> Result, HTTPError> { - log::info!("GET /categories/{id}/ - Fetching category: {}", id); - let category = app_state .repositories .category .get_by_id(id) .await? - .ok_or_else(|| { - log::warn!("GET /categories/{id}/ - Category not found: {}", id); - HTTPError::NotFound - })?; + .ok_or_else(|| HTTPError::NotFound)?; - log::info!("GET /categories/{id}/ - Category found: {}", id); Ok(Json(CategoryResponse::from(category))) } @@ -73,18 +63,9 @@ pub async fn category_create( State(app_state): State, Json(serializer): Json, ) -> Result, HTTPError> { - log::info!( - "POST /categories/ - Creating category: {:?}", - serializer.name - ); - let active: category::ActiveModel = serializer.into(); let category: category::Model = active.insert(app_state.db.get_connection()).await?; - log::info!( - "POST /categories/ - Category created with id: {}", - category.id - ); Ok(Json(CategoryResponse::from(category))) } @@ -93,15 +74,10 @@ pub async fn category_update( Path(id): Path, Json(serializer): Json, ) -> Result, HTTPError> { - log::info!("PUT /categories/{id}/ - Updating category: {}", id); - let category = category::Entity::find_by_id(id) .one(app_state.db.get_connection()) .await? - .ok_or_else(|| { - log::warn!("PUT /categories/{id}/ - Category not found: {}", id); - HTTPError::NotFound - })?; + .ok_or_else(|| HTTPError::NotFound)?; let active = category.into_active_model(); @@ -111,7 +87,6 @@ pub async fn category_update( .update(app_state.db.get_connection()) .await?; - log::info!("PUT /categories/{id}/ - Category updated: {}", id); Ok(Json(CategoryResponse::from(category))) } @@ -119,17 +94,13 @@ pub async fn category_delete( State(app_state): State, Path(id): Path, ) -> Result { - log::info!("DELETE /categories/{id}/ - Deleting category: {}", id); - let result = category::Entity::delete_by_id(id) .exec(app_state.db.get_connection()) .await?; if result.rows_affected == 0 { - log::warn!("DELETE /categories/{id}/ - Category not found: {}", id); Err(HTTPError::NotFound) } else { - log::info!("DELETE /categories/{id}/ - Category deleted: {}", id); Ok(StatusCode::NO_CONTENT) } } diff --git a/src/network/http/web/api/channel.rs b/src/network/http/web/api/channel.rs index ffe897d..e4869c3 100644 --- a/src/network/http/web/api/channel.rs +++ b/src/network/http/web/api/channel.rs @@ -1,13 +1,13 @@ -use axum::Json; +use crate::app::AppState; +use crate::interfaces::http::dto::channel::{ChannelResponse, CreateChannelRequest}; +use crate::models::channel; +use crate::network::http::{AppRouter, HTTPError}; use axum::extract::{Path, State}; use axum::http::StatusCode; use axum::routing::{delete, get, post, put}; +use axum::Json; use sea_orm::{ActiveModelTrait, EntityTrait, IntoActiveModel}; use uuid::Uuid; -use crate::app::AppState; -use crate::models::channel; -use crate::network::http::{AppRouter, HTTPError}; -use crate::serializers::ChannelSerializer; pub fn setup_route() -> AppRouter { AppRouter::new() @@ -19,42 +19,44 @@ pub fn setup_route() -> AppRouter { } pub async fn channel_list( - State(app_state): State -) -> Result>, HTTPError> { + State(app_state): State, +) -> Result>, HTTPError> { let channels = channel::Entity::find() .all(app_state.db.get_connection()) .await?; - Ok(Json(channels.into_iter().map(ChannelSerializer::from).collect())) + Ok(Json( + channels.into_iter().map(ChannelResponse::from).collect(), + )) } pub async fn channel_detail( State(app_state): State, - Path(id): Path -) -> Result, HTTPError> { + Path(id): Path, +) -> Result, HTTPError> { let channel = channel::Entity::find_by_id(id) .one(app_state.db.get_connection()) .await? .ok_or(HTTPError::NotFound)?; - Ok(Json(ChannelSerializer::from(channel))) + Ok(Json(ChannelResponse::from(channel))) } pub async fn channel_create( State(app_state): State, - Json(serializer): Json -) -> Result, HTTPError> { - let active = serializer.into_active_model(); + Json(dto): Json, +) -> Result, HTTPError> { + let active: channel::ActiveModel = dto.into(); let channel: channel::Model = active.insert(app_state.db.get_connection()).await?; - Ok(Json(ChannelSerializer::from(channel))) + Ok(Json(ChannelResponse::from(channel))) } pub async fn channel_update( State(app_state): State, Path(id): Path, - Json(serializer): Json, -) -> Result, HTTPError> { + Json(dto): Json, +) -> Result, HTTPError> { let channel = channel::Entity::find_by_id(id) .one(app_state.db.get_connection()) .await? @@ -62,16 +64,17 @@ pub async fn channel_update( let active = channel.into_active_model(); - let channel: channel::Model = serializer.apply_to_active_model(active) + let channel: channel::Model = dto + .apply_to(active) .update(app_state.db.get_connection()) .await?; - Ok(Json(ChannelSerializer::from(channel))) + Ok(Json(ChannelResponse::from(channel))) } pub async fn channel_delete( State(app_state): State, - Path(id): Path + Path(id): Path, ) -> Result { let result = channel::Entity::delete_by_id(id) .exec(app_state.db.get_connection()) @@ -82,4 +85,4 @@ pub async fn channel_delete( } else { Err(HTTPError::NotFound) } -} \ No newline at end of file +} diff --git a/src/network/http/web/api/message.rs b/src/network/http/web/api/message.rs index e996997..75b91d7 100644 --- a/src/network/http/web/api/message.rs +++ b/src/network/http/web/api/message.rs @@ -1,13 +1,13 @@ -use axum::Json; +use crate::app::AppState; +use crate::interfaces::http::dto::message::{CreateMessageRequest, MessageResponse}; +use crate::models::message; +use crate::network::http::{AppRouter, HTTPError, RequestContext}; use axum::extract::{Path, State}; use axum::http::StatusCode; use axum::routing::{delete, get, post, put}; +use axum::{Extension, Json}; use sea_orm::{ActiveModelTrait, EntityTrait, IntoActiveModel}; use uuid::Uuid; -use crate::app::AppState; -use crate::models::message; -use crate::network::http::{AppRouter, HTTPError}; -use crate::serializers::MessageSerializer; pub fn setup_route() -> AppRouter { AppRouter::new() @@ -19,58 +19,63 @@ pub fn setup_route() -> AppRouter { } pub async fn message_list( - State(app_state): State -) -> Result>, HTTPError> { + State(app_state): State, +) -> Result>, HTTPError> { let messages = message::Entity::find() .all(app_state.db.get_connection()) .await?; - Ok(Json(messages.into_iter().map(MessageSerializer::from).collect())) + Ok(Json( + messages.into_iter().map(MessageResponse::from).collect(), + )) } pub async fn message_detail( State(app_state): State, - Path(id): Path -) -> Result, HTTPError> { + Path(id): Path, +) -> Result, HTTPError> { let message = message::Entity::find_by_id(id) .one(app_state.db.get_connection()) .await? .ok_or(HTTPError::NotFound)?; - Ok(Json(MessageSerializer::from(message))) + Ok(Json(MessageResponse::from(message))) } pub async fn message_create( State(app_state): State, - Json(serializer): Json -) -> Result, HTTPError> { - let active = serializer.into_active_model(); + Extension(ctx): Extension, + Json(dto): Json, +) -> Result, HTTPError> { + let author_id = ctx.user.map(|u| u.id).unwrap_or_else(Uuid::new_v4); + let active = dto.into_active_model(author_id); let message: message::Model = active.insert(app_state.db.get_connection()).await?; - Ok(Json(MessageSerializer::from(message))) + Ok(Json(MessageResponse::from(message))) } pub async fn message_update( State(app_state): State, Path(id): Path, - Json(serializer): Json, -) -> Result, HTTPError> { + Json(dto): Json, +) -> Result, HTTPError> { let message = message::Entity::find_by_id(id) .one(app_state.db.get_connection()) .await? .ok_or(HTTPError::NotFound)?; let active = message.into_active_model(); - let message: message::Model = serializer.apply_to_active_model(active) + let message: message::Model = dto + .apply_to(active) .update(app_state.db.get_connection()) .await?; - Ok(Json(MessageSerializer::from(message))) + Ok(Json(MessageResponse::from(message))) } pub async fn message_delete( State(app_state): State, - Path(id): Path + Path(id): Path, ) -> Result { let result = message::Entity::delete_by_id(id) .exec(app_state.db.get_connection()) @@ -81,4 +86,4 @@ pub async fn message_delete( } else { Ok(StatusCode::NO_CONTENT) } -} \ No newline at end of file +} diff --git a/src/network/http/web/api/mod.rs b/src/network/http/web/api/mod.rs index bea5b0a..6528ad9 100644 --- a/src/network/http/web/api/mod.rs +++ b/src/network/http/web/api/mod.rs @@ -1,18 +1,18 @@ use crate::network::http::AppRouter; +mod auth; mod category; mod channel; mod message; mod server; mod user; -mod auth; pub fn setup_route() -> AppRouter { - AppRouter::new() .nest("/category", category::setup_route()) .nest("/channel", channel::setup_route()) .nest("/message", message::setup_route()) .nest("/server", server::setup_route()) .nest("/user", user::setup_route()) -} \ No newline at end of file + .nest("/auth", auth::setup_route()) +} diff --git a/src/network/http/web/api/user.rs b/src/network/http/web/api/user.rs index d2bbbcc..c363955 100644 --- a/src/network/http/web/api/user.rs +++ b/src/network/http/web/api/user.rs @@ -1,42 +1,48 @@ -use axum::{Extension, Json}; +use crate::app::AppState; +use crate::interfaces::http::dto::user::UserResponse; +use crate::network::http::{AppRouter, HTTPError}; use axum::extract::{Path, State}; use axum::routing::get; +use axum::{Extension, Json}; use uuid::Uuid; -use crate::app::AppState; -use crate::network::http::{AppRouter, HTTPError}; -use crate::serializers::UserSerializer; pub fn setup_route() -> AppRouter { AppRouter::new() - .route("/users/", get(user_list)) - .route("/users/{id}/", get(user_detail)) + .route("/me", get(get_me)) + .route("/", get(user_list)) + .route("/{id}", get(user_detail)) +} + +pub async fn get_me( + Extension(ctx): Extension, +) -> Result, HTTPError> { + let user = ctx.user.ok_or(HTTPError::Unauthorized)?; + Ok(Json(UserResponse { + id: user.id, + username: user.username, + pub_key: "".to_string(), // On peut laisser vide ou charger en DB si besoin + })) } pub async fn user_list( State(app_state): State, - Extension(_ctx): Extension -) -> Result>, HTTPError> { - log::info!("GET /users/ - Fetching user list"); - + Extension(_ctx): Extension, +) -> Result>, HTTPError> { let users = app_state.repositories.user.get_all().await?; - log::info!("GET /users/ - Found {} users", users.len()); - Ok(Json(users.into_iter().map(UserSerializer::from).collect())) + Ok(Json(users.into_iter().map(UserResponse::from).collect())) } pub async fn user_detail( State(app_state): State, - Path(id): Path -) -> Result, HTTPError> { - log::info!("GET /users/{id}/ - Fetching user with id: {}", id); + Path(id): Path, +) -> Result, HTTPError> { + let user = app_state + .repositories + .user + .get_by_id(id) + .await? + .ok_or_else(|| HTTPError::NotFound)?; - let user = app_state.repositories.user - .get_by_id(id).await? - .ok_or_else(|| { - log::warn!("GET /users/{id}/ - User not found: {}", id); - HTTPError::NotFound - })?; - - log::info!("GET /users/{id}/ - User found: {}", id); - Ok(Json(UserSerializer::from(user))) + Ok(Json(UserResponse::from(user))) } diff --git a/src/serializers/category.rs b/src/serializers/category.rs deleted file mode 100644 index b2450d8..0000000 --- a/src/serializers/category.rs +++ /dev/null @@ -1,48 +0,0 @@ -use serde::{Serialize, Deserialize}; -use sea_orm::ActiveValue::Set; -use uuid::Uuid; -use crate::models::category; - -#[derive(Serialize, Deserialize)] -pub struct CategorySerializer { - #[serde(skip_deserializing)] - pub id: Option, - - pub server_id: Uuid, - - pub name: String, - - #[serde(skip_deserializing)] - pub created_at: Option, - - #[serde(skip_deserializing)] - pub updated_at: Option, -} - -impl From for CategorySerializer { - fn from(model: category::Model) -> Self { - Self { - id: Some(model.id), - server_id: model.server_id, - name: model.name, - created_at: Some(model.created_at.to_string()), - updated_at: Some(model.updated_at.to_string()), - } - } -} - -impl CategorySerializer { - pub fn into_active_model(self) -> category::ActiveModel { - category::ActiveModel { - server_id: Set(self.server_id), - name: Set(self.name), - ..Default::default() - } - } - - pub fn apply_to_active_model(self, mut active_model: category::ActiveModel) -> category::ActiveModel { - active_model.server_id = Set(self.server_id); - active_model.name = Set(self.name); - active_model - } -} diff --git a/src/serializers/channel.rs b/src/serializers/channel.rs deleted file mode 100644 index 59a4cd1..0000000 --- a/src/serializers/channel.rs +++ /dev/null @@ -1,58 +0,0 @@ -use serde::{Serialize, Deserialize}; -use sea_orm::ActiveValue::Set; -use uuid::Uuid; -use crate::models::channel; -use crate::models::channel::ChannelType; - -#[derive(Serialize, Deserialize)] -pub struct ChannelSerializer { - #[serde(skip_deserializing)] - pub id: Option, - pub server_id: Option, - pub category_id: Option, - pub name: Option, - pub position: Option, - pub channel_type: ChannelType, - - #[serde(skip_deserializing)] - pub created_at: Option, - #[serde(skip_deserializing)] - pub updated_at: Option, -} - -impl From for ChannelSerializer { - fn from(model: channel::Model) -> Self { - Self { - id: Some(model.id), - server_id: model.server_id, - category_id: model.category_id, - name: model.name, - position: Some(model.position), - channel_type: model.channel_type, - created_at: Some(model.created_at.to_string()), - updated_at: Some(model.updated_at.to_string()), - } - } -} - -impl ChannelSerializer { - pub fn into_active_model(self) -> channel::ActiveModel { - channel::ActiveModel { - server_id: Set(self.server_id), - category_id: Set(self.category_id), - name: Set(self.name), - position: Set(self.position.unwrap_or(0)), - channel_type: Set(self.channel_type), - ..Default::default() - } - } - - pub fn apply_to_active_model(self, mut active_model: channel::ActiveModel) -> channel::ActiveModel { - active_model.server_id = Set(self.server_id); - active_model.category_id = Set(self.category_id); - active_model.name = Set(self.name); - active_model.position = Set(self.position.unwrap_or(0)); - active_model.channel_type = Set(self.channel_type); - active_model - } -} \ No newline at end of file diff --git a/src/serializers/message.rs b/src/serializers/message.rs deleted file mode 100644 index 2ef7d74..0000000 --- a/src/serializers/message.rs +++ /dev/null @@ -1,49 +0,0 @@ -use serde::{Serialize, Deserialize}; -use sea_orm::ActiveValue::Set; -use uuid::Uuid; -use crate::models::message; - -#[derive(Serialize, Deserialize)] -pub struct MessageSerializer { - #[serde(skip_deserializing)] - pub id: Option, - pub channel_id: Option, - pub author_id: Option, - pub content: String, - - #[serde(skip_deserializing)] - pub created_at: Option, - #[serde(skip_deserializing)] - pub updated_at: Option, -} - -impl From for MessageSerializer { - fn from(model: message::Model) -> Self { - Self { - id: Some(model.id), - channel_id: Some(model.channel_id), - author_id: Some(model.user_id), - content: model.content, - created_at: Some(model.created_at.to_string()), - updated_at: Some(model.edited_at.unwrap_or(model.created_at).to_string()), - } - } -} - -impl MessageSerializer { - pub fn into_active_model(self) -> message::ActiveModel { - message::ActiveModel { - channel_id: Set(self.channel_id.unwrap()), - user_id: Set(self.author_id.unwrap()), - content: Set(self.content), - ..Default::default() - } - } - - pub fn apply_to_active_model(self, mut active_model: message::ActiveModel) -> message::ActiveModel { - active_model.channel_id = Set(self.channel_id.unwrap()); - active_model.user_id = Set(self.author_id.unwrap()); - active_model.content = Set(self.content); - active_model - } -} \ No newline at end of file diff --git a/src/serializers/mod.rs b/src/serializers/mod.rs deleted file mode 100644 index 8ebbbd8..0000000 --- a/src/serializers/mod.rs +++ /dev/null @@ -1,21 +0,0 @@ -mod server; -mod category; -mod channel; -mod message; -mod user; - -trait BaseSerializer -where - M: ActiveModelTrait, -{ - fn into_active_model(self) -> M; - - fn apply_to_active_model(self, active_model: M) -> M; -} - -use sea_orm::ActiveModelTrait; -pub use server::*; -pub use category::*; -pub use channel::*; -pub use message::*; -pub use user::*; \ No newline at end of file diff --git a/src/serializers/server.rs b/src/serializers/server.rs deleted file mode 100644 index cae8d2e..0000000 --- a/src/serializers/server.rs +++ /dev/null @@ -1,91 +0,0 @@ -use serde::{Serialize, Deserialize}; -use sea_orm::ActiveValue::Set; -use uuid::Uuid; -use crate::models::server; -use crate::repositories::types::{ServerExplorerItem, ServerTree}; -use super::{CategorySerializer, ChannelSerializer}; - -#[derive(Serialize, Deserialize)] -pub struct ServerSerializer { - #[serde(skip_deserializing)] - pub id: Option, - - pub name: String, - - #[serde(skip_serializing)] - pub password: Option, - - #[serde(skip_deserializing)] - pub created_at: Option, - - #[serde(skip_deserializing)] - pub updated_at: Option, -} - -// On part du Model (données « propres » venant de la BDD), -// pas de l'ActiveModel (qui contient des ActiveValue). -impl From for ServerSerializer { - fn from(model: server::Model) -> Self { - Self { - id: Some(model.id), - name: model.name, - password: model.password, - created_at: Some(model.created_at.to_string()), - updated_at: Some(model.updated_at.to_string()), - } - } -} - -impl ServerSerializer { - /// équivalent de `create()` d’un serializer DRF - pub fn into_active_model(self) -> server::ActiveModel { - server::ActiveModel { - name: Set(self.name), - // champ Option -> tu peux le passer directement à Set(...) - password: Set(self.password), - ..Default::default() - } - } - - /// équivalent de `update(instance, validated_data)` en DRF - pub fn apply_to_active_model(self, mut active_model: server::ActiveModel) -> server::ActiveModel { - active_model.name = Set(self.name); - active_model.password = Set(self.password); - active_model - } -} - -#[derive(Serialize)] -#[serde(tag = "type", rename_all = "snake_case")] -pub enum TreeItemSerializer { - Category { - #[serde(flatten)] - category: CategorySerializer, - channels: Vec - }, - Channel(ChannelSerializer) -} - -#[derive(Serialize)] -#[serde(transparent)] -pub struct ServerTreeSerializer { - pub items: Vec, -} - -impl From for ServerTreeSerializer { - fn from(layout: ServerTree) -> Self { - Self { - items: layout.items.into_iter().map(|item| match item { - ServerExplorerItem::Category(cat, chans) => { - TreeItemSerializer::Category { - category: CategorySerializer::from(cat), - channels: chans.into_iter().map(ChannelSerializer::from).collect(), - } - }, - ServerExplorerItem::Channel(chan) => { - TreeItemSerializer::Channel(ChannelSerializer::from(chan)) - } - }).collect(), - } - } -} diff --git a/src/serializers/user.rs b/src/serializers/user.rs deleted file mode 100644 index 2bfa4db..0000000 --- a/src/serializers/user.rs +++ /dev/null @@ -1,44 +0,0 @@ -use sea_orm::ActiveValue::Set; -use serde::{Deserialize, Serialize}; -use uuid::Uuid; -use crate::models::{category, user}; - -#[derive(Serialize, Deserialize)] -pub struct UserSerializer { - #[serde(skip_deserializing)] - pub id: Option, - pub username: String, - pub pub_key: String, - - #[serde(skip_deserializing)] - pub created_at: String, - #[serde(skip_deserializing)] - pub updated_at: String, -} - -impl From for UserSerializer { - fn from(model: user::Model) -> Self { - Self { - id: Some(model.id), - username: model.username, - pub_key: model.pub_key, - created_at: model.created_at.to_string(), - updated_at: model.updated_at.to_string(), - } - } -} - -impl UserSerializer { - pub fn into_active_model(self) -> user::ActiveModel { - user::ActiveModel { - username: Set(self.username), - pub_key: Set(self.pub_key), - ..Default::default() - } - } - - pub fn apply_to_active_model(self, mut active_model: user::ActiveModel) -> user::ActiveModel { active_model.username = Set(self.username); - active_model.pub_key = Set(self.pub_key); - active_model - } -} \ No newline at end of file diff --git a/src/utils/auth.rs b/src/utils/auth.rs new file mode 100644 index 0000000..a9b71f1 --- /dev/null +++ b/src/utils/auth.rs @@ -0,0 +1,48 @@ +use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation}; +use serde::{Deserialize, Serialize}; +use std::time::{SystemTime, UNIX_EPOCH}; +use uuid::Uuid; + +#[derive(Debug, Serialize, Deserialize)] +pub struct Claims { + pub user_id: Uuid, // User ID + pub expire_at: usize, // Expiration time + pub created_at: usize, // Issued at + pub username: String, +} + +pub fn create_jwt( + user_id: Uuid, + username: &str, + secret: &str, + expiration_seconds: u64, +) -> Result { + let now = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Time went backwards") + .as_secs(); + + let claims = Claims { + user_id: user_id, + expire_at: (now + expiration_seconds) as usize, + created_at: now as usize, + username: username.to_string(), + }; + + encode( + &Header::default(), + &claims, + &EncodingKey::from_secret(secret.as_ref()), + ) +} + +pub fn verify_jwt(token: &str, secret: &str) -> Result { + let validation = Validation::default(); + let token_data = decode::( + token, + &DecodingKey::from_secret(secret.as_ref()), + &validation, + )?; + + Ok(token_data.claims) +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index edd580d..3fd5a8d 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,3 +1,4 @@ +pub mod auth; pub mod password; pub mod ssh_auth; pub mod toolbox;