orion-server 0.2.0

Declarative services runtime powered by dataflow-rs
use serde::{Deserialize, Serialize};

use crate::errors::OrionError;

/// Admin API authentication configuration.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct AdminAuthConfig {
    /// Enable authentication for admin API endpoints.
    pub enabled: bool,
    /// One or more API keys (multiple values support zero-downtime rotation).
    /// Empty strings are ignored.
    pub api_keys: Vec<String>,
    /// Header name to extract the API key from.
    /// When "Authorization" (default), expects `Bearer <token>` format.
    /// For other values (e.g. "X-API-Key"), expects the raw key value.
    pub header: String,
}

impl AdminAuthConfig {
    /// Return the effective list of API keys (non-empty `api_keys` entries).
    pub fn effective_keys(&self) -> Vec<&str> {
        self.api_keys
            .iter()
            .filter(|k| !k.is_empty())
            .map(String::as_str)
            .collect()
    }

    pub(crate) fn validate(&self, is_production: bool) -> Result<(), OrionError> {
        if self.enabled && self.effective_keys().is_empty() {
            return Err(OrionError::Config {
                message:
                    "At least one admin API key must be configured when admin auth is enabled. \
                     Set admin_auth.api_keys"
                        .to_string(),
            });
        }
        if !self.enabled {
            if is_production {
                return Err(OrionError::Config {
                    message: "admin_auth must be enabled when environment starts with 'prod'. \
                              Set admin_auth.enabled = true and configure admin_auth.api_keys"
                        .to_string(),
                });
            }
            tracing::warn!(
                "Admin auth is disabled. For production, enable admin_auth with a strong API key"
            );
        }
        Ok(())
    }
}

impl Default for AdminAuthConfig {
    fn default() -> Self {
        Self {
            enabled: false,
            api_keys: Vec::new(),
            header: "Authorization".to_string(),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_admin_auth_config_default() {
        let config = AdminAuthConfig::default();
        assert!(!config.enabled);
        assert!(config.api_keys.is_empty());
        assert_eq!(config.header, "Authorization");
    }

    #[test]
    fn test_effective_keys_returns_configured_keys() {
        let config = AdminAuthConfig {
            enabled: true,
            api_keys: vec!["key-a".to_string(), "key-b".to_string()],
            header: "Authorization".to_string(),
        };
        assert_eq!(config.effective_keys(), vec!["key-a", "key-b"]);
    }

    #[test]
    fn test_effective_keys_filters_empty_strings() {
        let config = AdminAuthConfig {
            enabled: true,
            api_keys: vec!["".to_string(), "key-a".to_string(), "".to_string()],
            header: "Authorization".to_string(),
        };
        assert_eq!(config.effective_keys(), vec!["key-a"]);
    }

    #[test]
    fn test_effective_keys_empty() {
        let config = AdminAuthConfig::default();
        assert!(config.effective_keys().is_empty());
    }
}