Files
zesdex/apps/infrastructure/src/middleware/auth.rs
T
asepharyana a04651905f feat(token): add refresh token verification to TokenService
feat(bootstrap): create temporary settings and config files to prevent data loss

refactor(edit_log): switch from Vec to VecDeque for efficient memory management

fix(gateway): ensure store directories are created before starting the API server

refactor(bgbash): implement a global singleton for BashControl

feat(auth): enhance session authentication middleware to use SessionRepository

fix(edit_log_repo): update to use VecDeque for in-memory edit log storage

fix(memory_repo): add newline escaping for frontmatter fields

fix(session_lock_repo): improve error handling for lock file operations

fix(bash_tools): prevent path traversal in job_id argument

refactor(delete): enforce empty directory deletion in file system tools

fix(edit): optimize string replacement to only replace the first occurrence

fix(git_cred): improve credential management with piped input to git commands

feat(git_operator): add safety filter to block destructive git operations

fix(shell): register background jobs in Bash control

feat(spawn): add access tier specification for pipeline stages

refactor(hive_mind): run directives concurrently for improved performance

fix(auth): update refresh token verification in the refresh handler

fix(chat): optimize LLM client usage based on model matching

fix(conversations): enhance message deletion to target specific indices

feat(api): add JWT authentication middleware for all API routes

fix(state): implement refresh token verification in JwtTokenService

fix(daemon): improve usage tracking with saturating addition

fix(tui): handle compacted messages in the TUI state management
2026-07-20 12:26:10 +07:00

121 lines
3.6 KiB
Rust

//! Authentication middleware — session-lock based auth for Axum.
//!
//! Validates `X-Session-Id` header against the `SessionRepository` before
//! forwarding the request to the inner service.
use std::future::Future;
use std::path::PathBuf;
use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};
use axum::body::Body;
use axum::http::{Request, Response, StatusCode};
use axum::response::IntoResponse;
use serde::{Deserialize, Serialize};
use tower::{Layer, Service};
use zesdex_domain::auth::{SessionId, SessionRepository};
/// Identity extracted from a validated session.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionIdentity {
pub session_id: String,
pub user_agent: String,
pub connected_at: i64,
}
impl SessionIdentity {
pub fn new(session_id: String, user_agent: String) -> Self {
let connected_at = chrono::Utc::now().timestamp();
Self {
session_id,
user_agent,
connected_at,
}
}
}
/// Tower Layer that produces SessionAuthMiddleware services.
///
/// Holds a reference to the `SessionRepository` and the base directory
/// needed to validate session IDs.
#[derive(Debug, Clone)]
pub struct SessionAuthLayer<R: SessionRepository + Send + Sync + 'static> {
base_dir: PathBuf,
repo: Arc<R>,
}
impl<R: SessionRepository + Send + Sync + 'static> SessionAuthLayer<R> {
pub fn new(base_dir: PathBuf, repo: Arc<R>) -> Self {
Self { base_dir, repo }
}
}
impl<S, R> Layer<S> for SessionAuthLayer<R>
where
R: SessionRepository + Send + Sync + 'static,
{
type Service = SessionAuthMiddleware<S, R>;
fn layer(&self, inner: S) -> Self::Service {
SessionAuthMiddleware {
inner,
base_dir: self.base_dir.clone(),
repo: self.repo.clone(),
}
}
}
/// Tower Service that validates X-Session-Id before forwarding.
#[derive(Debug, Clone)]
pub struct SessionAuthMiddleware<S, R: SessionRepository + Send + Sync + 'static> {
inner: S,
base_dir: PathBuf,
repo: Arc<R>,
}
impl<S, ReqBody, R> Service<Request<ReqBody>> for SessionAuthMiddleware<S, R>
where
S: Service<Request<ReqBody>, Response = Response<Body>> + Send + 'static,
S::Future: Send + 'static,
ReqBody: Send + 'static,
R: SessionRepository + Send + Sync + 'static,
{
type Response = S::Response;
type Error = S::Error;
type Future =
Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send + 'static>>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx)
}
fn call(&mut self, req: Request<ReqBody>) -> Self::Future {
let session_id = req
.headers()
.get("X-Session-Id")
.and_then(|v| v.to_str().ok())
.map(|s| s.to_string());
// Validate the session against the repository.
match session_id {
Some(sid) => match SessionId::new(&sid) {
Ok(id) => match self.repo.load_session(&self.base_dir, &id) {
Ok(_session) => {
// Session is valid — forward the request.
let fut = self.inner.call(req);
return Box::pin(fut);
}
Err(_) => { /* fall through to 401 */ }
},
Err(_) => { /* fall through to 401 */ }
},
None => { /* fall through to 401 */ }
}
Box::pin(async move {
Ok((StatusCode::UNAUTHORIZED, "missing or invalid X-Session-Id header").into_response())
})
}
}