diff --git a/bin/fxt/src/auth/jwt_generator.rs b/bin/fxt/src/auth/jwt_generator.rs index 559065e05..f63b5ecef 100644 --- a/bin/fxt/src/auth/jwt_generator.rs +++ b/bin/fxt/src/auth/jwt_generator.rs @@ -47,15 +47,20 @@ pub struct JwtConfig { impl Default for JwtConfig { fn default() -> Self { + // Precedence: env var > config file > dev fallback + let resolved_secret = std::env::var("JWT_SECRET") + .ok() + .or_else(|| { + crate::config::TliConfig::load() + .ok() + .and_then(|c| c.jwt_secret) + }) + .unwrap_or_else(|| { + tracing::warn!("JWT_SECRET not set and no jwt_secret in config.toml - using development fallback"); + "test-secret-must-be-at-least-64-characters-long-for-security-validation-ok-1234567890".to_owned() + }); Self { - // Read JWT_SECRET from environment to match API Gateway configuration - // Falls back to test secret for development without .env - // Note: Production systems should use Vault via the API Gateway - secret: std::env::var("JWT_SECRET") - .unwrap_or_else(|_| { - tracing::warn!("JWT_SECRET not set - using development fallback secret"); - "test-secret-must-be-at-least-64-characters-long-for-security-validation-ok-1234567890".to_owned() - }), + secret: resolved_secret, issuer: std::env::var("JWT_ISSUER") .unwrap_or_else(|_| "foxhunt-api-gateway".to_owned()), audience: std::env::var("JWT_AUDIENCE") diff --git a/bin/fxt/src/config.rs b/bin/fxt/src/config.rs index 4da3824ec..41604c5c5 100644 --- a/bin/fxt/src/config.rs +++ b/bin/fxt/src/config.rs @@ -10,7 +10,7 @@ //! ## Example Config File //! ```toml //! # ~/.foxhunt/config.toml -//! api_gateway_url = "http://localhost:50051" +//! api_gateway_url = "https://api.fxhnt.ai" //! log_level = "info" //! token_storage = "keyring" //! ``` @@ -96,13 +96,17 @@ pub struct TliConfig { #[serde(default = "default_token_storage")] pub token_storage: String, + /// JWT secret for token signing (must match API Gateway's JWT_SECRET) + #[serde(default, skip_serializing_if = "Option::is_none")] + pub jwt_secret: Option, + /// Broker connectivity settings #[serde(default)] pub broker: BrokerConfig, } fn default_api_gateway_url() -> String { - "http://localhost:50051".to_owned() + "https://api.fxhnt.ai".to_owned() } fn default_log_level() -> String { @@ -119,6 +123,7 @@ impl Default for TliConfig { api_gateway_url: default_api_gateway_url(), log_level: default_log_level(), token_storage: default_token_storage(), + jwt_secret: None, broker: BrokerConfig::default(), } } @@ -165,6 +170,7 @@ impl TliConfig { /// api_gateway_url: "http://localhost:50051".to_string(), /// log_level: "debug".to_string(), /// token_storage: "keyring".to_string(), + /// jwt_secret: None, /// broker: BrokerConfig::default(), /// }; /// config.save().unwrap(); @@ -203,7 +209,7 @@ mod tests { fn test_default_config() { // Test that Default trait provides correct values let config = TliConfig::default(); - assert_eq!(config.api_gateway_url, "http://localhost:50051"); + assert_eq!(config.api_gateway_url, "https://api.fxhnt.ai"); assert_eq!(config.log_level, "info"); assert_eq!(config.token_storage, "keyring"); } @@ -214,6 +220,7 @@ mod tests { api_gateway_url: "http://example.com:50051".to_owned(), log_level: "debug".to_owned(), token_storage: "file".to_owned(), + jwt_secret: None, broker: BrokerConfig::default(), }; @@ -238,7 +245,7 @@ mod tests { // Test that empty TOML string deserializes to defaults let empty_toml = ""; let config: TliConfig = toml::from_str(empty_toml).unwrap(); - assert_eq!(config.api_gateway_url, "http://localhost:50051"); + assert_eq!(config.api_gateway_url, "https://api.fxhnt.ai"); assert_eq!(config.log_level, "info"); assert_eq!(config.token_storage, "keyring"); }