From 409e67d57b8ef6bc3e4ac76eebd8d85b15e501fe Mon Sep 17 00:00:00 2001 From: bruno Date: Wed, 28 Oct 2026 13:45:27 -0400 Subject: [PATCH 1/4] CORS recherche/alertes (fusion #68/#69) : allowlist soeurs prod+dev explicites --- app/recherche.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/app/recherche.py b/app/recherche.py index 381c520..0f8ba5d 100644 --- a/app/recherche.py +++ b/app/recherche.py @@ -9,9 +9,14 @@ testables sans base ni serveur. recherche Gitea de la page ``/search`` (titres + corps, tous états, tous projets du workspace) et la regroupe par dépôt. - La CORS bornée n'autorise que ``GET /api/search``, et seulement - pour les origines sœurs résolues par ``app.config`` (explicite > - hôte de la requête > dérivation > défaut officiel — donc ``-dev`` - reste ``-dev``), jamais ``*``. Le middleware est un ASGI pur : + pour les origines sœurs officielles **prod ET dev, listées + explicitement** (``PROD_ORIGINS | DEV_ORIGINS``), **plus** les + origines résolues par ``app.config`` — jamais ``*``. Correctif + 2026-10-28 (fusion #68/#69, matrice constatée par Bruno) : + l'ancienne allowlist ne contenait que l'environnement résolu + (prod OU dev), donc une page de l'autre environnement voyait sa + section afficher « Injoignable depuis cet hôte (CORS ou réseau) ». + Le middleware est un ASGI pur : il répond lui-même aux preflights OPTIONS (qui ne portent jamais de cookies, donc qu'``AuthMiddleware`` refuserait sinon) et ajoute les en-têtes aux réponses de recherche, sans toucher aux autres @@ -97,7 +102,10 @@ def sister_origins( origin = origin_of((resolved or {}).get(key)) if origin: origins.add(origin) - return frozenset(origins) + # Correctif 2026-10-28 : prod ET dev toujours, quelle que soit + # la résolution — ``resolved`` n'ajoute que des origines de + # configuration sans jamais retirer une origine officielle. + return frozenset(origins) | PROD_ORIGINS | DEV_ORIGINS def cors_headers_for( From aa98eb6866503dcdb164f05220ff138e640be3f1 Mon Sep 17 00:00:00 2001 From: bruno Date: Wed, 28 Oct 2026 13:45:30 -0400 Subject: [PATCH 2/4] CORS alertes (fusion #69) : allowlist /api/events prod+dev --- app/main.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/app/main.py b/app/main.py index 95da1c7..124a365 100644 --- a/app/main.py +++ b/app/main.py @@ -438,10 +438,16 @@ class DracoAlertesCorsMiddleware(BaseHTTPMiddleware): if request.url.path != "/api/events": return await call_next(request) from app.config import resoudre_urls_draco + from app.recherche import DEV_ORIGINS, PROD_ORIGINS, origin_of urls = resoudre_urls_draco(request.headers.get("host")) - soeurs = {urls.sso_url, urls.flowdeck_url, urls.tiktak_url, - urls.public_url} + # Correctif CORS alertes (fusion #69, 2026-10-28) : comme + # pour /api/search, les origines sœurs prod ET dev sont + # listées explicitement — l'ancienne allowlist (environnement + # résolu seul) refusait les pages de l'autre environnement. + soeurs = ({origin_of(u) for u in (urls.sso_url, urls.flowdeck_url, + urls.tiktak_url, urls.public_url)} + | set(PROD_ORIGINS) | set(DEV_ORIGINS)) - {None} origine = request.headers.get("origin", "") if origine not in soeurs: return await call_next(request) From 7556cd0bee55b0d2d27c2de35d3df8b5b3c7857e Mon Sep 17 00:00:00 2001 From: bruno Date: Wed, 28 Oct 2026 13:45:33 -0400 Subject: [PATCH 3/4] Tests CORS recherche : prod+dev autorises, tiers refuse --- tests/test_recherche.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/tests/test_recherche.py b/tests/test_recherche.py index ecf6d1c..a484bcc 100644 --- a/tests/test_recherche.py +++ b/tests/test_recherche.py @@ -34,7 +34,13 @@ def test_origin_of(): def test_sister_origins_dev(): + # Correctif 2026-10-28 : 8 origines officielles, prod ET dev, + # quelle que soit la résolution. assert sister_origins(RESOLVED_DEV) == { + "https://og.dracodev.net", + "https://flowdeck.dracodev.net", + "https://tiktak.dracodev.net", + "https://kanbante.dracodev.net", "https://og-dev.dracodev.net", "https://flowdeck-dev.dracodev.net", "https://tiktak-dev.dracodev.net", @@ -47,7 +53,11 @@ def test_cors_headers(): assert headers["Access-Control-Allow-Origin"] == "https://tiktak-dev.dracodev.net" assert headers["Access-Control-Allow-Credentials"] == "true" assert cors_headers_for("https://evil.example", RESOLVED_DEV) is None - assert cors_headers_for("https://og.dracodev.net", RESOLVED_DEV) is None + assert cors_headers_for("https://og-dev.dracodev.net.evil.example", + RESOLVED_DEV) is None + # Autre environnement autorisé (correctif 2026-10-28). + assert cors_headers_for("https://og.dracodev.net", + RESOLVED_DEV) is not None def test_preflight_headers(): @@ -86,7 +96,14 @@ def test_groupes_vers_json_vide(): def test_sister_origins_hote_requete(): - # Configuration muette + hôte dev : le repli inclut les -dev. + # Correctif 2026-10-28 : configuration muette, quel que soit + # l'hôte de la requête, prod ET dev passent. assert "https://og-dev.dracodev.net" in sister_origins( {}, request_host="kanbante-dev.dracodev.net" ) + assert "https://og.dracodev.net" in sister_origins( + {}, request_host="kanbante-dev.dracodev.net" + ) + assert "https://og-dev.dracodev.net" in sister_origins( + {}, request_host="kanbante.dracodev.net" + ) From 3c4bbbf2f13f558e27a4652f17ea11e44747f2bf Mon Sep 17 00:00:00 2001 From: bruno Date: Wed, 28 Oct 2026 13:45:35 -0400 Subject: [PATCH 4/4] Tests CORS alertes : origine dev autorisee en preflight --- tests/test_draco_alertes.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/test_draco_alertes.py b/tests/test_draco_alertes.py index 15ad7e4..d2e66f0 100644 --- a/tests/test_draco_alertes.py +++ b/tests/test_draco_alertes.py @@ -44,3 +44,10 @@ def test_cors_events_soeur_preflight_get_et_tiers(client): headers={"Origin": "https://evil.example", "Access-Control-Request-Method": "GET"}) assert "access-control-allow-origin" not in r3.headers + # Correctif 2026-10-28 : une origine sœur dev passe aussi. + r4 = client.options("/api/events", + headers={"Origin": "https://tiktak-dev.dracodev.net", + "Access-Control-Request-Method": "GET"}) + assert r4.status_code == 200 + assert r4.headers.get("access-control-allow-origin") == "https://tiktak-dev.dracodev.net" + assert r4.headers.get("access-control-allow-credentials") == "true"