use std::net::SocketAddr; use axum::Router; use axum::http::{HeaderName, HeaderValue}; use k_core::http::server::{ServerConfig, apply_standard_middleware}; use tokio::net::TcpListener; use tower_http::cors::{AllowHeaders, AllowMethods, AllowOrigin, CorsLayer}; use crate::config::Config; use crate::routes; use crate::state::AppState; pub async fn build_and_serve(state: AppState, config: &Config) -> anyhow::Result<()> { let server_config = ServerConfig { cors_origins: config.cors_allowed_origins.clone(), }; let app = Router::new() .nest("/api/v1", routes::api_v1_router()) .with_state(state); let app = apply_standard_middleware(app, &server_config); // Wrap with an outer CorsLayer that includes the custom password headers. // Being outermost it handles OPTIONS preflights before k_core's inner layer. let origins: Vec = config .cors_allowed_origins .iter() .filter_map(|o| o.parse().ok()) .collect(); let cors = CorsLayer::new() .allow_origin(AllowOrigin::list(origins)) .allow_methods(AllowMethods::any()) .allow_headers(AllowHeaders::list([ axum::http::header::AUTHORIZATION, axum::http::header::CONTENT_TYPE, HeaderName::from_static("x-channel-password"), HeaderName::from_static("x-block-password"), ])); let app = app.layer(cors); let addr: SocketAddr = format!("{}:{}", config.host, config.port).parse()?; let listener = TcpListener::bind(addr).await?; tracing::info!("🚀 API server running at http://{}", addr); tracing::info!("🔒 Authentication mode: JWT (Bearer token)"); #[cfg(feature = "auth-jwt")] tracing::info!(" ✓ JWT auth enabled"); #[cfg(feature = "auth-oidc")] tracing::info!(" ✓ OIDC integration enabled (stateless cookie state)"); tracing::info!("📝 API endpoints available at /api/v1/..."); axum::serve(listener, app).await?; Ok(()) }