This commit is contained in:
2026-06-21 19:11:23 +02:00
parent 3fd2b8ade8
commit e9d77fbd05
11 changed files with 96 additions and 77 deletions
Generated
+1
View File
@@ -2350,6 +2350,7 @@ dependencies = [
"chrono",
"config",
"event_bus",
"form_urlencoded",
"futures-util",
"jsonwebtoken",
"log",
+1
View File
@@ -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"
+5
View File
@@ -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()
}
+16 -2
View File
@@ -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 = () => {
+3 -1
View File
@@ -1,6 +1,8 @@
import {defineStore} from "pinia";
export const useServerStore = defineStore("server", {
state: () => ({}),
state: () => ({
servers: []
}),
actions: {}
});
+23 -15
View File
@@ -91,30 +91,38 @@ pub async fn auth_middleware(
mut req: Request<Body>,
next: Next,
) -> Response {
// Extraction du JWT depuis le header Authorization
let user: Option<CurrentUser> = 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<CurrentUser> =
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 {
+15 -1
View File
@@ -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", &"<redacted>")
.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")]
+17 -4
View File
@@ -34,8 +34,15 @@ impl UserRepository {
pub async fn check_password(&self, username: &str, password: &str) -> AnyResult<user::Model> {
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?;
+5 -50
View File
@@ -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<Message>, 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;
}
+6 -2
View File
@@ -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 {
+4 -2
View File
@@ -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()))
}