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>
This commit is contained in:
84
warzone/crates/warzone-server/src/auth_middleware.rs
Normal file
84
warzone/crates/warzone-server/src/auth_middleware.rs
Normal file
@@ -0,0 +1,84 @@
|
||||
//! 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()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user