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()))
}