Paramètres : clé d'API FF Nancy (en-tête X-API-Key)

L'API api.ffnancy.fr exige désormais une clé (401 « Clé d'API requise »).
- Nouveau champ « Clé d'API » dans Paramètres → API FF Nancy, stocké dans
  data/.db-config.json (ffApiKey), jamais renvoyé au navigateur ; vide =
  conserver la clé enregistrée. Repli sur la variable FF_API_KEY.
- Tous les appels à l'API FF envoient l'en-tête X-API-Key (ff_get, proxy
  commandes-auto, publicités, /api/diag, test de connexion).
- Le test de connexion signale explicitement une clé manquante ou refusée.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KdY28Lq8Q8xX4RZsAvWRQ7
This commit is contained in:
Claude committed 2026-10-17 12:44:28 +00:00
1 parent 712174cd85
commit 4142e7c73e
11 files changed
+167 -59

No files matched your search

+11 -2
View File
@@ -116,6 +116,9 @@ pub async fn get_parametres_affichables(ctx: Ctx) -> AppResult<Value> {
"motDePasseEnregistre": config.qlik_password.as_deref().map(|p| !p.is_empty()).unwrap_or(false) || qlik_pwd_env,
},
"ffApiBaseUrl": config.ff_api_base_url.clone().unwrap_or_default(),
// La clé ne quitte pas le serveur : seulement l'indication qu'il y en a une
// (réglage enregistré, puis variable d'environnement `FF_API_KEY`).
"ffApiKeyEnregistree": crate::ff::api::resoudre_ff_api_key(config.ff_api_key.clone()).is_some(),
}))
}
@@ -219,12 +222,14 @@ pub async fn test_qlik_connection(ctx: Ctx) -> AppResult<Value> {
}
}
/// Enregistre l'URL de l'API FF Nancy. Chaîne vide = revenir au défaut.
/// Enregistre l'URL de l'API FF Nancy (chaîne vide = revenir au défaut) et sa
/// clé d'API (vide ou absente = conserver celle qui est enregistrée).
pub async fn save_ff_api_settings(ctx: Ctx) -> AppResult<Value> {
if let Err(e) = ctx.admin().await {
return Ok(refus(e));
}
let brut: String = ctx.arg::<Option<String>>(0)?.unwrap_or_default();
let cle: Option<String> = ctx.arg::<Option<String>>(1)?.map(|s| s.trim().to_string()).filter(|s| !s.is_empty());
let cleaned = brut.trim().trim_end_matches('/').to_string();
let lower = cleaned.to_ascii_lowercase();
if !cleaned.is_empty() && !(lower.starts_with("http://") || lower.starts_with("https://")) {
@@ -232,6 +237,9 @@ pub async fn save_ff_api_settings(ctx: Ctx) -> AppResult<Value> {
}
let mut patch = Map::new();
patch.insert("ffApiBaseUrl".into(), if cleaned.is_empty() { Value::Null } else { Value::String(cleaned.clone()) });
if let Some(cle) = cle {
patch.insert("ffApiKey".into(), Value::String(cle));
}
match write_config(patch) {
Ok(()) => {
// Sans cela, l'ancienne URL resterait servie jusqu'à 30 s après la sauvegarde.
@@ -252,7 +260,8 @@ pub async fn test_ff_api_connection(ctx: Ctx) -> AppResult<Value> {
return Ok(j!({ "success": false, "url": "", "error": e.message }));
}
let base: Option<String> = ctx.arg(0)?;
Ok(super::qlik_bridge::diagnostiquer_api_ff(base).await)
let cle: Option<String> = ctx.arg::<Option<String>>(1)?;
Ok(super::qlik_bridge::diagnostiquer_api_ff(base, cle).await)
}
#[cfg(test)]