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