From e9d77fbd056016f53b5bdc0160cb3d7f4d6d16d5 Mon Sep 17 00:00:00 2001 From: Nell Date: Sun, 21 Jun 2026 19:11:23 +0200 Subject: [PATCH] Init --- Cargo.lock | 1 + Cargo.toml | 1 + frontend/src/stores/auth.ts | 5 ++++ frontend/src/stores/gateway.ts | 18 +++++++++-- frontend/src/stores/server.ts | 4 ++- src/http/middleware.rs | 38 +++++++++++++---------- src/models/user.rs | 16 +++++++++- src/repositories/user.rs | 21 ++++++++++--- src/routes/gateway/handlers.rs | 55 ++++------------------------------ src/routes/gateway/mod.rs | 8 +++-- src/routes/mod.rs | 6 ++-- 11 files changed, 96 insertions(+), 77 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7f7f815..149be3d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2350,6 +2350,7 @@ dependencies = [ "chrono", "config", "event_bus", + "form_urlencoded", "futures-util", "jsonwebtoken", "log", diff --git a/Cargo.toml b/Cargo.toml index 732c7e7..4e09dc0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,3 +38,4 @@ validator = { version = "0.20.0", features = ["derive"] } async-trait = "0.1.89" anyhow = "1.0.102" futures-util = "0.3" +form_urlencoded = "1.2.2" diff --git a/frontend/src/stores/auth.ts b/frontend/src/stores/auth.ts index 444c835..4b12a20 100644 --- a/frontend/src/stores/auth.ts +++ b/frontend/src/stores/auth.ts @@ -1,5 +1,6 @@ import {defineStore} from 'pinia' import {useApi} from "@/composables/useApi"; +import {useGatewayStore} from "@/stores/gateway.ts"; export interface User { id: string @@ -36,6 +37,10 @@ export const useAuthStore = defineStore('auth', { if (response.ok) { const data = await response.json() this.user = data.user + + // Initialisation du gateway + const gatewayStore = useGatewayStore() + await gatewayStore.connect() } else { this.logout() } diff --git a/frontend/src/stores/gateway.ts b/frontend/src/stores/gateway.ts index 7f210b6..a40655b 100644 --- a/frontend/src/stores/gateway.ts +++ b/frontend/src/stores/gateway.ts @@ -1,4 +1,6 @@ import {defineStore} from 'pinia'; +import {useAppStore} from "@/stores/app.ts"; +import {useAuthStore} from "@/stores/auth.ts"; type GatewayStatus = 'disconnected' | 'connecting' | 'connected' | 'error' @@ -15,9 +17,21 @@ export const useGatewayStore = defineStore('gateway', { return } - this.status = 'connecting' + const appStore = useAppStore() + const authStore = useAuthStore() - const wsUrl = `ws://localhost:3000/ws` + + this.status = 'connecting' + const token = authStore.token + if (!token) { + this.status = 'error' + return + } + + const apiUri = appStore.baseurl ? new URL(appStore.baseurl) : new URL(window.location.href) + const wsProtocol = apiUri.protocol === 'https:' ? 'wss:' : 'ws:' + + const wsUrl = `${wsProtocol}//${apiUri.host}/ws/gateway?token=${encodeURIComponent(token)}` const socket = new WebSocket(wsUrl) socket.onopen = () => { diff --git a/frontend/src/stores/server.ts b/frontend/src/stores/server.ts index b53262a..c0811df 100644 --- a/frontend/src/stores/server.ts +++ b/frontend/src/stores/server.ts @@ -1,6 +1,8 @@ import {defineStore} from "pinia"; export const useServerStore = defineStore("server", { - state: () => ({}), + state: () => ({ + servers: [] + }), actions: {} }); \ No newline at end of file diff --git a/src/http/middleware.rs b/src/http/middleware.rs index 4596292..3f5e9c2 100644 --- a/src/http/middleware.rs +++ b/src/http/middleware.rs @@ -91,30 +91,38 @@ pub async fn auth_middleware( mut req: Request, next: Next, ) -> Response { - // Extraction du JWT depuis le header Authorization - let user: Option = match req + // Extraction du JWT : d'abord via le header Authorization, sinon via la query string "token" + let token = req .headers() .get(header::AUTHORIZATION) .and_then(|v| v.to_str().ok()) .and_then(|auth_header| { if auth_header.starts_with("Bearer ") { - Some(&auth_header[7..]) + Some(auth_header[7..].to_string()) } else { None } }) - .and_then(|token| verify_jwt(token, &app_state.config.jwt.secret).ok()) - { - Some(claims) => app_state - .repositories - .user - .get_by_id(claims.user_id) - .await - .ok() - .flatten() - .map(CurrentUser), - None => None, - }; + .or_else(|| { + req.uri().query().and_then(|q| { + form_urlencoded::parse(q.as_bytes()) + .find(|(key, _)| key == "token") + .map(|(_, value)| value.into_owned()) + }) + }); + + let user: Option = + match token.and_then(|t| verify_jwt(&t, &app_state.config.jwt.secret).ok()) { + Some(claims) => app_state + .repositories + .user + .get_by_id(claims.user_id) + .await + .ok() + .flatten() + .map(CurrentUser), + None => None, + }; // Mise à jour du RequestContext existant if let Some(user) = &user { diff --git a/src/models/user.rs b/src/models/user.rs index 8bd74b8..f2a04c2 100644 --- a/src/models/user.rs +++ b/src/models/user.rs @@ -4,7 +4,7 @@ use sea_orm::entity::prelude::*; use sea_orm::prelude::async_trait::async_trait; use sea_orm::Set; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, PartialEq, DeriveEntityModel, Eq)] #[sea_orm(table_name = "user")] pub struct Model { #[sea_orm(primary_key, auto_increment = false)] @@ -18,6 +18,20 @@ pub struct Model { pub is_superuser: bool, } +impl std::fmt::Debug for Model { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("User") + .field("id", &self.id) + .field("username", &self.username) + .field("password", &"") + .field("pub_key", &self.pub_key) + .field("created_at", &self.created_at) + .field("updated_at", &self.updated_at) + .field("is_superuser", &self.is_superuser) + .finish() + } +} + #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { #[sea_orm(has_many = "super::channel_user::Entity")] diff --git a/src/repositories/user.rs b/src/repositories/user.rs index ddadafc..20ed70d 100644 --- a/src/repositories/user.rs +++ b/src/repositories/user.rs @@ -34,8 +34,15 @@ impl UserRepository { pub async fn check_password(&self, username: &str, password: &str) -> AnyResult { let user = self.get_by_username(username.to_string()).await?; if let Some(user) = user { - let password_ok = password::verify_password(password, user.password.as_str()) - .map_err(|e| anyhow::anyhow!("Password hashing failed: {}", e))?; + let password_to_verify = password.to_string(); + let hash_to_verify = user.password.clone(); + + let password_ok = tokio::task::spawn_blocking(move || { + password::verify_password(&password_to_verify, &hash_to_verify) + }) + .await + .map_err(|e| anyhow::anyhow!("Join error: {}", e))? + .map_err(|e| anyhow::anyhow!("Password hashing failed: {}", e))?; if password_ok { return Ok(user); @@ -63,8 +70,14 @@ impl UserRepository { .ok_or_else(|| anyhow::anyhow!("User not found"))?; let mut active = user.into_active_model(); - let password = password::hash_password(&password) - .map_err(|e| anyhow::anyhow!("Password hashing failed: {}", e))?; + let password_to_hash = password.clone(); + + let password = + tokio::task::spawn_blocking(move || password::hash_password(&password_to_hash)) + .await + .map_err(|e| anyhow::anyhow!("Join error: {}", e))? + .map_err(|e| anyhow::anyhow!("Password hashing failed: {}", e))?; + active.password = Set(password); let user = self.update(active).await?; diff --git a/src/routes/gateway/handlers.rs b/src/routes/gateway/handlers.rs index 818f959..11de1ab 100644 --- a/src/routes/gateway/handlers.rs +++ b/src/routes/gateway/handlers.rs @@ -1,4 +1,3 @@ -use crate::auth::token::verify_jwt; use crate::core::AppState; use crate::http::context::CurrentUser; use crate::models::user::Model as User; @@ -13,7 +12,6 @@ use axum::{ use futures_util::{sink::SinkExt, stream::StreamExt}; use serde::Deserialize; use tokio::sync::mpsc; -use uuid::Uuid; #[derive(Deserialize)] pub struct WsQuery { @@ -49,9 +47,6 @@ async fn handle_socket(socket: WebSocket, state: AppState, user: User) { client.on_connect().await; state.gateway.add_client(client.clone()); - // // Enregistrement du client (Connect) - // on_connect(user_id, tx, &state).await; - // // Task pour envoyer les messages du canal mpsc vers le WebSocket let mut send_task = tokio::spawn(async move { while let Some(message) = rx.recv().await { @@ -60,7 +55,7 @@ async fn handle_socket(socket: WebSocket, state: AppState, user: User) { } } }); - // + // Task pour recevoir les messages du WebSocket let client_clone = client.clone(); let state_clone = state.clone(); @@ -69,54 +64,14 @@ async fn handle_socket(socket: WebSocket, state: AppState, user: User) { client_clone.on_message(message).await; } }); - // + // Attente de la fin d'une des tâches (déconnexion) tokio::select! { _ = (&mut send_task) => recv_task.abort(), _ = (&mut recv_task) => send_task.abort(), }; - // + + state.gateway.remove_client(client.clone()); // // Déconnexion (Disconnect) - // on_disconnect(user_id, &state).await; -} - -pub async fn on_connect(user_id: Uuid, tx: mpsc::UnboundedSender, state: &AppState) { - tracing::info!("Client connected: {}", user_id); - let mut clients = state - .gateway - .clients - .write() - .expect("Failed to lock clients for writing"); - clients.insert(user_id, GatewayClient { user_id, tx }); -} - -pub async fn on_disconnect(user_id: Uuid, state: &AppState) { - tracing::info!("Client disconnected: {}", user_id); - let mut clients = state - .gateway - .clients - .write() - .expect("Failed to lock clients for writing"); - clients.remove(&user_id); -} - -pub async fn on_message(user_id: Uuid, message: Message, _state: &AppState) { - tracing::debug!("Message received from {}: {:?}", user_id, message); - - // Exemple d'utilisation de l'état/repositories - // let user_opt = state.repositories.user.get_by_id(user_id).await.ok().flatten(); - - match message { - Message::Text(text) => { - tracing::info!("Received text from {}: {}", user_id, text); - // Logique de dispatch ou de traitement ici - } - Message::Binary(_) => { - tracing::info!("Received binary from {}", user_id); - } - Message::Close(_) => { - tracing::info!("Received close from {}", user_id); - } - _ => {} - } + client.on_disconnect().await; } diff --git a/src/routes/gateway/mod.rs b/src/routes/gateway/mod.rs index 09105d0..1be3a30 100644 --- a/src/routes/gateway/mod.rs +++ b/src/routes/gateway/mod.rs @@ -49,9 +49,13 @@ impl GatewayClient { } } - async fn on_connect(&self) {} + async fn on_connect(&self) { + tracing::info!("Client connected: {:?}", self.user); + } - async fn on_disconnect(&self) {} + async fn on_disconnect(&self) { + tracing::info!("Client disconnected: {:?}", self.user); + } async fn on_message(&self, message: Message) { match message { diff --git a/src/routes/mod.rs b/src/routes/mod.rs index 5c73551..098ba3f 100644 --- a/src/routes/mod.rs +++ b/src/routes/mod.rs @@ -31,10 +31,12 @@ pub fn router() -> OxRouter { let api_routes = Router::new() .merge(secure_routes) .merge(auth::routes::router()) - .merge(core::routes::router()) - .merge(gateway::routes::router()); + .merge(core::routes::router()); + + let ws_routes = Router::new().merge(gateway::routes::router()); Router::new() .nest("/api", api_routes) + .nest("/ws", ws_routes) .merge(SwaggerUi::new("/swagger").url("/api-docs/openapi.json", openapi::ApiDoc::openapi())) }