diff --git a/.env.example b/.env.example index a8a157a..7460b37 100644 --- a/.env.example +++ b/.env.example @@ -20,6 +20,27 @@ GITHUB_OAUTH_CLIENT_SECRET= # Exemple : OAUTH_REDIRECT_URI=https://flowdeck.dracodev.net/auth/callback OAUTH_REDIRECT_URI= +# ── URLs publiques dracodev — dev/prod (v7.82.0) ── +# AUCUN nom public n'est codé en dur dans le code vivant : tout passe +# par ces variables (résolution : env > config > défaut). Les DÉFAUTS +# sont les noms OFFICIELS (production, sans suffixe). Un environnement +# DEV déclare ses noms en -dev ici — le .env survit aux déploiements +# par release (passé par environment:/env_file: au conteneur). +# Laisser vide = défaut officiel indiqué en commentaire. +DRACO_SSO_URL= # ObsiGate, émettrice SSO — défaut https://og.dracodev.net ; dev : https://og-dev.dracodev.net +DRACO_AGENT_URL= # Service d'agents Spark — défaut https://agent.dracodev.net ; dev : https://agent-dev.dracodev.net +TIKTAK_URL= # TikTak (preset MCP, barre, refs) — défaut https://tiktak.dracodev.net ; dev : https://tiktak-dev.dracodev.net +KANBANTE_URL= # Kanbante (barre, refs) — défaut https://kanbante.dracodev.net ; dev : https://kanbante-dev.dracodev.net +FLOWDECK_URL= # URL publique de CETTE instance — défaut : dérivée d'OAUTH_REDIRECT_URI, sinon https://flowdeck.dracodev.net ; dev : https://flowdeck-dev.dracodev.net +# SSO draco (Fusion ACC-3.2) — secret partagé avec l'émettrice ObsiGate. +DRACO_SESSION_SECRET= +DRACO_SESSION_SECRET_PREV= +DRACO_SSO_MACHINE_KEY= +# Événements vers le journal du service d'agents (Fusion, Lot 2). +# AGENT_EVENEMENTS_URL vide = dérivée de DRACO_AGENT_URL (+ /api/v1/evenements). +AGENT_EVENEMENTS_URL= +AGENT_EVENEMENTS_CLE= + # ── Connecteurs OAuth (v7.56.0) — Google / Microsoft 365 ── # Vidés = connecteur « non configuré » (le menu + affiche la raison). # Le redirect URI à enregistrer est dérivé d'APP_BASE_URL : diff --git a/CHANGELOG.md b/CHANGELOG.md index 36986a4..abeff56 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,44 @@ # Changelog - FlowDeck +## v7.82.0 (2026-10-25) — Noms publics dracodev par configuration (dev/prod) + +- **Aucun nom public d'une autre application n'est plus codé en dur** + dans le code vivant : nouveau module `app/services/draco_urls.py`, + source unique qui résout chaque URL depuis l'environnement + (env > `app/config.py` > défaut). Les **défauts sont les noms + officiels** (production) ; un environnement dev déclare ses noms + en `-dev` dans son `.env` (qui survit aux déploiements par release). + Variables **ajoutées** : `DRACO_AGENT_URL`, `TIKTAK_URL`, + `KANBANTE_URL`, `FLOWDECK_URL`, `AGENT_EVENEMENTS_URL` (documentée). + Variables **réutilisées** : `DRACO_SSO_URL` (ObsiGate/SSO, déjà en + service), `OAUTH_REDIRECT_URI` (son origine sert d'URL publique à + l'instance si `FLOWDECK_URL` est vide), `AGENT_EVENEMENTS_URL` + + `AGENT_EVENEMENTS_CLE`, `GITEA_URL`, `APP_BASE_URL`. +- **Surfaces corrigées** : CSP (`connect-src`/`frame-src` du service + d'agents dérivés de `DRACO_AGENT_URL`) ; bouton « Login with + ObsiGate (SSO) » de la page de connexion (émettrice + retour + dérivés de la config) ; émission d'événements (endpoint dérivé de + la base de l'agent si `AGENT_EVENEMENTS_URL` est vide) ; révocations + SSO (`DRACO_SSO_URL`) ; preset MCP TikTak des connecteurs et du + panneau agent (URL injectée par l'hôte) ; User-Agent du fetcher OG + (URL publique de l'instance) ; barre commune `_draco_bar.html` + (href injectés par l'hôte via `draco_urls()`) ; panneau Spark + vendangé (contrat d'injection `window.DRACO_PANNEAU_CONFIG.agent_url` + rendu par `base.html`, repli = nom officiel seulement ; déduction + de l'app hôte insensible au suffixe `-dev`) ; liens Gitea du + dashboard et des réglages (`GITEA_URL`). +- **Références croisées** : le parseur de liens profonds reconnaît + l'hôte **configuré** de chaque app **et** l'hôte officiel, et l'URL + d'affichage est reconstruite avec l'hôte configuré (chemin, requête + et fragment intacts). **Aucune donnée migrée** : `target_url` reste + stockée telle que collée par l'utilisateur ; seule l'affichage est + recalculée. +- **Extension Web Clipper** : les manifests autorisent aussi + l'hôte dev (`flowdeck-dev.dracodev.net`) à côté de l'officiel. +- Tests : nouvelle suite `tests/test_draco_urls_devprod.py` + (défauts officiels sans `-dev`, surcharges dev, CSP, liens profonds + dev + officiels, URL d'affichage, preset TikTak, bouton SSO). + ## v7.81.0 (2026-10-22) — Panneau Spark V3 : la console en surimpression pleine page - **Fini le nouvel onglet** (demande de Bruno) : le bouton diff --git a/README.md b/README.md index 7595806..b39c0f9 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,25 @@ APP_PORT=8080 DATABASE_URL=sqlite:////data/flowdeck.db ``` +### Environnements dev/prod (v7.82.0) + +Aucun nom public d'une application dracodev n'est codé en dur : les +URL des autres apps (ObsiGate/SSO, service d'agents, TikTak, Kanbante) +et l'URL publique de Flowdeck se résolvent depuis le `.env` (voir la +section « URLs publiques dracodev » de `.env.example` et +`app/services/draco_urls.py`). Les valeurs par défaut sont les noms +**officiels** (production) ; un environnement de développement déclare +les noms en `-dev` dans son `.env`, par exemple +`DRACO_SSO_URL=https://og-dev.dracodev.net`, +`DRACO_AGENT_URL=https://agent-dev.dracodev.net`, +`TIKTAK_URL=https://tiktak-dev.dracodev.net`, +`KANBANTE_URL=https://kanbante-dev.dracodev.net` et +`OAUTH_REDIRECT_URI=https://flowdeck-dev.dracodev.net/auth/callback` +(dont l'origine sert d'URL publique à l'instance, sauf si +`FLOWDECK_URL` est posée). La CSP, la barre commune, le panneau Spark, +le bouton SSO, le preset MCP TikTak et les références croisées +suivent automatiquement. + ## Tests ```bash diff --git a/VERSION b/VERSION index 80af335..de7c343 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -7.81.0 +7.82.0 diff --git a/app/config.py b/app/config.py index 9c498b9..872e68d 100644 --- a/app/config.py +++ b/app/config.py @@ -147,6 +147,28 @@ class Settings(BaseSettings): github_token: str = "" # PAT GitHub : 30 req/min au lieu de 10 web_fetch_max_chars: int = 20000 # texte renvoyé par fetch_url + # ── URLs publiques dracodev (dev/prod, v7.82.0) ── + # AUCUN nom public d'une autre app n'est codé en dur dans le code + # vivant : chaque URL se résout ici (voir app/services/draco_urls.py). + # Les défauts sont les noms OFFICIELS (production) ; un environnement + # dev déclare les noms en `-dev` dans son .env, par exemple : + # DRACO_SSO_URL=https://og-dev.dracodev.net + # DRACO_AGENT_URL=https://agent-dev.dracodev.net + # TIKTAK_URL=https://tiktak-dev.dracodev.net + # KANBANTE_URL=https://kanbante-dev.dracodev.net + # FLOWDECK_URL=https://flowdeck-dev.dracodev.net + draco_sso_url: str = "https://og.dracodev.net" # ObsiGate (émettrice SSO) + draco_agent_url: str = "https://agent.dracodev.net" # service d'agents (Spark) + tiktak_url: str = "https://tiktak.dracodev.net" + kanbante_url: str = "https://kanbante.dracodev.net" + # URL publique de CETTE instance ; vide = dérivée de + # OAUTH_REDIRECT_URI, puis d'APP_BASE_URL (si non locale), puis du + # nom officiel. + flowdeck_url: str = "" + # AGENT_EVENEMENTS_URL (déjà en service) : vide = dérivée de + # DRACO_AGENT_URL + /api/v1/evenements. + agent_evenements_url: str = "" + # ── Vues Notion : géocodage + plafonds (§8.2, §13.4, §20) ── GEOCODER_PROVIDER: str = "none" # none | nominatim | custom GEOCODER_ENDPOINT: str = "https://nominatim.openstreetmap.org" diff --git a/app/middleware/security.py b/app/middleware/security.py index 6e67bcc..4724959 100644 --- a/app/middleware/security.py +++ b/app/middleware/security.py @@ -133,9 +133,14 @@ class ContentSecurityPolicyMiddleware(BaseHTTPMiddleware): # Exception Fusion (2026-10-21) : le panneau Spark parle au # service d'agents commun (premier parti, même programme) — # seul hôte externe nommé, rien d'autre ne s'ouvre. - "connect-src 'self' https://agent.dracodev.net ws://{host} wss://{host}; " + # v7.82.0 (dev/prod) : cet hôte n'est plus figé — {agent} est + # l'origine résolue par la config (DRACO_AGENT_URL ; défaut = + # nom officiel, un dev déclare son agent-dev dans le .env). + # Même origine pour frame-src : la console du service est + # encadrée en iframe par le panneau (surimpression v3). + "connect-src 'self' {agent} ws://{host} wss://{host}; " "media-src 'self' blob:; " - "frame-src 'self'; " + "frame-src 'self' {agent}; " "object-src 'none'; " "base-uri 'self'; " "form-action 'self'; " @@ -155,8 +160,12 @@ class ContentSecurityPolicyMiddleware(BaseHTTPMiddleware): # on retire quand même tout caractère hors base URL par sécurité. host = re.sub(r"[^0-9A-Za-z.\-:\[\]]", "", request.headers.get("host", "")) + from app.services import draco_urls + + agent = re.sub(r"[^0-9A-Za-z.\-:/]", "", + draco_urls.origine(draco_urls.agent_url())) response.headers[self.CSP_HEADER] = self.CSP_VALUE.format( - nonce=nonce, host=host + nonce=nonce, host=host, agent=agent ) return response diff --git a/app/routers/auth.py b/app/routers/auth.py index d9755e6..252d35a 100644 --- a/app/routers/auth.py +++ b/app/routers/auth.py @@ -32,12 +32,32 @@ def get_redirect_uri(request: Request) -> str: host = fwd_host.split(",")[0].strip() or request.headers.get("host", "localhost:8080") return f"{scheme}://{host}/auth/callback" +def _sso_login_url() -> str: + """URL du bouton « Login with ObsiGate (SSO) », résolue par la config. + + Émettrice = DRACO_SSO_URL (défaut : nom officiel) ; retour = + URL publique de cette instance (FLOWDECK_URL / OAUTH_REDIRECT_URI / + APP_BASE_URL, défaut : nom officiel) + ``/auth/draco``. Aucun nom + public figé : un dev déclare ses noms en ``-dev`` dans le .env. + """ + from urllib.parse import quote + + from app.services import draco_urls + + retour = f"{draco_urls.flowdeck_url()}/auth/draco" + return (f"{draco_urls.obsigate_url()}/sso/authorize" + f"?return_to={quote(retour, safe='')}") + + def _with_nonce(html: str) -> str: """A20 : injecte le nonce CSP au moment du rendu. `LOCAL_LOGIN_HTML` est une constante de module — le nonce, lui, est par - requête, donc il ne peut être figé qu'ici. + requête, donc il ne peut être figé qu'ici. Même traitement pour l'URL + du bouton SSO ObsiGate (placeholder ``__SSO_LOGIN_URL__``), qui + dépend de la config dev/prod courante. """ + html = html.replace("__SSO_LOGIN_URL__", _sso_login_url()) return html.replace(" diff --git a/app/templates/base.html b/app/templates/base.html index 54c3dac..360d1d2 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -2871,6 +2871,11 @@ +{# Panneau Spark vendangé (dracodev-commons) : contrat d'injection — + l'hôte fournit l'URL du service d'agents depuis SA config + (DRACO_AGENT_URL ; défaut = nom officiel, -dev via le .env). + Le JS ne contient plus aucun nom public en dur. #} +