Files
featherChat/warzone/crates/warzone-server/src/auth_middleware.rs
Siavash Sameni 3e0889e5dc v0.0.21: TUI overhaul, WZP call infrastructure, security hardening, federation
TUI:
- Split 1,756-line app.rs monolith into 7 modules (types, draw, commands, input, file_transfer, network, mod)
- Message timestamps [HH:MM], scrolling (PageUp/Down/arrows), connection status dot, unread badge
- /help command, terminal bell on incoming DM, /devices + /kick commands
- 44 unit tests (types, input, draw with TestBackend)

Server — WZP Call Infrastructure (FC-2/3/5/6/7/10):
- Call state management (CallState, CallStatus, active_calls, calls + missed_calls sled trees)
- WS call signal awareness (Offer/Answer/Hangup update state, missed call on offline)
- Group call endpoint (POST /groups/:name/call with SHA-256 room ID, fan-out)
- Presence API (GET /presence/:fp, POST /presence/batch)
- Missed call flush on WS reconnect
- WZP relay config + CORS

Server — Security (FC-P1):
- Auth enforcement middleware (AuthFingerprint extractor on 13 write handlers)
- Session auto-recovery (delete corrupted ratchet, show [session reset])
- WS connection cap (5/fingerprint) + global concurrency limit (200)
- Device management (GET /devices, POST /devices/:id/kick, POST /devices/revoke-all)

Server — Federation:
- Two-server federation via JSON config (--federation flag)
- Periodic presence sync (every 5s, full-state, self-healing)
- Message forwarding via HTTP POST with SHA-256(secret||body) auth
- Graceful degradation (peer down = queue locally)
- deliver_or_queue() replaces push-or-queue in ws.rs + messages.rs

Client — Group Messaging:
- SenderKeyDistribution storage + GroupSenderKey decryption in TUI
- sender_keys sled tree in LocalDb

WASM:
- All 8 WireMessage variants handled (no more "unsupported")
- decrypt_group_message() + create_sender_key_from_distribution() exports
- CallSignal parsing with signal_type mapping

Docs:
- ARCHITECTURE.md rewritten with Mermaid diagrams
- README.md created
- TASK_PLAN.md with FC-P{phase}-T{task} naming
- PROGRESS.md updated to v0.0.21

WZP submodule updated to 6f4e8eb (IAX2 trunking, adaptive quality, metrics, all S-tasks done)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 16:45:58 +04:00

85 lines
2.6 KiB
Rust

//! Auth enforcement middleware: axum extractor that validates bearer tokens.
//!
//! Reads `Authorization: Bearer <token>` from request headers, validates via
//! [`crate::routes::auth::validate_token`], and returns the authenticated
//! fingerprint or a 401 rejection.
use axum::{
extract::FromRequestParts,
http::{request::Parts, StatusCode},
response::{IntoResponse, Response},
};
use crate::state::AppState;
/// Extractor that validates a bearer token and provides the authenticated fingerprint.
///
/// Place this as the **first** parameter in any handler that requires authentication.
/// The extractor will reject the request with 401 if the token is missing or invalid.
///
/// # Example
///
/// ```ignore
/// async fn my_handler(
/// auth: AuthFingerprint,
/// State(state): State<AppState>,
/// ) -> impl IntoResponse {
/// let fp = auth.fingerprint; // guaranteed valid
/// // ...
/// }
/// ```
pub struct AuthFingerprint {
pub fingerprint: String,
}
#[axum::async_trait]
impl FromRequestParts<AppState> for AuthFingerprint {
type Rejection = AuthError;
async fn from_request_parts(
parts: &mut Parts,
state: &AppState,
) -> Result<Self, Self::Rejection> {
let header = parts
.headers
.get("authorization")
.and_then(|v| v.to_str().ok())
.and_then(|s| s.strip_prefix("Bearer "))
.map(|s| s.trim().to_string());
let token = match header {
Some(t) if !t.is_empty() => t,
_ => return Err(AuthError::MissingToken),
};
match crate::routes::auth::validate_token(&state.db.tokens, &token) {
Some(fingerprint) => Ok(AuthFingerprint { fingerprint }),
None => Err(AuthError::InvalidToken),
}
}
}
/// Rejection type for [`AuthFingerprint`] extractor failures.
pub enum AuthError {
/// No `Authorization: Bearer <token>` header was present (or it was empty).
MissingToken,
/// The token was present but did not pass validation (expired or unknown).
InvalidToken,
}
impl IntoResponse for AuthError {
fn into_response(self) -> Response {
let (status, msg) = match self {
AuthError::MissingToken => (
StatusCode::UNAUTHORIZED,
"missing or empty Authorization: Bearer <token> header",
),
AuthError::InvalidToken => (
StatusCode::UNAUTHORIZED,
"invalid or expired token",
),
};
(status, axum::Json(serde_json::json!({ "error": msg }))).into_response()
}
}