fix(fxt): read JWT secret from config.toml for gateway auth

The fxt client was using a hardcoded dev fallback secret for JWT signing,
causing InvalidSignature errors against the API gateway. Now reads
jwt_secret from ~/.foxhunt/config.toml with fallback chain:
env var > config file > dev secret.

Also updates default api_gateway_url to https://api.fxhnt.ai.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
jgrusewski
2026-03-03 10:31:04 +01:00
parent 1b7f7683bd
commit 72f092e0e5
2 changed files with 24 additions and 12 deletions

View File

@@ -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")

View File

@@ -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<String>,
/// 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");
}