Init
This commit is contained in:
@@ -6,7 +6,7 @@ use argon2::{
|
||||
/// Hache un password avec Argon2id
|
||||
/// Génère automatiquement un salt cryptographiquement sûr
|
||||
pub fn hash_password(password: &str) -> Result<String, argon2::password_hash::Error> {
|
||||
let params = Params::new(65540, 18, 1, None)?;
|
||||
let params = Params::new(65540, 3, 4, None)?;
|
||||
let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
|
||||
|
||||
argon2
|
||||
|
||||
+8
-11
@@ -5,11 +5,9 @@ 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 is_superuser: bool, // Ajoutez ce champ
|
||||
pub user_id: Uuid, // User ID
|
||||
pub exp: usize, // Changé de expire_at -> exp (Standard JWT)
|
||||
pub iat: usize, // Changé de created_at -> iat (Standard JWT)
|
||||
}
|
||||
|
||||
pub fn create_jwt(
|
||||
@@ -25,11 +23,9 @@ pub fn create_jwt(
|
||||
.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(),
|
||||
is_superuser, // Et ici
|
||||
user_id,
|
||||
exp: (now + expiration_seconds) as usize,
|
||||
iat: now as usize,
|
||||
};
|
||||
|
||||
encode(
|
||||
@@ -40,12 +36,13 @@ pub fn create_jwt(
|
||||
}
|
||||
|
||||
pub fn verify_jwt(token: &str, secret: &str) -> Result<Claims, jsonwebtoken::errors::Error> {
|
||||
println!("Verifying token: {}", token);
|
||||
let validation = Validation::default();
|
||||
let token_data = decode::<Claims>(
|
||||
token,
|
||||
&DecodingKey::from_secret(secret.as_ref()),
|
||||
&validation,
|
||||
)?;
|
||||
|
||||
println!("Token data: {:?}", token_data);
|
||||
Ok(token_data.claims)
|
||||
}
|
||||
|
||||
+2
-2
@@ -9,7 +9,7 @@ use crate::udp::server::UdpServer;
|
||||
use event_bus::EventBus;
|
||||
use migration::{Migrator, MigratorTrait};
|
||||
pub use state::AppState;
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::time::Duration;
|
||||
use uuid::Uuid;
|
||||
|
||||
@@ -66,7 +66,7 @@ impl App {
|
||||
db,
|
||||
config: Arc::new(config),
|
||||
repositories,
|
||||
init_token,
|
||||
init_token: Arc::new(RwLock::new(init_token)),
|
||||
default_server: Arc::new(default_server),
|
||||
metrics,
|
||||
};
|
||||
|
||||
+2
-2
@@ -3,14 +3,14 @@ use crate::metrics::AppMetrics;
|
||||
use crate::models::server;
|
||||
use crate::repositories::Repositories;
|
||||
use sea_orm::DatabaseConnection;
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AppState {
|
||||
pub db: DatabaseConnection,
|
||||
pub config: Arc<AppConfig>,
|
||||
pub repositories: Repositories,
|
||||
pub init_token: Option<uuid::Uuid>,
|
||||
pub init_token: Arc<RwLock<Option<uuid::Uuid>>>,
|
||||
pub default_server: Arc<server::Model>,
|
||||
pub metrics: AppMetrics,
|
||||
}
|
||||
|
||||
+3
-1
@@ -79,7 +79,9 @@ impl IntoResponse for HTTPError {
|
||||
.into_response();
|
||||
}
|
||||
HTTPError::Internal(err) => {
|
||||
tracing::error!(error = ?err, "An unexpected error occurred");
|
||||
// On utilise %err pour un message d'erreur clair sans backtrace brute
|
||||
// mais on garde les détails pour le span tracing si besoin.
|
||||
tracing::error!(%err, "Request error");
|
||||
(StatusCode::INTERNAL_SERVER_ERROR, "Internal server error")
|
||||
}
|
||||
};
|
||||
|
||||
+25
-3
@@ -1,5 +1,5 @@
|
||||
use axum::{
|
||||
body::Body,
|
||||
body::{Body, HttpBody},
|
||||
extract::State,
|
||||
http::{header, Request},
|
||||
middleware::Next,
|
||||
@@ -55,8 +55,30 @@ pub async fn request_context_middleware(mut req: Request<Body>, next: Next) -> R
|
||||
);
|
||||
|
||||
async move {
|
||||
info!("Incoming request");
|
||||
next.run(req).await
|
||||
let response = next.run(req).await;
|
||||
let elapsed = started_at.elapsed();
|
||||
let status = response.status();
|
||||
|
||||
let size = response
|
||||
.body()
|
||||
.size_hint()
|
||||
.exact()
|
||||
.map(|s| s.to_string())
|
||||
.or_else(|| {
|
||||
response
|
||||
.headers()
|
||||
.get(header::CONTENT_LENGTH)
|
||||
.and_then(|v| v.to_str().ok())
|
||||
.map(|s| s.to_string())
|
||||
})
|
||||
.unwrap_or_else(|| "0".to_string());
|
||||
|
||||
if status.is_server_error() {
|
||||
tracing::error!(%status, %size, ?elapsed, "Request failed");
|
||||
} else {
|
||||
info!("{} {}b in {:?}", status, size, elapsed);
|
||||
}
|
||||
response
|
||||
}
|
||||
.instrument(span)
|
||||
.await
|
||||
|
||||
+2
-2
@@ -11,8 +11,8 @@ pub struct Model {
|
||||
pub id: Uuid,
|
||||
pub username: String,
|
||||
pub password: String,
|
||||
#[sea_orm(column_type = "Text", unique)]
|
||||
pub pub_key: String,
|
||||
#[sea_orm(column_type = "Text", unique, nullable)]
|
||||
pub pub_key: Option<String>,
|
||||
pub created_at: DateTimeUtc,
|
||||
pub updated_at: DateTimeUtc,
|
||||
pub is_superuser: bool,
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
use crate::routes::user::dto::UserResponse;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use utoipa::ToSchema;
|
||||
|
||||
#[derive(Deserialize, ToSchema)]
|
||||
#[derive(Debug, Deserialize, ToSchema)]
|
||||
pub struct LoginRequest {
|
||||
pub username: String,
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, ToSchema)]
|
||||
#[derive(Debug, Serialize, ToSchema)]
|
||||
pub struct LoginResponse {
|
||||
pub token: String,
|
||||
pub username: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, ToSchema)]
|
||||
pub struct CheckResponse {
|
||||
pub authenticated: bool,
|
||||
#[derive(Debug, Serialize, ToSchema)]
|
||||
pub struct MeResponse {
|
||||
pub user: UserResponse,
|
||||
}
|
||||
|
||||
+16
-13
@@ -1,14 +1,17 @@
|
||||
use super::dto::{CheckResponse, LoginRequest, LoginResponse};
|
||||
use super::dto::{LoginRequest, LoginResponse, MeResponse};
|
||||
use crate::auth::token::create_jwt;
|
||||
use crate::core::AppState;
|
||||
use crate::http::context::CurrentUser;
|
||||
use crate::http::error::HTTPError;
|
||||
use crate::routes::user::mapper::user_model_to_user_response;
|
||||
use axum::extract::State;
|
||||
use axum::Json;
|
||||
use sea_orm::ActiveModelBehavior;
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
get,
|
||||
path = "/auth/login",
|
||||
request_body = LoginRequest,
|
||||
responses(
|
||||
(status = 200, description = "Login successful", body = LoginResponse),
|
||||
(status = 401, description = "Unauthorized")
|
||||
@@ -29,22 +32,20 @@ pub async fn login_user_pw(
|
||||
let token = create_jwt(
|
||||
user.id,
|
||||
&user.username,
|
||||
user.is_superuser,
|
||||
&state.config.jwt.secret,
|
||||
state.config.jwt.duration,
|
||||
)
|
||||
.map_err(|_| HTTPError::InternalServerError("Failed to create JWT token".to_string()))?;
|
||||
|
||||
Ok(Json(LoginResponse {
|
||||
username: user.username,
|
||||
token,
|
||||
}))
|
||||
Ok(Json(LoginResponse { token }))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/auth/check",
|
||||
path = "/auth/me",
|
||||
responses(
|
||||
(status = 200, description = "Login successful", body = LoginResponse),
|
||||
(status = 200, description = "Token valid", body = LoginResponse),
|
||||
(status = 401, description = "Unauthorized")
|
||||
),
|
||||
security(
|
||||
@@ -52,11 +53,13 @@ pub async fn login_user_pw(
|
||||
),
|
||||
tag = "Auth"
|
||||
)]
|
||||
pub async fn check(
|
||||
pub async fn me(
|
||||
State(_state): State<AppState>,
|
||||
_user: CurrentUser,
|
||||
) -> Result<Json<CheckResponse>, HTTPError> {
|
||||
Ok(Json(CheckResponse {
|
||||
authenticated: true,
|
||||
CurrentUser(user): CurrentUser,
|
||||
) -> Result<Json<MeResponse>, HTTPError> {
|
||||
let user_response = user_model_to_user_response(user);
|
||||
|
||||
Ok(Json(MeResponse {
|
||||
user: user_response,
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use crate::http::OxRouter;
|
||||
use crate::routes::auth::handlers;
|
||||
use axum::routing::post;
|
||||
use axum::routing::{get, post};
|
||||
use axum::Router;
|
||||
|
||||
pub fn router() -> OxRouter {
|
||||
Router::new()
|
||||
.route("/auth/login", post(handlers::login_user_pw))
|
||||
.route("/auth/check", post(handlers::check))
|
||||
.route("/auth/me", get(handlers::me))
|
||||
}
|
||||
|
||||
@@ -34,10 +34,24 @@ pub async fn join(
|
||||
));
|
||||
};
|
||||
|
||||
let user_am = join_request_to_user_am(payload, state.init_token)?;
|
||||
let user_am = {
|
||||
let init_token_lock = state
|
||||
.init_token
|
||||
.read()
|
||||
.map_err(|e| HTTPError::InternalServerError(e.to_string()))?;
|
||||
join_request_to_user_am(payload, *init_token_lock)?
|
||||
};
|
||||
|
||||
let user = state.repositories.user.create(user_am).await?;
|
||||
|
||||
if user.is_superuser {
|
||||
let mut init_token_lock = state
|
||||
.init_token
|
||||
.write()
|
||||
.map_err(|e| HTTPError::InternalServerError(e.to_string()))?;
|
||||
*init_token_lock = None;
|
||||
}
|
||||
|
||||
state
|
||||
.repositories
|
||||
.server
|
||||
|
||||
@@ -9,13 +9,12 @@ pub fn join_request_to_user_am(
|
||||
payload: JoinRequest,
|
||||
superuser_token: Option<Uuid>,
|
||||
) -> AnyResult<user::ActiveModel> {
|
||||
let is_super_admin = match (payload.superuser_token.as_ref(), superuser_token) {
|
||||
(Some(provided), Some(init)) => provided == &init.to_string(),
|
||||
_ => false,
|
||||
};
|
||||
let mut is_super_admin = false;
|
||||
if let (Some(provided), Some(init)) = (payload.superuser_token.as_ref(), superuser_token) {
|
||||
is_super_admin = provided == &init.to_string();
|
||||
}
|
||||
|
||||
Ok(user::ActiveModel {
|
||||
id: Default::default(),
|
||||
username: Set(payload.username),
|
||||
password: Set(hash_password(&payload.password)?),
|
||||
is_superuser: Set(is_super_admin),
|
||||
|
||||
+5
-4
@@ -29,9 +29,10 @@ pub fn router() -> OxRouter {
|
||||
// Routes publiques (ou gérant leur propre auth)
|
||||
let api_routes = Router::new()
|
||||
.merge(secure_routes)
|
||||
.merge(auth::routes::router());
|
||||
.merge(auth::routes::router())
|
||||
.merge(core::routes::router());
|
||||
|
||||
Router::new().nest("/api", api_routes).merge(
|
||||
SwaggerUi::new("/swagger-ui").url("/api-docs/openapi.json", openapi::ApiDoc::openapi()),
|
||||
)
|
||||
Router::new()
|
||||
.nest("/api", api_routes)
|
||||
.merge(SwaggerUi::new("/swagger").url("/api-docs/openapi.json", openapi::ApiDoc::openapi()))
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use utoipa::{Modify, OpenApi};
|
||||
#[openapi(
|
||||
paths(
|
||||
auth::handlers::login_user_pw,
|
||||
auth::handlers::check,
|
||||
auth::handlers::me,
|
||||
user::handlers::get_all,
|
||||
user::handlers::get_by_id,
|
||||
user::handlers::create,
|
||||
@@ -44,7 +44,7 @@ use utoipa::{Modify, OpenApi};
|
||||
schemas(
|
||||
auth::dto::LoginRequest,
|
||||
auth::dto::LoginResponse,
|
||||
auth::dto::CheckResponse,
|
||||
auth::dto::MeResponse,
|
||||
user::dto::UserResponse,
|
||||
user::dto::CreateUserRequest,
|
||||
user::dto::UpdateUserRequest,
|
||||
|
||||
@@ -7,14 +7,14 @@ use uuid::Uuid;
|
||||
pub struct CreateUserRequest {
|
||||
pub username: String,
|
||||
pub password: String,
|
||||
pub pub_key: String,
|
||||
pub pub_key: Option<String>,
|
||||
pub is_superuser: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, ToSchema)]
|
||||
pub struct UpdateUserRequest {
|
||||
pub username: String,
|
||||
pub pub_key: String,
|
||||
pub pub_key: Option<String>,
|
||||
pub is_superuser: bool,
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ pub struct UpdateUserRequest {
|
||||
pub struct UserResponse {
|
||||
pub id: Uuid,
|
||||
pub username: String,
|
||||
pub pub_key: String,
|
||||
pub pub_key: Option<String>,
|
||||
pub is_superuser: bool,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
|
||||
Reference in New Issue
Block a user