fix(api): perbaiki keamanan auth & WebSocket, tambah rate limiting
Security fixes hasil audit: - fix(auth): refresh token kini memakai claim typ=refresh; access token tidak bisa dipakai sebagai refresh token (sebelumnya bisa — eskalasi masa berlaku 1 jam -> 7 hari) - fix(api): layer JWT hanya melindungi route /sessions dan /chat; /auth/login, /auth/register, /auth/refresh, /health kini publik (sebelumnya semua route 401-lock, API tidak bisa dipakai sama sekali) - fix(ws): endpoint /ws kini memverifikasi token ZESDEX_WS_TOKEN via query param jika env diset (mencegah pemakaian LLM proxy terbuka) - feat(api): rate limiting login/register/refresh (20 request / 10 menit per client IP) memakai RateLimiter yang tadinya dead code - test(jwt): tambah unit test token type access vs refresh + expired
This commit is contained in:
@@ -56,25 +56,34 @@ pub fn build_router(state: ApiState) -> Router {
|
||||
// CORS layer — permissive for local daemon / development use
|
||||
let cors = CorsLayer::permissive();
|
||||
|
||||
// JWT auth middleware — validates Bearer tokens on all API routes.
|
||||
// Health and auth endpoints (login/register/refresh) are also
|
||||
// protected; adjust route ordering or add an allow-list inside the
|
||||
// middleware if public access is needed.
|
||||
// JWT auth middleware — protects session/chat routes.
|
||||
// Auth (login/register/refresh) and health endpoints stay public.
|
||||
let jwt_auth = middleware::auth::JwtAuthLayer::new(shared_state.clone());
|
||||
|
||||
// Combine all sub-routers under a versioned prefix
|
||||
Router::new()
|
||||
.nest("/api/v1", api_v1_router())
|
||||
.nest("/api/v1/auth", auth_router())
|
||||
.nest("/api/v1/health", health_router())
|
||||
.nest(
|
||||
"/api/v1",
|
||||
protected_router().layer(jwt_auth),
|
||||
)
|
||||
.layer(cors)
|
||||
.layer(jwt_auth)
|
||||
.with_state(shared_state)
|
||||
}
|
||||
|
||||
/// Version 1 API sub-router.
|
||||
///
|
||||
/// Groups all resource routes under `/api/v1/*`.
|
||||
fn api_v1_router() -> Router<Arc<ApiState>> {
|
||||
use handlers::{auth, chat, conversations, health, sessions};
|
||||
/// Auth + health sub-routers — publicly accessible (no JWT required).
|
||||
fn auth_router() -> Router<Arc<ApiState>> {
|
||||
handlers::auth::router()
|
||||
}
|
||||
|
||||
fn health_router() -> Router<Arc<ApiState>> {
|
||||
Router::new().route("/", axum::routing::get(handlers::health::health))
|
||||
}
|
||||
|
||||
/// Protected sub-router — sessions + chat, guarded by JWT auth layer.
|
||||
fn protected_router() -> Router<Arc<ApiState>> {
|
||||
use handlers::{chat, conversations, sessions};
|
||||
|
||||
// Sessions router combines session CRUD + nested conversations
|
||||
let sessions_router = Router::new()
|
||||
@@ -96,8 +105,6 @@ fn api_v1_router() -> Router<Arc<ApiState>> {
|
||||
);
|
||||
|
||||
Router::new()
|
||||
.route("/health", axum::routing::get(health::health))
|
||||
.nest("/auth", auth::router())
|
||||
.nest("/sessions", sessions_router)
|
||||
.nest("/chat", chat::router())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user