Skip to main content

allsource_core/infrastructure/security/
middleware.rs

1use crate::{
2    error::AllSourceError,
3    infrastructure::security::{
4        auth::{AuthManager, Claims, Permission, Role},
5        rate_limit::RateLimiter,
6    },
7};
8use axum::{
9    extract::{Request, State},
10    http::{HeaderMap, StatusCode},
11    middleware::Next,
12    response::{IntoResponse, Response},
13};
14use std::sync::{Arc, LazyLock};
15
16/// Paths that bypass authentication (exact match)
17pub const AUTH_SKIP_PATHS: &[&str] = &[
18    "/health",
19    "/metrics",
20    "/api/v1/auth/register",
21    "/api/v1/auth/login",
22    "/api/v1/demo/seed",
23];
24
25/// Path prefixes that bypass authentication and rate limiting.
26///
27/// Internal endpoints are used by the sentinel process for automated failover
28/// (promote, repoint). They must not require API keys or be rate-limited,
29/// otherwise failover can timeout or fail when credentials are unavailable.
30pub const AUTH_SKIP_PREFIXES: &[&str] = &["/internal/"];
31
32/// Check if a path should skip authentication and rate limiting.
33#[inline]
34pub fn should_skip_auth(path: &str) -> bool {
35    AUTH_SKIP_PATHS.contains(&path) || AUTH_SKIP_PREFIXES.iter().any(|pfx| path.starts_with(pfx))
36}
37
38/// Check if development mode is enabled via environment variable.
39/// When enabled, authentication and rate limiting are bypassed for local
40/// development (e.g., MCP server integration, quick curl queries against
41/// `/api/v1/events/query` without generating a JWT).
42///
43/// Set any of the following to `true`/`1` to enable:
44/// - `ALLSOURCE_DEV_MODE` — historical name, still supported
45/// - `ALLSOURCE_AUTH_DISABLED` — explicit "turn auth off" alias (issue #131)
46///
47/// **WARNING**: Never enable this in production environments! The feature
48/// grants admin context to any request that arrives without a token.
49fn env_flag_enabled(name: &str) -> bool {
50    std::env::var(name)
51        .map(|v| matches!(v.to_lowercase().as_str(), "true" | "1" | "yes"))
52        .unwrap_or(false)
53}
54
55static DEV_MODE_ENABLED: LazyLock<bool> = LazyLock::new(|| {
56    let via_dev = env_flag_enabled("ALLSOURCE_DEV_MODE");
57    let via_auth_off = env_flag_enabled("ALLSOURCE_AUTH_DISABLED");
58    let enabled = via_dev || via_auth_off;
59    if enabled {
60        let source = if via_auth_off && via_dev {
61            "ALLSOURCE_DEV_MODE + ALLSOURCE_AUTH_DISABLED"
62        } else if via_auth_off {
63            "ALLSOURCE_AUTH_DISABLED"
64        } else {
65            "ALLSOURCE_DEV_MODE"
66        };
67        tracing::warn!(
68            "⚠️  Auth disabled via {source} — all requests run as admin with no rate limits. DO NOT use in production."
69        );
70    }
71    enabled
72});
73
74/// Check if dev mode is enabled
75#[inline]
76pub fn is_dev_mode() -> bool {
77    *DEV_MODE_ENABLED
78}
79
80/// Create a development-mode AuthContext with admin privileges
81fn dev_mode_auth_context() -> AuthContext {
82    AuthContext {
83        claims: Claims::new(
84            "dev-user".to_string(),
85            "dev-tenant".to_string(),
86            Role::Admin,
87            chrono::Duration::hours(24),
88        ),
89    }
90}
91
92/// Authentication state shared across requests
93#[derive(Clone)]
94pub struct AuthState {
95    pub auth_manager: Arc<AuthManager>,
96}
97
98/// Rate limiting state
99#[derive(Clone)]
100pub struct RateLimitState {
101    pub rate_limiter: Arc<RateLimiter>,
102}
103
104/// Authenticated request context
105#[derive(Debug, Clone)]
106pub struct AuthContext {
107    pub claims: Claims,
108}
109
110impl AuthContext {
111    /// Check if user has required permission
112    pub fn require_permission(&self, permission: Permission) -> Result<(), AllSourceError> {
113        if self.claims.has_permission(permission) {
114            Ok(())
115        } else {
116            Err(AllSourceError::ValidationError(
117                "Insufficient permissions".to_string(),
118            ))
119        }
120    }
121
122    /// Get tenant ID from context
123    pub fn tenant_id(&self) -> &str {
124        &self.claims.tenant_id
125    }
126
127    /// Get user ID from context
128    pub fn user_id(&self) -> &str {
129        &self.claims.sub
130    }
131}
132
133/// Extract token from Authorization header, with X-API-Key fallback for backwards compatibility.
134fn extract_token(headers: &HeaderMap) -> Result<String, AllSourceError> {
135    // Primary: Authorization header (Bearer <token> or plain <token>)
136    // Fallback: X-API-Key header (legacy, deprecated)
137    let auth_header = if let Some(val) = headers.get("authorization") {
138        val.to_str()
139            .map_err(|_| {
140                AllSourceError::ValidationError("Invalid authorization header".to_string())
141            })?
142            .to_string()
143    } else if let Some(val) = headers.get("x-api-key") {
144        val.to_str()
145            .map_err(|_| AllSourceError::ValidationError("Invalid X-API-Key header".to_string()))?
146            .to_string()
147    } else {
148        return Err(AllSourceError::ValidationError(
149            "Missing authorization header".to_string(),
150        ));
151    };
152
153    // Support both "Bearer <token>" and "<token>" formats
154    let token = if auth_header.starts_with("Bearer ") {
155        auth_header.trim_start_matches("Bearer ").trim()
156    } else if auth_header.starts_with("bearer ") {
157        auth_header.trim_start_matches("bearer ").trim()
158    } else {
159        auth_header.trim()
160    };
161
162    if token.is_empty() {
163        return Err(AllSourceError::ValidationError(
164            "Empty authorization token".to_string(),
165        ));
166    }
167
168    Ok(token.to_string())
169}
170
171/// Returns true for paths that require Admin permission regardless of other checks.
172///
173/// - POST /api/v1/auth/api-keys — key creation (agents must not self-replicate)
174/// - /api/v1/tenants/* — tenant management (agents must not alter tenants)
175///
176/// Note: /api/v1/auth/register is in AUTH_SKIP_PATHS (public self-registration) and
177/// additionally enforces Admin at the handler level when any auth token is present.
178#[inline]
179pub fn is_admin_only_path(path: &str, method: &str) -> bool {
180    (path == "/api/v1/auth/api-keys" && method == "POST") || path.starts_with("/api/v1/tenants")
181}
182
183/// Authentication middleware
184pub async fn auth_middleware(
185    State(auth_state): State<AuthState>,
186    mut request: Request,
187    next: Next,
188) -> Result<Response, AuthError> {
189    // Skip authentication for public and internal paths
190    let path = request.uri().path();
191    if should_skip_auth(path) {
192        return Ok(next.run(request).await);
193    }
194
195    // Dev mode: if a valid token is present, authenticate normally so that
196    // /me returns the real tenant_id (not a hardcoded "dev-tenant").
197    // Fall back to the synthetic dev context only when no token is provided.
198    if is_dev_mode() {
199        let headers = request.headers();
200        let auth_ctx = match extract_token(headers) {
201            Ok(token) => {
202                let claims = if token.starts_with("ask_") {
203                    auth_state.auth_manager.validate_api_key(&token).ok()
204                } else {
205                    auth_state.auth_manager.validate_token(&token).ok()
206                };
207                claims.map_or_else(dev_mode_auth_context, |c| AuthContext { claims: c })
208            }
209            Err(_) => dev_mode_auth_context(),
210        };
211        request.extensions_mut().insert(auth_ctx);
212        return Ok(next.run(request).await);
213    }
214
215    let headers = request.headers();
216
217    // Extract and validate token (JWT or API key)
218    let token = extract_token(headers)?;
219
220    let claims = if token.starts_with("ask_") {
221        // API Key authentication
222        auth_state.auth_manager.validate_api_key(&token)?
223    } else {
224        // JWT authentication
225        auth_state.auth_manager.validate_token(&token)?
226    };
227
228    let auth_ctx = AuthContext { claims };
229
230    // Enforce that service accounts (agent API keys) cannot access admin-only paths.
231    // Admin-only paths: user registration, API key creation, and all tenant management.
232    // These are also enforced at handler level; this middleware block provides defence-in-depth.
233    let path = request.uri().path();
234    let method = request.method().as_str();
235    let is_admin_only_path = is_admin_only_path(path, method);
236
237    if is_admin_only_path {
238        auth_ctx
239            .require_permission(Permission::Admin)
240            .map_err(|_| {
241                AuthError(AllSourceError::ValidationError(
242                    "Admin permission required".to_string(),
243                ))
244            })?;
245    }
246
247    // Insert auth context into request extensions
248    request.extensions_mut().insert(auth_ctx);
249
250    Ok(next.run(request).await)
251}
252
253/// Optional authentication middleware (allows unauthenticated requests)
254pub async fn optional_auth_middleware(
255    State(auth_state): State<AuthState>,
256    mut request: Request,
257    next: Next,
258) -> Response {
259    let headers = request.headers();
260
261    if let Ok(token) = extract_token(headers) {
262        // Try to authenticate, but don't fail if invalid
263        let claims = if token.starts_with("ask_") {
264            auth_state.auth_manager.validate_api_key(&token).ok()
265        } else {
266            auth_state.auth_manager.validate_token(&token).ok()
267        };
268
269        if let Some(claims) = claims {
270            request.extensions_mut().insert(AuthContext { claims });
271        }
272    }
273
274    next.run(request).await
275}
276
277/// Error type for authentication failures
278#[derive(Debug)]
279pub struct AuthError(AllSourceError);
280
281impl From<AllSourceError> for AuthError {
282    fn from(err: AllSourceError) -> Self {
283        AuthError(err)
284    }
285}
286
287impl IntoResponse for AuthError {
288    fn into_response(self) -> Response {
289        let (status, message) = match self.0 {
290            AllSourceError::ValidationError(msg) => (StatusCode::UNAUTHORIZED, msg),
291            _ => (
292                StatusCode::INTERNAL_SERVER_ERROR,
293                "Internal server error".to_string(),
294            ),
295        };
296
297        (status, message).into_response()
298    }
299}
300
301/// Axum extractor for authenticated requests
302pub struct Authenticated(pub AuthContext);
303
304impl<S> axum::extract::FromRequestParts<S> for Authenticated
305where
306    S: Send + Sync,
307{
308    type Rejection = (StatusCode, &'static str);
309
310    async fn from_request_parts(
311        parts: &mut axum::http::request::Parts,
312        _state: &S,
313    ) -> Result<Self, Self::Rejection> {
314        parts
315            .extensions
316            .get::<AuthContext>()
317            .cloned()
318            .map(Authenticated)
319            .ok_or((StatusCode::UNAUTHORIZED, "Unauthorized"))
320    }
321}
322
323/// Axum extractor for optional authentication (never rejects, returns Option)
324/// Use this for routes that work with or without authentication
325pub struct OptionalAuth(pub Option<AuthContext>);
326
327impl<S> axum::extract::FromRequestParts<S> for OptionalAuth
328where
329    S: Send + Sync,
330{
331    type Rejection = std::convert::Infallible;
332
333    async fn from_request_parts(
334        parts: &mut axum::http::request::Parts,
335        _state: &S,
336    ) -> Result<Self, Self::Rejection> {
337        Ok(OptionalAuth(parts.extensions.get::<AuthContext>().cloned()))
338    }
339}
340
341/// Axum extractor for admin-only requests
342pub struct Admin(pub AuthContext);
343
344impl<S> axum::extract::FromRequestParts<S> for Admin
345where
346    S: Send + Sync,
347{
348    type Rejection = (StatusCode, &'static str);
349
350    async fn from_request_parts(
351        parts: &mut axum::http::request::Parts,
352        _state: &S,
353    ) -> Result<Self, Self::Rejection> {
354        let auth_ctx = parts
355            .extensions
356            .get::<AuthContext>()
357            .cloned()
358            .ok_or((StatusCode::UNAUTHORIZED, "Unauthorized"))?;
359
360        auth_ctx
361            .require_permission(Permission::Admin)
362            .map_err(|_| (StatusCode::FORBIDDEN, "Admin permission required"))?;
363
364        Ok(Admin(auth_ctx))
365    }
366}
367
368/// Rate limiting middleware
369/// Checks rate limits based on tenant_id from auth context
370pub async fn rate_limit_middleware(
371    State(rate_limit_state): State<RateLimitState>,
372    request: Request,
373    next: Next,
374) -> Result<Response, RateLimitError> {
375    // Skip rate limiting for public and internal paths
376    let path = request.uri().path();
377    if should_skip_auth(path) {
378        return Ok(next.run(request).await);
379    }
380
381    // Dev mode: bypass rate limiting entirely
382    if is_dev_mode() {
383        return Ok(next.run(request).await);
384    }
385
386    // Extract auth context from request
387    let auth_ctx = request
388        .extensions()
389        .get::<AuthContext>()
390        .ok_or(RateLimitError::Unauthorized)?;
391
392    // Check rate limit for this tenant
393    let result = rate_limit_state
394        .rate_limiter
395        .check_rate_limit(auth_ctx.tenant_id());
396
397    if !result.allowed {
398        return Err(RateLimitError::RateLimitExceeded {
399            retry_after: result.retry_after.unwrap_or_default().as_secs(),
400            limit: result.limit,
401        });
402    }
403
404    // Add rate limit headers to response
405    let mut response = next.run(request).await;
406    let headers = response.headers_mut();
407    headers.insert(
408        "X-RateLimit-Limit",
409        result.limit.to_string().parse().unwrap(),
410    );
411    headers.insert(
412        "X-RateLimit-Remaining",
413        result.remaining.to_string().parse().unwrap(),
414    );
415
416    Ok(response)
417}
418
419/// Error type for rate limiting failures
420#[derive(Debug)]
421pub enum RateLimitError {
422    RateLimitExceeded { retry_after: u64, limit: u32 },
423    Unauthorized,
424}
425
426impl IntoResponse for RateLimitError {
427    fn into_response(self) -> Response {
428        match self {
429            RateLimitError::RateLimitExceeded { retry_after, limit } => {
430                let mut response = (
431                    StatusCode::TOO_MANY_REQUESTS,
432                    format!("Rate limit exceeded. Limit: {limit} requests/min"),
433                )
434                    .into_response();
435
436                if retry_after > 0 {
437                    response
438                        .headers_mut()
439                        .insert("Retry-After", retry_after.to_string().parse().unwrap());
440                }
441
442                response
443            }
444            RateLimitError::Unauthorized => (
445                StatusCode::UNAUTHORIZED,
446                "Authentication required for rate limiting",
447            )
448                .into_response(),
449        }
450    }
451}
452
453/// Helper macro to require specific permission
454#[macro_export]
455macro_rules! require_permission {
456    ($auth:expr, $perm:expr) => {
457        $auth.0.require_permission($perm).map_err(|_| {
458            (
459                axum::http::StatusCode::FORBIDDEN,
460                "Insufficient permissions",
461            )
462        })?
463    };
464}
465
466// ============================================================================
467// Tenant Isolation Middleware (Phase 5B)
468// ============================================================================
469
470use crate::domain::{entities::Tenant, repositories::TenantRepository, value_objects::TenantId};
471
472/// Tenant isolation state for middleware
473#[derive(Clone)]
474pub struct TenantState<R: TenantRepository> {
475    pub tenant_repository: Arc<R>,
476}
477
478/// Validated tenant context injected into requests
479///
480/// This context is created by the tenant_isolation_middleware after
481/// validating that the tenant exists and is active.
482#[derive(Debug, Clone)]
483pub struct TenantContext {
484    pub tenant: Tenant,
485}
486
487impl TenantContext {
488    /// Get the tenant ID
489    pub fn tenant_id(&self) -> &TenantId {
490        self.tenant.id()
491    }
492
493    /// Check if tenant is active
494    pub fn is_active(&self) -> bool {
495        self.tenant.is_active()
496    }
497}
498
499/// Tenant isolation middleware
500///
501/// Validates that the authenticated tenant exists and is active.
502/// Injects TenantContext into the request for use by handlers.
503///
504/// # Phase 5B: Tenant Isolation
505/// This middleware enforces tenant boundaries by:
506/// 1. Extracting tenant_id from AuthContext
507/// 2. Loading tenant from repository
508/// 3. Validating tenant is active
509/// 4. Injecting TenantContext into request extensions
510///
511/// Must be applied after auth_middleware.
512pub async fn tenant_isolation_middleware<R: TenantRepository + 'static>(
513    State(tenant_state): State<TenantState<R>>,
514    mut request: Request,
515    next: Next,
516) -> Result<Response, TenantError> {
517    // Extract auth context (must be authenticated)
518    let auth_ctx = request
519        .extensions()
520        .get::<AuthContext>()
521        .ok_or(TenantError::Unauthorized)?
522        .clone();
523
524    // Parse tenant ID
525    let tenant_id =
526        TenantId::new(auth_ctx.tenant_id().to_string()).map_err(|_| TenantError::InvalidTenant)?;
527
528    // Load tenant from repository
529    let tenant = tenant_state
530        .tenant_repository
531        .find_by_id(&tenant_id)
532        .await
533        .map_err(|e| TenantError::RepositoryError(e.to_string()))?
534        .ok_or(TenantError::TenantNotFound)?;
535
536    // Validate tenant is active
537    if !tenant.is_active() {
538        return Err(TenantError::TenantInactive);
539    }
540
541    // Inject tenant context into request
542    request.extensions_mut().insert(TenantContext { tenant });
543
544    // Continue to next middleware/handler
545    Ok(next.run(request).await)
546}
547
548/// Error type for tenant isolation failures
549#[derive(Debug)]
550pub enum TenantError {
551    Unauthorized,
552    InvalidTenant,
553    TenantNotFound,
554    TenantInactive,
555    RepositoryError(String),
556}
557
558impl IntoResponse for TenantError {
559    fn into_response(self) -> Response {
560        let (status, message) = match self {
561            TenantError::Unauthorized => (
562                StatusCode::UNAUTHORIZED,
563                "Authentication required for tenant access",
564            ),
565            TenantError::InvalidTenant => (StatusCode::BAD_REQUEST, "Invalid tenant identifier"),
566            TenantError::TenantNotFound => (StatusCode::NOT_FOUND, "Tenant not found"),
567            TenantError::TenantInactive => (StatusCode::FORBIDDEN, "Tenant is inactive"),
568            TenantError::RepositoryError(_) => (
569                StatusCode::INTERNAL_SERVER_ERROR,
570                "Failed to validate tenant",
571            ),
572        };
573
574        (status, message).into_response()
575    }
576}
577
578// ============================================================================
579// Request ID Middleware (Phase 5C)
580// ============================================================================
581
582use uuid::Uuid;
583
584/// Request context with unique ID for tracing
585#[derive(Debug, Clone)]
586pub struct RequestId(pub String);
587
588impl Default for RequestId {
589    fn default() -> Self {
590        Self::new()
591    }
592}
593
594impl RequestId {
595    /// Generate a new request ID
596    pub fn new() -> Self {
597        Self(Uuid::new_v4().to_string())
598    }
599
600    /// Get the request ID as a string
601    pub fn as_str(&self) -> &str {
602        &self.0
603    }
604}
605
606/// Request ID middleware
607///
608/// Generates a unique request ID for each request and injects it into:
609/// - Request extensions (for use in handlers/logging)
610/// - Response headers (X-Request-ID)
611///
612/// If the request already has an X-Request-ID header, it will be used instead.
613///
614/// # Phase 5C: Request Tracing
615/// This middleware enables distributed tracing by:
616/// 1. Generating unique IDs for each request
617/// 2. Propagating IDs through the request lifecycle
618/// 3. Returning IDs in response headers
619/// 4. Supporting client-provided request IDs
620pub async fn request_id_middleware(mut request: Request, next: Next) -> Response {
621    // Check if request already has a request ID
622    let request_id = request
623        .headers()
624        .get("x-request-id")
625        .and_then(|v| v.to_str().ok())
626        .map_or_else(RequestId::new, |s| RequestId(s.to_string()));
627
628    // Store request ID in extensions
629    request.extensions_mut().insert(request_id.clone());
630
631    // Process request
632    let mut response = next.run(request).await;
633
634    // Add request ID to response headers
635    response
636        .headers_mut()
637        .insert("x-request-id", request_id.0.parse().unwrap());
638
639    response
640}
641
642// ============================================================================
643// Security Headers Middleware (Phase 5C)
644// ============================================================================
645
646/// Security headers configuration
647#[derive(Debug, Clone)]
648pub struct SecurityConfig {
649    /// Enable HSTS (HTTP Strict Transport Security)
650    pub enable_hsts: bool,
651    /// HSTS max age in seconds
652    pub hsts_max_age: u32,
653    /// Enable X-Frame-Options
654    pub enable_frame_options: bool,
655    /// X-Frame-Options value
656    pub frame_options: FrameOptions,
657    /// Enable X-Content-Type-Options
658    pub enable_content_type_options: bool,
659    /// Enable X-XSS-Protection
660    pub enable_xss_protection: bool,
661    /// Content Security Policy
662    pub csp: Option<String>,
663    /// CORS allowed origins
664    pub cors_origins: Vec<String>,
665    /// CORS allowed methods
666    pub cors_methods: Vec<String>,
667    /// CORS allowed headers
668    pub cors_headers: Vec<String>,
669    /// CORS max age
670    pub cors_max_age: u32,
671}
672
673#[derive(Debug, Clone)]
674pub enum FrameOptions {
675    Deny,
676    SameOrigin,
677    AllowFrom(String),
678}
679
680impl Default for SecurityConfig {
681    fn default() -> Self {
682        Self {
683            enable_hsts: true,
684            hsts_max_age: 31_536_000, // 1 year
685            enable_frame_options: true,
686            frame_options: FrameOptions::Deny,
687            enable_content_type_options: true,
688            enable_xss_protection: true,
689            csp: Some("default-src 'self'".to_string()),
690            cors_origins: vec!["*".to_string()],
691            cors_methods: vec![
692                "GET".to_string(),
693                "POST".to_string(),
694                "PUT".to_string(),
695                "DELETE".to_string(),
696            ],
697            cors_headers: vec!["Content-Type".to_string(), "Authorization".to_string()],
698            cors_max_age: 3600,
699        }
700    }
701}
702
703#[derive(Clone)]
704pub struct SecurityState {
705    pub config: SecurityConfig,
706}
707
708/// Security headers middleware
709///
710/// Adds security-related HTTP headers to all responses:
711/// - HSTS (Strict-Transport-Security)
712/// - X-Frame-Options
713/// - X-Content-Type-Options
714/// - X-XSS-Protection
715/// - Content-Security-Policy
716/// - CORS headers
717///
718/// # Phase 5C: Security Hardening
719/// This middleware provides defense-in-depth by:
720/// 1. Preventing clickjacking (X-Frame-Options)
721/// 2. Preventing MIME sniffing (X-Content-Type-Options)
722/// 3. Enforcing HTTPS (HSTS)
723/// 4. Preventing XSS (CSP, X-XSS-Protection)
724/// 5. Enabling CORS for controlled access
725pub async fn security_headers_middleware(
726    State(security_state): State<SecurityState>,
727    request: Request,
728    next: Next,
729) -> Response {
730    let mut response = next.run(request).await;
731    let headers = response.headers_mut();
732    let config = &security_state.config;
733
734    // HSTS
735    if config.enable_hsts {
736        headers.insert(
737            "strict-transport-security",
738            format!("max-age={}", config.hsts_max_age).parse().unwrap(),
739        );
740    }
741
742    // X-Frame-Options
743    if config.enable_frame_options {
744        let value = match &config.frame_options {
745            FrameOptions::Deny => "DENY",
746            FrameOptions::SameOrigin => "SAMEORIGIN",
747            FrameOptions::AllowFrom(origin) => origin,
748        };
749        headers.insert("x-frame-options", value.parse().unwrap());
750    }
751
752    // X-Content-Type-Options
753    if config.enable_content_type_options {
754        headers.insert("x-content-type-options", "nosniff".parse().unwrap());
755    }
756
757    // X-XSS-Protection
758    if config.enable_xss_protection {
759        headers.insert("x-xss-protection", "1; mode=block".parse().unwrap());
760    }
761
762    // Content-Security-Policy
763    if let Some(csp) = &config.csp {
764        headers.insert("content-security-policy", csp.parse().unwrap());
765    }
766
767    // CORS headers
768    headers.insert(
769        "access-control-allow-origin",
770        config.cors_origins.join(", ").parse().unwrap(),
771    );
772    headers.insert(
773        "access-control-allow-methods",
774        config.cors_methods.join(", ").parse().unwrap(),
775    );
776    headers.insert(
777        "access-control-allow-headers",
778        config.cors_headers.join(", ").parse().unwrap(),
779    );
780    headers.insert(
781        "access-control-max-age",
782        config.cors_max_age.to_string().parse().unwrap(),
783    );
784
785    response
786}
787
788// ============================================================================
789// IP Filtering Middleware (Phase 5C)
790// ============================================================================
791
792use crate::infrastructure::security::IpFilter;
793use std::net::SocketAddr;
794
795#[derive(Clone)]
796pub struct IpFilterState {
797    pub ip_filter: Arc<IpFilter>,
798}
799
800/// IP filtering middleware
801///
802/// Blocks or allows requests based on IP address rules.
803/// Supports both global and per-tenant IP filtering.
804///
805/// # Phase 5C: Access Control
806/// This middleware provides IP-based access control by:
807/// 1. Extracting client IP from request
808/// 2. Checking against global and tenant-specific rules
809/// 3. Blocking requests from unauthorized IPs
810/// 4. Supporting both allowlists and blocklists
811pub async fn ip_filter_middleware(
812    State(ip_filter_state): State<IpFilterState>,
813    request: Request,
814    next: Next,
815) -> Result<Response, IpFilterError> {
816    // Extract client IP address
817    let client_ip = request
818        .extensions()
819        .get::<axum::extract::ConnectInfo<SocketAddr>>()
820        .map(|connect_info| connect_info.0.ip())
821        .ok_or(IpFilterError::NoIpAddress)?;
822
823    // Check if this is a tenant-scoped request
824    let result = if let Some(tenant_ctx) = request.extensions().get::<TenantContext>() {
825        // Tenant-specific filtering
826        ip_filter_state
827            .ip_filter
828            .is_allowed_for_tenant(tenant_ctx.tenant_id(), &client_ip)
829    } else {
830        // Global filtering only
831        ip_filter_state.ip_filter.is_allowed(&client_ip)
832    };
833
834    // Block if not allowed
835    if !result.allowed {
836        return Err(IpFilterError::Blocked {
837            reason: result.reason,
838        });
839    }
840
841    // Allow request to proceed
842    Ok(next.run(request).await)
843}
844
845/// Error type for IP filtering failures
846#[derive(Debug)]
847pub enum IpFilterError {
848    NoIpAddress,
849    Blocked { reason: String },
850}
851
852impl IntoResponse for IpFilterError {
853    fn into_response(self) -> Response {
854        match self {
855            IpFilterError::NoIpAddress => (
856                StatusCode::BAD_REQUEST,
857                "Unable to determine client IP address",
858            )
859                .into_response(),
860            IpFilterError::Blocked { reason } => {
861                (StatusCode::FORBIDDEN, format!("Access denied: {reason}")).into_response()
862            }
863        }
864    }
865}
866
867#[cfg(test)]
868mod tests {
869    use super::*;
870    use crate::infrastructure::security::auth::Role;
871
872    #[test]
873    fn test_extract_bearer_token() {
874        let mut headers = HeaderMap::new();
875        headers.insert("authorization", "Bearer test_token_123".parse().unwrap());
876
877        let token = extract_token(&headers).unwrap();
878        assert_eq!(token, "test_token_123");
879    }
880
881    #[test]
882    fn test_extract_lowercase_bearer() {
883        let mut headers = HeaderMap::new();
884        headers.insert("authorization", "bearer test_token_123".parse().unwrap());
885
886        let token = extract_token(&headers).unwrap();
887        assert_eq!(token, "test_token_123");
888    }
889
890    #[test]
891    fn test_extract_plain_token() {
892        let mut headers = HeaderMap::new();
893        headers.insert("authorization", "test_token_123".parse().unwrap());
894
895        let token = extract_token(&headers).unwrap();
896        assert_eq!(token, "test_token_123");
897    }
898
899    #[test]
900    fn test_missing_auth_header() {
901        let headers = HeaderMap::new();
902        assert!(extract_token(&headers).is_err());
903    }
904
905    #[test]
906    fn test_empty_auth_header() {
907        let mut headers = HeaderMap::new();
908        headers.insert("authorization", "".parse().unwrap());
909        assert!(extract_token(&headers).is_err());
910    }
911
912    #[test]
913    fn test_bearer_with_empty_token() {
914        let mut headers = HeaderMap::new();
915        headers.insert("authorization", "Bearer ".parse().unwrap());
916        assert!(extract_token(&headers).is_err());
917    }
918
919    // -------------------------------------------------------------------------
920    // US-008: Agent Permission Enforcement
921    // -------------------------------------------------------------------------
922
923    #[test]
924    fn test_service_account_blocked_on_admin_paths() {
925        // ServiceAccount (agent API key) must NOT have Admin permission.
926        // The middleware uses require_permission(Permission::Admin) to block them.
927        let claims = Claims::new(
928            "agent-key".to_string(),
929            "tenant1".to_string(),
930            Role::ServiceAccount,
931            chrono::Duration::hours(1),
932        );
933        let ctx = AuthContext { claims };
934        assert!(
935            ctx.require_permission(Permission::Admin).is_err(),
936            "ServiceAccount must not have Admin permission"
937        );
938        // But it should still be able to read/write events
939        assert!(ctx.require_permission(Permission::Read).is_ok());
940        assert!(ctx.require_permission(Permission::Write).is_ok());
941    }
942
943    #[test]
944    fn test_admin_role_passes_admin_paths() {
945        // Admin role must have Admin permission — passes admin-only paths.
946        let claims = Claims::new(
947            "admin-user".to_string(),
948            "tenant1".to_string(),
949            Role::Admin,
950            chrono::Duration::hours(1),
951        );
952        let ctx = AuthContext { claims };
953        assert!(
954            ctx.require_permission(Permission::Admin).is_ok(),
955            "Admin must have Admin permission"
956        );
957    }
958
959    #[test]
960    fn test_developer_blocked_on_admin_paths() {
961        // Developer role cannot access admin-only paths.
962        let claims = Claims::new(
963            "dev-user".to_string(),
964            "tenant1".to_string(),
965            Role::Developer,
966            chrono::Duration::hours(1),
967        );
968        let ctx = AuthContext { claims };
969        assert!(
970            ctx.require_permission(Permission::Admin).is_err(),
971            "Developer must not have Admin permission"
972        );
973    }
974
975    #[test]
976    fn test_readonly_blocked_on_admin_paths() {
977        let claims = Claims::new(
978            "ro-user".to_string(),
979            "tenant1".to_string(),
980            Role::ReadOnly,
981            chrono::Duration::hours(1),
982        );
983        let ctx = AuthContext { claims };
984        assert!(ctx.require_permission(Permission::Admin).is_err());
985        assert!(ctx.require_permission(Permission::Read).is_ok());
986        assert!(ctx.require_permission(Permission::Write).is_err());
987    }
988
989    #[test]
990    fn test_is_admin_only_path_api_keys_create() {
991        // POST to api-keys is admin-only (agent keys must not self-replicate)
992        assert!(is_admin_only_path("/api/v1/auth/api-keys", "POST"));
993        // Other methods on api-keys are NOT admin-only (GET to list keys)
994        assert!(!is_admin_only_path("/api/v1/auth/api-keys", "GET"));
995        assert!(!is_admin_only_path("/api/v1/auth/api-keys", "DELETE"));
996    }
997
998    #[test]
999    fn test_is_admin_only_path_tenants() {
1000        // All tenant management paths are admin-only
1001        assert!(is_admin_only_path("/api/v1/tenants", "GET"));
1002        assert!(is_admin_only_path("/api/v1/tenants", "POST"));
1003        assert!(is_admin_only_path("/api/v1/tenants/some-id", "DELETE"));
1004        assert!(is_admin_only_path("/api/v1/tenants/some-id/config", "PUT"));
1005    }
1006
1007    #[test]
1008    fn test_is_admin_only_path_normal_paths() {
1009        // Normal event/query paths are NOT admin-only
1010        assert!(!is_admin_only_path("/api/v1/events", "POST"));
1011        assert!(!is_admin_only_path("/api/v1/events/query", "GET"));
1012        assert!(!is_admin_only_path("/api/v1/auth/me", "GET"));
1013        assert!(!is_admin_only_path("/api/v1/auth/login", "POST"));
1014        assert!(!is_admin_only_path("/api/v1/schemas", "GET"));
1015    }
1016
1017    #[test]
1018    fn test_auth_context_permissions() {
1019        let claims = Claims::new(
1020            "user1".to_string(),
1021            "tenant1".to_string(),
1022            Role::Developer,
1023            chrono::Duration::hours(1),
1024        );
1025
1026        let ctx = AuthContext { claims };
1027
1028        assert!(ctx.require_permission(Permission::Read).is_ok());
1029        assert!(ctx.require_permission(Permission::Write).is_ok());
1030        assert!(ctx.require_permission(Permission::Admin).is_err());
1031    }
1032
1033    #[test]
1034    fn test_auth_context_admin_permissions() {
1035        let claims = Claims::new(
1036            "admin1".to_string(),
1037            "tenant1".to_string(),
1038            Role::Admin,
1039            chrono::Duration::hours(1),
1040        );
1041
1042        let ctx = AuthContext { claims };
1043
1044        assert!(ctx.require_permission(Permission::Read).is_ok());
1045        assert!(ctx.require_permission(Permission::Write).is_ok());
1046        assert!(ctx.require_permission(Permission::Admin).is_ok());
1047    }
1048
1049    #[test]
1050    fn test_auth_context_readonly_permissions() {
1051        let claims = Claims::new(
1052            "readonly1".to_string(),
1053            "tenant1".to_string(),
1054            Role::ReadOnly,
1055            chrono::Duration::hours(1),
1056        );
1057
1058        let ctx = AuthContext { claims };
1059
1060        assert!(ctx.require_permission(Permission::Read).is_ok());
1061        assert!(ctx.require_permission(Permission::Write).is_err());
1062        assert!(ctx.require_permission(Permission::Admin).is_err());
1063    }
1064
1065    #[test]
1066    fn test_auth_context_tenant_id() {
1067        let claims = Claims::new(
1068            "user1".to_string(),
1069            "my-tenant".to_string(),
1070            Role::Developer,
1071            chrono::Duration::hours(1),
1072        );
1073
1074        let ctx = AuthContext { claims };
1075        assert_eq!(ctx.tenant_id(), "my-tenant");
1076    }
1077
1078    #[test]
1079    fn test_auth_context_user_id() {
1080        let claims = Claims::new(
1081            "my-user".to_string(),
1082            "tenant1".to_string(),
1083            Role::Developer,
1084            chrono::Duration::hours(1),
1085        );
1086
1087        let ctx = AuthContext { claims };
1088        assert_eq!(ctx.user_id(), "my-user");
1089    }
1090
1091    #[test]
1092    fn test_request_id_new() {
1093        let id1 = RequestId::new();
1094        let id2 = RequestId::new();
1095
1096        // IDs should be unique
1097        assert_ne!(id1.as_str(), id2.as_str());
1098        // IDs should be valid UUIDs (36 chars with hyphens)
1099        assert_eq!(id1.as_str().len(), 36);
1100    }
1101
1102    #[test]
1103    fn test_request_id_default() {
1104        let id = RequestId::default();
1105        assert_eq!(id.as_str().len(), 36);
1106    }
1107
1108    #[test]
1109    fn test_security_config_default() {
1110        let config = SecurityConfig::default();
1111
1112        assert!(config.enable_hsts);
1113        assert_eq!(config.hsts_max_age, 31536000);
1114        assert!(config.enable_frame_options);
1115        assert!(config.enable_content_type_options);
1116        assert!(config.enable_xss_protection);
1117        assert!(config.csp.is_some());
1118    }
1119
1120    #[test]
1121    fn test_frame_options_variants() {
1122        let deny = FrameOptions::Deny;
1123        let same_origin = FrameOptions::SameOrigin;
1124        let allow_from = FrameOptions::AllowFrom("https://example.com".to_string());
1125
1126        // Check that variants are distinct via debug formatting
1127        assert!(format!("{deny:?}").contains("Deny"));
1128        assert!(format!("{same_origin:?}").contains("SameOrigin"));
1129        assert!(format!("{allow_from:?}").contains("AllowFrom"));
1130    }
1131
1132    #[test]
1133    fn test_auth_error_from_validation_error() {
1134        let error = AllSourceError::ValidationError("test error".to_string());
1135        let auth_error = AuthError::from(error);
1136        assert!(format!("{auth_error:?}").contains("ValidationError"));
1137    }
1138
1139    #[test]
1140    fn test_rate_limit_error_display() {
1141        let error = RateLimitError::RateLimitExceeded {
1142            retry_after: 60,
1143            limit: 100,
1144        };
1145        assert!(format!("{error:?}").contains("RateLimitExceeded"));
1146
1147        let unauth_error = RateLimitError::Unauthorized;
1148        assert!(format!("{unauth_error:?}").contains("Unauthorized"));
1149    }
1150
1151    #[test]
1152    fn test_tenant_error_variants() {
1153        let errors = vec![
1154            TenantError::Unauthorized,
1155            TenantError::InvalidTenant,
1156            TenantError::TenantNotFound,
1157            TenantError::TenantInactive,
1158            TenantError::RepositoryError("test".to_string()),
1159        ];
1160
1161        for error in errors {
1162            // Ensure each variant can be debug-formatted
1163            let _ = format!("{error:?}");
1164        }
1165    }
1166
1167    #[test]
1168    fn test_ip_filter_error_variants() {
1169        let errors = vec![
1170            IpFilterError::NoIpAddress,
1171            IpFilterError::Blocked {
1172                reason: "blocked".to_string(),
1173            },
1174        ];
1175
1176        for error in errors {
1177            let _ = format!("{error:?}");
1178        }
1179    }
1180
1181    #[test]
1182    fn test_security_state_clone() {
1183        let config = SecurityConfig::default();
1184        let state = SecurityState {
1185            config: config.clone(),
1186        };
1187        let cloned = state.clone();
1188        assert_eq!(cloned.config.hsts_max_age, config.hsts_max_age);
1189    }
1190
1191    #[test]
1192    fn test_auth_state_clone() {
1193        let auth_manager = Arc::new(AuthManager::new("test-secret"));
1194        let state = AuthState { auth_manager };
1195        let cloned = state.clone();
1196        assert!(Arc::ptr_eq(&state.auth_manager, &cloned.auth_manager));
1197    }
1198
1199    #[test]
1200    fn test_rate_limit_state_clone() {
1201        use crate::infrastructure::security::rate_limit::RateLimitConfig;
1202        let rate_limiter = Arc::new(RateLimiter::new(RateLimitConfig::free_tier()));
1203        let state = RateLimitState { rate_limiter };
1204        let cloned = state.clone();
1205        assert!(Arc::ptr_eq(&state.rate_limiter, &cloned.rate_limiter));
1206    }
1207
1208    #[test]
1209    fn test_auth_skip_paths_contains_expected() {
1210        // Verify public paths are configured for auth/rate-limit skipping
1211        assert!(should_skip_auth("/health"));
1212        assert!(should_skip_auth("/metrics"));
1213        assert!(should_skip_auth("/api/v1/auth/register"));
1214        assert!(should_skip_auth("/api/v1/auth/login"));
1215        assert!(should_skip_auth("/api/v1/demo/seed"));
1216
1217        // Verify internal endpoints bypass auth (sentinel failover)
1218        assert!(should_skip_auth("/internal/promote"));
1219        assert!(should_skip_auth("/internal/repoint"));
1220        assert!(should_skip_auth("/internal/anything"));
1221
1222        // Verify protected paths are NOT in skip list
1223        assert!(!should_skip_auth("/api/v1/events"));
1224        assert!(!should_skip_auth("/api/v1/auth/me"));
1225        assert!(!should_skip_auth("/api/v1/tenants"));
1226    }
1227
1228    #[test]
1229    fn test_dev_mode_auth_context() {
1230        let ctx = dev_mode_auth_context();
1231
1232        // Dev user should have admin privileges
1233        assert_eq!(ctx.tenant_id(), "dev-tenant");
1234        assert_eq!(ctx.user_id(), "dev-user");
1235        assert!(ctx.require_permission(Permission::Admin).is_ok());
1236        assert!(ctx.require_permission(Permission::Read).is_ok());
1237        assert!(ctx.require_permission(Permission::Write).is_ok());
1238    }
1239
1240    #[test]
1241    fn test_dev_mode_disabled_by_default() {
1242        // Dev mode should be disabled by default (env var not set in tests)
1243        // Note: This test may fail if ALLSOURCE_DEV_MODE is set in the test environment
1244        // In a clean environment, dev mode is disabled
1245        let env_value = std::env::var("ALLSOURCE_DEV_MODE").unwrap_or_default();
1246        if env_value.is_empty() {
1247            assert!(!is_dev_mode());
1248        }
1249    }
1250}