Compare commits

...
3 Commits
Author SHA1 Message Date
Spark 03a1c9f6f5 fix: entrée SSO — retour honoré à la restauration de session et nom du compte poussé à la barre (BUG-123, BUG-124)
CI / lint (pull_request) Successful in 1m24s
CI / security (pull_request) Successful in 17s
CI / test (pull_request) Successful in 3m38s
CI / build (pull_request) Successful in 12s
CI / e2e (pull_request) Failing after 15m36s
BUG-123 : /sso/authorize déposait l'utilisateur déjà connecté
sur l'accueil ObsiGate sans suivre le return_to — seul
saveToken() (login frais) l'honorait. initAuth() l'honore
désormais sur ses deux chemins de restauration au démarrage ;
les refresh en cours de session ne redirigent jamais.

BUG-124 : après un login SPA, le menu compte de la barre
affichait « Compte » jusqu'au rechargement. Barre commune
re-vendangée en v0.7.0 (DRACO_BAR.setUser, commons PR #15) ;
renderUserSection() et le fetch d'amorçage poussent le nom.

Garde : tests/test_draco_barre.py (nouveau test épinglé).
Suite complète : 1977 passed, 1 skipped ; ruff et mypy 0.
2026-11-08 17:49:27 +00:00
bruno 045abd6a3c Merge pull request 'ObsiGate 2.86.1 — barre v0.6.0 : entrée « Administration » du menu compte (fusion #142)' (#72) from menu-compte-admin-v060 into main
CI / lint (push) Successful in 1m19s
CI / security (push) Successful in 19s
CI / test (push) Successful in 3m37s
CI / build (push) Successful in 11s
CI / e2e (push) Failing after 15m5s
2026-11-06 15:27:43 -05:00
Spark 06855937bd feat: barre v0.6.0 — entrée « Administration » du menu compte affirmée par le rôle global (fusion #142)
CI / lint (pull_request) Successful in 1m31s
CI / security (pull_request) Successful in 29s
CI / test (pull_request) Successful in 3m40s
CI / build (pull_request) Successful in 12s
CI / e2e (pull_request) Failing after 14m47s
2026-11-06 20:21:31 +00:00
18 changed files with 342 additions and 14 deletions
Regular → Executable
View File
Regular → Executable
View File
+14 -1
View File
@@ -6,7 +6,7 @@ Format basé sur [Keep a Changelog](https://keepachangelog.com/fr/1.1.0/),
et [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
> **En cours de développement** : les changements à venir sont listés dans la section
> [Unreleased](#unreleased). La dernière version livrée est **2.86.0**.
> [Unreleased](#unreleased). La dernière version livrée est **2.86.2**.
---
@@ -14,6 +14,19 @@ et [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
---
## [2.86.2] — 2026-11-08
- **BUG-123 — Aller-retour SSO refermé aussi quand la session ObsiGate existe déjà** (chantier « entrée SSO de bout en bout », écart constaté par Bruno en test réel le 2026-11-08) : `GET /sso/authorize?return_to=…` redirige vers `/?return_to=…`, mais le retour n'était honoré que par `AuthManager.saveToken()` (login frais, MFA, WebAuthn). Pour un utilisateur déjà connecté chez ObsiGate, `initAuth()` restaurait la session — jeton encore valide ou refresh silencieux — et affichait l'accueil sans jamais suivre le `return_to` : l'utilisateur restait chez ObsiGate, et l'application sœur ne recevait jamais son pont (aucune session locale). Correctif (`frontend/js/auth.js`) : `_honorReturnTo()` signale désormais la redirection (valeur de retour), et les deux chemins de restauration d'`initAuth()` l'invoquent avant d'afficher l'application. Les refresh silencieux **en cours de session** ne sont pas touchés : ils ne doivent jamais rediriger. Même liste blanche qu'avant (https + `*.dracodev.net`, jamais l'hôte courant). Garde : `tests/test_draco_barre.py` (`test_barre_v070_setuser_et_retour_sso_restauration`) ; chaîne complète prouvée en navigateur réel dans une pile locale aux noms publics (dossier `preuve-entree-sso`). Fiche : `docs/features/entree-sso-bug-123-124.md`.
- **BUG-124 — Le nom du compte rejoint la barre commune dès le login, sans rechargement** (même chantier) : après un login dans la SPA, le menu compte de la barre affichait l'en-tête générique « Compte » et l'avatar neutre « ✦ » jusqu'au rechargement forcé — `renderUserSection()` poussait le rôle (`setAdmin`) mais jamais le nom, et le seul poseur d'identité était le fetch d'amorçage d'`index.html`, limité aux sessions déjà présentes au chargement. Barre commune re-vendangée **verbatim** en **v0.7.0** depuis dracodev-commons (PR commons #15) : nouvelle API `DRACO_BAR.setUser(user)` qui resynchronise l'avatar (initiale, infobulle) et l'en-tête du menu compte immédiatement. Branchement hôte : `renderUserSection()` appelle `setUser(name)` pour toute session établie (et `setUser(null)` sans identité), le fetch d'amorçage d'`index.html` également. Garde : `tests/test_draco_barre.py` (même test).
---
## [2.86.1] — 2026-11-06
- **Barre commune v0.6.0 — entrée « Administration » du menu compte** (fusion #142, demande de Bruno) : barre re-vendangée **verbatim** depuis dracodev-commons (PR commons #14) — `frontend/draco-barre.js` et `frontend/draco-barre.css`. Le menu compte gagne un cinquième lien Hub, « Administration » → `<hub>/admin` (le Hub redirige vers `/admin/utilisateurs`), après le séparateur et avant « Déconnexion », à **visibilité conditionnelle** : l'entrée n'apparaît que si l'hôte affirme que le compte courant est administrateur de suite (`DRACO_BAR.setAdmin`). Branchement hôte : `frontend/index.html` affirme le statut depuis la réponse de `/api/auth/me` déjà chargée pour l'avatar — `setAdmin(u.role === "admin")`. Le rôle global du compte chez ObsiGate est la définition même de l'administrateur de suite (c'est lui que la garde `/admin/*` du Hub vérifie, via ce même endpoint) : ObsiGate le connaît nativement. Tout autre rôle, ou échec de l'appel : aucune affirmation, l'entrée reste masquée. Garde : `tests/test_draco_barre.py` (`test_barre_v060_entree_administration`).
---
## [2.86.0] — 2026-11-06
- **BUG-122 — Déconnexion globale navigable : `GET /sso/logout`** (fusion #141) : le bouton « Déconnexion » du menu compte de la barre commune ne faisait rien dans les applications sœurs — la barre relaie l'action à l'hôte, et la déconnexion globale d'ObsiGate (ACC-3.1, #216) n'était appelable qu'en same-origin depuis l'interface d'ObsiGate (`POST /api/auth/logout`). Nouvelle route `GET /sso/logout?return_to=`, symétrique de `/sso/authorize` : elle exécute la déconnexion globale — logique extraite dans `apply_global_logout()` (`backend/auth/router.py`), désormais partagée par les deux entrées (jetons natifs refresh/access révoqués, `jti` du cookie `draco_session` révoqué, cookie effacé sur le domaine parent) — puis redirige vers le `return_to` validé par la liste blanche existante (`validate_return_to` : https + `*.dracodev.net`, repli « / »). Idempotente sans session. Les applications sœurs branchent leur action `deconnexion` sur cette route (leur session locale est détruite d'abord chez celles qui en ont une) — et la barre d'ObsiGate elle-même aussi (`frontend/index.html`, retour à sa racine). Tests : `tests/test_draco_session.py` (+3 : révocation et redirection, liste blanche, idempotence), `tests/test_draco_barre.py` (+1 : branchement de la barre d'ObsiGate). Fiche : `docs/features/deconnexion-globale-bug-122.md`.
+3 -3
View File
@@ -4,7 +4,7 @@
**Porte d'entrée web ultra-léger pour vos vaults Obsidian** — Accédez, naviguez et recherchez dans toutes vos notes Obsidian depuis n'importe quel appareil via une interface web moderne et responsive.
[![Version](https://img.shields.io/badge/Version-2.86.0-blue.svg)]()
[![Version](https://img.shields.io/badge/Version-2.86.2-blue.svg)]()
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Docker](https://img.shields.io/badge/Docker-Ready-blue.svg)](https://www.docker.com/)
[![Python](https://img.shields.io/badge/Python-3.11+-green.svg)](https://www.python.org/)
@@ -977,8 +977,8 @@ Ce projet est sous licence **MIT** — voir le fichier [LICENSE](LICENSE) pour l
## 📝 Changelog
Consultez le [CHANGELOG.md](./CHANGELOG.md) pour l'historique complet de toutes les versions (v1.0.0 → v2.86.0).
Consultez le [CHANGELOG.md](./CHANGELOG.md) pour l'historique complet de toutes les versions (v1.0.0 → v2.86.2).
---
*Projet : ObsiGate | Version : 2.86.0 | Dernière mise à jour : Septembre 2026*
*Projet : ObsiGate | Version : 2.86.2 | Dernière mise à jour : Septembre 2026*
+3 -3
View File
@@ -2,7 +2,7 @@
**Ultra-light web gateway for your Obsidian vaults** — Access, browse, and search all your Obsidian notes from any device via a modern, responsive web interface.
[![Version](https://img.shields.io/badge/Version-2.86.0-blue.svg)]()
[![Version](https://img.shields.io/badge/Version-2.86.2-blue.svg)]()
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Docker](https://img.shields.io/badge/Docker-Ready-blue.svg)](https://www.docker.com/)
[![Python](https://img.shields.io/badge/Python-3.11+-green.svg)](https://www.python.org/)
@@ -1152,8 +1152,8 @@ This project is licensed under the **MIT License** - see the [LICENSE](LICENSE)
## 📝 Changelog
See [CHANGELOG.md](./CHANGELOG.md) for the complete version history (v1.0.0 → v2.86.0).
See [CHANGELOG.md](./CHANGELOG.md) for the complete version history (v1.0.0 → v2.86.2).
---
*Project: ObsiGate | Version: 2.86.0 | Last updated: September 2026*
*Project: ObsiGate | Version: 2.86.2 | Last updated: September 2026*
+1 -1
View File
@@ -1 +1 @@
2.86.0
2.86.2
+1 -1
View File
@@ -2626,7 +2626,7 @@ dependencies = [
[[package]]
name = "obsigate-desktop"
version = "2.86.0"
version = "2.86.2"
dependencies = [
"chrono",
"env_logger",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "obsigate-desktop"
version = "2.86.0"
version = "2.86.2"
description = "ObsiGate Desktop — Porte d'entrée native pour vos vaults Obsidian"
authors = ["Bruno Charest"]
edition = "2021"
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://raw.githubusercontent.com/nicedoc/obsigate/main/desktop/tauri.conf.schema.json",
"productName": "ObsiGate",
"version": "2.86.0",
"version": "2.86.2",
"identifier": "com.obsigate.desktop",
"build": {
"frontendDist": "../frontend",
+3
View File
@@ -228,6 +228,8 @@ Avant de corriger quoi que ce soit, un agent IA doit :
| *BUG-119* | Panneau Spark : le « mode console » (surimpression iframe de la console du service d'agents) est bloqué par le navigateur (« This content is blocked ») | 🟢 corrigé | P1 | ⚙️ backend | IA | `backend/main.py` (`SecurityHeadersMiddleware`) | Panneau Spark → « Console ⤢ » : l'iframe reste sur la page d'erreur du navigateur | `frame-src 'self' blob: https://agent.dracodev.net` dans la CSP servie ; test `tests/test_security_headers.py::test_csp_frame_src_allows_agent_console` | Cause : `frame-src` n'autorisait pas l'origine du service d'agents, pourtant déjà présente dans `connect-src` (le panneau texte fonctionnait). Aucune autre directive modifiée. Redéploiement Docker requis |
| *BUG-120* | Menu inter-apps : sur l'environnement dev, les URL sœurs servies (`window.DRACO_URLS` : ObsiGate, Flowdeck, TikTak, Kanbante) restent en noms **prod** alors que l'URL de l'agent est bien en `-dev` | 🟢 corrigé | P1 | ⚙️ backend | IA | `backend/draco_config.py` (`app_url`, `_suffixe_environnement`) | Sur og-dev : `window.DRACO_URLS` donne `agent: https://agent-dev.dracodev.net` mais `tiktak: https://tiktak.dracodev.net` (prod) — le `.env` dev ne déclare pas les quatre `DRACO_<APP>_URL` | `backend/draco_config.py` : résolution sœur en 3 temps — explicite, puis **dérivation d'environnement** depuis l'hôte de `agent_url()` (`agent<SUFFIXE>.dracodev.net` → `https://<slug><SUFFIXE>.dracodev.net`, slugs `og`/`flowdeck`/`tiktak`/`kanbante` ; hors motif → rien), puis défaut officiel ; Gitea exclue. Tests `tests/test_draco_config.py` (+9 : dérivation `-dev` via `AGENT_EVENEMENTS_URL` et via `DRACO_AGENT_URL`, priorité de l'explicite, 5 hôtes hors motif, suffixe vide, injection) | Suite de #223 : seule l'URL de l'agent avait une dérivation de repli (`AGENT_EVENEMENTS_URL`) ; les sœurs retombaient sur les défauts officiels. Correctif de code demandé par Bruno : l'environnement se déduit de l'agent déjà configuré, sans variable supplémentaire. Redéploiement Docker requis |
| *BUG-122* | Déconnexion du menu compte inerte dans les applications sœurs : la barre commune relaie `deconnexion` à l'hôte, mais la déconnexion globale d'ObsiGate (ACC-3.1) n'est appelable qu'en same-origin (`POST /api/auth/logout`) — aucun point d'entrée navigable pour les autres hôtes | 🟢 corrigé | P1 | ⚙️ backend | IA | `backend/routers/sso.py` (`GET /sso/logout`), `backend/auth/router.py` (`apply_global_logout`) | Depuis la console Spark (ou Flowdeck/TikTak/ShaarIt/Hub) : menu compte → « Déconnexion » → rien ne se passe, la session draco reste vivante (vérifié en navigateur réel : cookie présent, `/api/v1/me` toujours 200) | Nouvelle route `GET /sso/logout?return_to=` : exécute la déconnexion globale (logique partagée `apply_global_logout` : jetons natifs refresh/access révoqués, `jti` draco révoqué, cookie effacé sur le domaine parent) puis redirige vers le `return_to` validé (liste blanche https + *.dracodev.net, repli `/`). Tests `tests/test_draco_session.py` (+3) | Signalé par Bruno le 2026-11-06 (capture console), carte fusion #141 ; les hôtes branchent leur action `deconnexion` sur cette route (PR sœurs). BUG-121 reste réservé au bug de couleur « Secrets » du vault Spark (proposé, non ordonné). Redéploiement Docker requis |
| *BUG-123* | Aller-retour SSO non refermé quand la session ObsiGate existe déjà : `GET /sso/authorize?return_to=…` dépose l'utilisateur sur `/?return_to=…`, mais le retour n'est honoré que par `AuthManager.saveToken()` (login frais, MFA, WebAuthn) — les chemins de restauration de session d'`initAuth()` (jeton encore valide en sessionStorage, ou refresh silencieux au démarrage) affichent l'accueil sans jamais suivre le `return_to` : l'utilisateur reste chez ObsiGate et, revenu manuellement à l'application sœur, n'y a toujours aucune session locale | 🟢 corrigé | P1 | 📱 frontend | IA | `frontend/js/auth.js` (`initAuth`, `_honorReturnTo`) | Navigateur avec session ObsiGate active → Kanbante → « Se connecter avec ObsiGate (SSO) » → retour automatique attendu dans Kanbante, connecté ; constaté en pile locale complète (chaîne d'URL journalisée) : arrivée sur l'accueil ObsiGate, retour jamais suivi | `_honorReturnTo()` retourne si la redirection a eu lieu ; les deux chemins de restauration d'`initAuth()` l'invoquent avant `showApp()` (les refresh en cours de session restent sans redirection). Garde : `tests/test_draco_barre.py` ; preuve navigateur complète du chantier (pile locale aux noms publics) | Signalé par Bruno le 2026-11-08 (test réel, compte « testeur » créé au Hub) ; le sous-cas login frais refermait déjà la boucle (vérifié). Fiche `docs/features/entree-sso-bug-123-124.md`. Redéploiement Docker requis |
| *BUG-124* | Nom du compte absent du menu de la barre commune juste après le login : `renderUserSection()` met à jour le bloc latéral et le drapeau admin de la barre, mais ne pousse jamais le nom — le seul poseur d'identité de la barre est le fetch d'amorçage au `DOMContentLoaded`, limité aux sessions déjà présentes au chargement. Après un login SPA sans rechargement : avatar « ✦ » et en-tête « Compte » jusqu'au rechargement forcé | 🟢 corrigé | P2 | 📱 frontend | IA | `frontend/js/auth.js` (`renderUserSection`), `frontend/index.html`, `frontend/draco-barre.js` (vendange commons v0.7.0) | Se connecter dans la SPA puis ouvrir le menu compte de la barre sans recharger : en-tête « Compte » au lieu du nom (reproduit en pile locale, capture) | Barre commune v0.7.0 re-vendangée verbatim (nouvelle API `DRACO_BAR.setUser`, PR dracodev-commons #15) ; `renderUserSection()` appelle `setUser(name)` / `setUser(null)`, le fetch d'amorçage d'`index.html` pousse aussi le nom par `setUser`. Garde : `tests/test_draco_barre.py` | Signalé par Bruno le 2026-11-08 (captures). Fiche `docs/features/entree-sso-bug-123-124.md`. Redéploiement Docker requis |
| # | Titre | Statut | Priorité | Scope | Assigné | Zone (fichier) | Cmd de repro | Correctif / Commit | Notes |
|---|---|---|---|---|---|---|---|---|---|
@@ -363,6 +365,7 @@ Avant de corriger quoi que ce soit, un agent IA doit :
| 2026-10-23 | BUG-119 | Correction | `backend/main.py`, `tests/test_security_headers.py`, `CHANGELOG.md`, `docs/ISSUES_TODOLIST.md`, `docs/ROADMAP.md` | **BUG-119 — le « mode console » du panneau Spark V3 (#222) était bloqué par le navigateur** (« This content is blocked ») : la console du service d'agents s'affiche dans une iframe `https://agent.dracodev.net`, mais la CSP servie par `SecurityHeadersMiddleware` limitait `frame-src` à `'self' blob:` — seul le panneau texte passait (`connect-src` autorisait déjà l'origine). Correctif strict : `frame-src 'self' blob: https://agent.dracodev.net`, toutes les autres directives inchangées à l'identique. Test de non-régression `test_csp_frame_src_allows_agent_console` (la CSP servie est analysée et la liste exacte des sources `frame-src` verrouillée). Vérifié : 100 passed (security_headers + csp_nonce + security_hardening + draco_barre + version), ruff propre sur les fichiers touchés (51 erreurs préexistantes ailleurs, inchangées), mypy 0 (120 fichiers) | 🟢 corrigé (en attente vérif utilisateur) |
| 2026-10-25 | BUG-120 (suite #223) | Correction | `backend/draco_config.py`, `tests/test_draco_config.py`, `.env.example`, `CHANGELOG.md`, `docs/ISSUES_TODOLIST.md`, `docs/ROADMAP.md`, `docs/features/devprod-urls-config-223.md` | **BUG-120 — le menu inter-apps de la dev pointait la prod** : `window.DRACO_URLS` servait l'agent en `-dev` mais les quatre sœurs en noms officiels, le `.env` dev ne déclarant pas les `DRACO_<APP>_URL`. Correctif dans `draco_config.app_url()` : après l'explicite, **dérivation d'environnement** — hôte de `agent_url()` de la forme `agent<SUFFIXE>.dracodev.net` sans port → sœur en `<slug><SUFFIXE>.dracodev.net` ; hors motif (autre domaine, LAN, IP, port) aucune dérivation ; Gitea jamais dérivée. Vérifié : `test_draco_config.py` 16 passed, suite complète **1668 passed / 9 skipped**, ruff 0 et mypy 0 sur les fichiers touchés (51 erreurs ruff préexistantes ailleurs, inchangées) | 🟢 corrigé (en attente vérif utilisateur) |
| 2026-11-06 | BUG-122 (fusion #141) | Correction | `backend/routers/sso.py`, `backend/auth/router.py`, `tests/test_draco_session.py`, `CHANGELOG.md`, `docs/ISSUES_TODOLIST.md`, `docs/features/deconnexion-globale-bug-122.md`, `VERSION` (+ dérivés) | **BUG-122 — la déconnexion du menu compte ne terminait rien hors ObsiGate** : ajout de `GET /sso/logout`, point d'entrée navigable de la déconnexion globale (logique partagée avec `POST /api/auth/logout`). Vérifié : `test_draco_session.py` 34 passed, ruff 0 et mypy 0 sur les fichiers touchés | 🟢 corrigé (en attente vérif utilisateur) |
| 2026-11-08 | BUG-123 + BUG-124 (chantier entrée SSO) | Correction | `frontend/js/auth.js`, `frontend/index.html`, `frontend/draco-barre.js`, `tests/test_draco_barre.py`, `CHANGELOG.md`, `docs/ISSUES_TODOLIST.md`, `docs/ROADMAP.md`, `docs/features/entree-sso-bug-123-124.md`, `VERSION` (+ dérivés) | **BUG-123 — aller-retour SSO refermé à la restauration de session** : `initAuth()` honore le `return_to` sur ses deux chemins de restauration (jeton valide, refresh silencieux au démarrage), comme le faisait déjà `saveToken` après un login frais. **BUG-124 — nom du compte dans la barre dès le login** : barre commune re-vendangée en v0.7.0 (`DRACO_BAR.setUser`, PR dracodev-commons #15), branchée dans `renderUserSection()` et le fetch d'amorçage. Chaînes prouvées en navigateur réel (pile locale aux noms publics, scénario de Bruno) | 🟢 corrigé (en attente vérif utilisateur) |
---
+3 -1
View File
@@ -1,6 +1,6 @@
# ObsiGate — Roadmap
> **Version :** 2.86.0 | **Dernière mise à jour :** 2026-11-06
> **Version :** 2.86.2 | **Dernière mise à jour :** 2026-11-08
> **Ce fichier ne contient que le travail à venir** (🔵 En cours + ⚪ Backlog) et un index compact
> vers les fonctionnalités livrées.
> - **Méthode de livraison à appliquer pour toute tâche : [DELIVERY_WORKFLOW.md](./DELIVERY_WORKFLOW.md)**
@@ -1227,6 +1227,8 @@
| BUG-119 | Panneau Spark — « mode console » bloqué par la CSP (`frame-src` sans l'origine du service d'agents) | 2.77.1 | [ISSUES_TODOLIST.md](./ISSUES_TODOLIST.md) |
| BUG-120 | Menu inter-apps — les URL sœurs (`window.DRACO_URLS`) dérivent l'environnement de l'hôte de l'agent (`-dev` en dev), explicite prioritaire, Gitea inchangée (suite de #223) | 2.78.2 | [ISSUES_TODOLIST.md](./ISSUES_TODOLIST.md) |
| 225 | Panneau Spark — version du service d'agents visible en mode sidebar (pied du panneau, lue en direct via `GET /healthz`) | 2.79.0 | [features/fusion-panneau-version-agent-225.md](./features/fusion-panneau-version-agent-225.md) |
| BUG-123 | Aller-retour SSO — le `return_to` de `/sso/authorize` est honoré aussi après restauration de session au démarrage (`initAuth`), pas seulement après un login frais | Unreleased | [features/entree-sso-bug-123-124.md](./features/entree-sso-bug-123-124.md) |
| BUG-124 | Barre commune v0.7.0 — `DRACO_BAR.setUser` branché dans `renderUserSection()` : le nom du compte s'affiche dans le menu compte dès le login, sans rechargement | Unreleased | [features/entree-sso-bug-123-124.md](./features/entree-sso-bug-123-124.md) |
---
+77
View File
@@ -0,0 +1,77 @@
# Entrée SSO de bout en bout — BUG-123 + BUG-124
Chantier « entrée SSO de bout en bout » (écarts constatés par Bruno en
test réel le 2026-11-08, compte « testeur » créé au Hub) : deux des
trois écarts tenaient à ObsiGate. Diagnostic mené en navigateur réel
dans une pile locale complète servie sous les noms publics
(`*-dev.dracodev.net`, TLS local), chaîne d'URL et de cookies
journalisée à chaque saut — dossier de preuves `preuve-entree-sso`.
## BUG-123 — L'aller-retour SSO ne se referme pas (session existante)
**Chaîne observée (cas fautif)** : clic « Se connecter avec
ObsiGate (SSO) » dans l'application sœur → `GET
/sso/authorize?return_to=<app>/auth/draco` → `302 /?return_to=…` →
la SPA restaure la session (`initAuth` : jeton encore valide, ou
refresh silencieux via le cookie HttpOnly) et affiche l'accueil —
**le `return_to` n'est jamais suivi**. L'utilisateur reste chez
ObsiGate ; revenu manuellement à l'application, il n'y trouve
aucune session locale (« toujours pas connecté »).
**Cause racine** : le retour n'était honoré qu'à un seul endroit,
`AuthManager.saveToken()` → `_honorReturnTo()` — c'est-à-dire après
un login frais, une MFA ou un WebAuthn. Les deux chemins de
restauration d'`initAuth()` ne passaient ni par `saveToken` ni par
`_honorReturnTo`. Le sous-cas « login frais » refermait la boucle
correctement (vérifié dans la même pile) — d'où un bug invisible
pour qui teste en navigateur vierge sans session centrale.
**Correctif** (`frontend/js/auth.js`) :
- `_honorReturnTo()` retourne `true` quand il redirige (même liste
blanche inchangée : https + `*.dracodev.net`, jamais l'hôte
courant — re-validation cliente en plus de celle du serveur).
- `initAuth()` invoque `_honorReturnTo()` sur ses deux chemins de
restauration réussie, avant `showApp()`.
- Les refresh silencieux **en cours de session** (`refreshAccessToken`
appelé par l'intercepteur) n'honorent toujours pas le retour :
une page déjà ouverte ne doit jamais être redirigée.
## BUG-124 — Le nom du compte absent du menu de la barre après login
**Constat** : juste après le login, le menu compte de la barre
commune affiche l'en-tête générique « Compte » et l'avatar neutre
« ✦ » ; un rechargement forcé fait apparaître le nom.
**Cause racine** : la barre résout le nom dans `cfg.user`, sinon
dans le `title` de l'avatar. Chez ObsiGate, le seul poseur du
`title` était le fetch d'amorçage d'`index.html` au
`DOMContentLoaded` — limité aux sessions **déjà présentes au
chargement**. `renderUserSection()` (`js/auth.js`), appelée pour
toutes les sessions établies (login, restauration), mettait à jour
le bloc latéral et le drapeau admin de la barre (`setAdmin`,
v0.6.0/fusion #142) mais jamais le nom : la barre n'avait aucune
API pour le recevoir.
**Correctif** :
- dracodev-commons **v0.7.0** (PR #15) : API publique
`DRACO_BAR.setUser(user)` — même contrat de valeur que `cfg.user`
(chaîne, ou objet `display_name` / `name` / `username`) ;
resynchronise l'avatar (initiale, infobulle, aria-label) et
l'en-tête du menu compte immédiatement ; `setUser(null)` rétablit
l'état neutre. Barre re-vendangée verbatim dans
`frontend/draco-barre.js`.
- `renderUserSection()` appelle `setUser(name)` pour toute session
établie et `setUser(null)` sans identité ; le fetch d'amorçage
d'`index.html` pousse aussi le nom par `setUser` (le `title` seul
ne resynchronisait ni l'aria-label ni un menu déjà construit).
## Vérifications
- `tests/test_draco_barre.py::test_barre_v070_setuser_et_retour_sso_restauration`
(contrats épinglés) + suites draco/session/version vertes.
- `node ui/barre-apps/tests/barre-apps.test.js` côté commons :
58 tests, 284 assertions (nouveau test setUser).
- Preuve navigateur de bout en bout (scénario de Bruno, contexte
vierge) : voir le rapport du chantier et `preuve-entree-sso/`.
+5
View File
@@ -101,6 +101,11 @@
text-decoration: none; cursor: pointer;
}
.draco-menu-item:hover, .draco-menu-item:focus-visible { background: var(--surface-2); }
/* Item conditionnel masqué (v0.6.0 : « Administration » tant que
l'hôte n'a pas affirmé le statut d'administrateur) — le
display:flex ci-dessus écraserait l'attribut hidden sans cette
règle, même patron que .draco-compte-menu[hidden]. */
.draco-menu-item[hidden] { display: none; }
/* Icônes monoline du menu compte (v0.4.0) : appendues après le
libellé dans le DOM, replacées à gauche par order: -1. */
.draco-menu-icone {
+96
View File
@@ -42,6 +42,33 @@
* l'ouverture par le mécanisme data-theme des hôtes, avec cache
* local court et repli intégral sur le comportement actuel de
* l'hôte si le Hub ne répond pas. Voir « Thème commun » plus bas.
* v0.6.0 (demande de Bruno, 2026-11-06) : le menu compte gagne un
* cinquième lien Hub, « Administration » → <hub>/admin (le Hub y
* redirige vers /admin/utilisateurs), placé après le séparateur,
* avant « Déconnexion », avec son icône monoline (curseurs de
* réglage). AFFICHAGE CONDITIONNEL : l'entrée n'apparaît que si
* l'hôte AFFIRME que le compte courant est administrateur de
* suite — cfg.admin === true à la configuration, ou
* DRACO_BAR.setAdmin(true) quand l'hôte l'apprend après coup
* (patron setUrls). L'hôte ne passe le drapeau que s'il l'a établi
* depuis SA source d'autorité (rôle global du compte chez
* ObsiGate, niveau du registre des rôles du Hub pour son app…) ;
* à défaut de certitude, pas de drapeau et l'entrée reste masquée.
* La barre ne devine jamais : le jeton draco ne porte aucun rôle
* (claims minimaux, par design). La vraie protection reste la
* garde serveur du Hub (/admin/*), pas ce masquage d'affichage.
* Voir le README, « Administration » — contrat de visibilité.
* v0.7.0 (chantier « entrée SSO de bout en bout », 2026-11-08) :
* l'API publique gagne DRACO_BAR.setUser(user) — l'hôte pousse le
* nom du compte courant DÈS qu'il l'établit, notamment une SPA qui
* termine son login sans rechargement (cas ObsiGate : le nom
* n'atteignait la barre qu'au chargement de la page suivante, le
* menu compte affichait l'en-tête générique « Compte » jusqu'au
* rechargement forcé). Même contrat de valeur que cfg.user
* (chaîne, ou objet display_name / name / username). L'avatar
* (initiale, infobulle) et l'en-tête du menu compte sont mis à
* jour immédiatement, sans rechargement ; null rétablit l'état
* neutre (✦, « Compte »).
*
* Aucun gestionnaire inline : un seul écouteur délégué sur le document
* (patron validé par le prototype — les onclick du HTML généré sont
@@ -54,6 +81,11 @@
* counts: { tiktak: 3, kanbante: 3, alertes: 3, spark: 0 },
* user: "Bruno", // nom affiché (v0.1.6) ;
* // ou { display_name: "Bruno", username: "bruno" }
* admin: true, // (v0.6.0, optionnel)
* // administrateur de suite ÉTABLI par l'hôte : démasque
* // l'entrée « Administration » du menu compte. Absent ou
* // non-strictement-true → entrée masquée. Peut aussi être
* // posé après coup via DRACO_BAR.setAdmin(true).
* urls: { // bases des applications
* obsigate: "https://og.dracodev.net/",
* flowdeck: "https://flowdeck.dracodev.net/",
@@ -653,6 +685,18 @@
["path", { d: "M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" }],
["circle", { cx: "12", cy: "7", r: "4" }]
],
/* Curseurs de réglage (trois axes, trois boutons) — v0.6.0. */
"admin": [
["line", { x1: "4", y1: "21", x2: "4", y2: "14" }],
["line", { x1: "4", y1: "10", x2: "4", y2: "3" }],
["line", { x1: "12", y1: "21", x2: "12", y2: "12" }],
["line", { x1: "12", y1: "8", x2: "12", y2: "3" }],
["line", { x1: "20", y1: "21", x2: "20", y2: "16" }],
["line", { x1: "20", y1: "12", x2: "20", y2: "3" }],
["line", { x1: "1", y1: "14", x2: "7", y2: "14" }],
["line", { x1: "9", y1: "8", x2: "15", y2: "8" }],
["line", { x1: "17", y1: "16", x2: "23", y2: "16" }]
],
/* Flèche sortant d'un cadre (porte). */
"deconnexion": [
["path", { d: "M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" }],
@@ -687,6 +731,15 @@
return span;
}
/* Visibilité de l'entrée « Administration » (v0.6.0) : vraie
* seulement si l'hôte l'a AFFIRMÉE — cfg.admin === true à la
* configuration, ou DRACO_BAR.setAdmin(true) après coup. La
* barre ne déduit jamais ce statut (le jeton draco ne porte
* aucun rôle) ; à défaut, l'entrée reste masquée et la garde
* serveur du Hub reste la vraie protection. */
var compteAdmin = cfg.admin === true;
var adminItem = null;
/* Menu compte (v0.1.6 ; v0.3.0 pour les liens) — en-tête
* d'identité, puis les liens du Hub (Télécharger, À Propos,
* Aides, Profil — vrais <a href>, même patron que le menu des
@@ -719,6 +772,22 @@
sep.className = "draco-menu-sep";
sep.setAttribute("aria-hidden", "true");
compteMenu.appendChild(sep);
/* « Administration » (v0.6.0) : cinquième lien Hub
* (<hub>/admin, où le Hub redirige vers /admin/utilisateurs).
* data-hub-link="admin" : son href est resynchronisé par
* refreshCompteHrefs / setUrls comme les quatre autres liens.
* Toujours construit ; masqué tant que l'hôte n'a pas affirmé
* le statut d'administrateur (compteAdmin). */
var adm = document.createElement("a");
adm.className = "draco-menu-item";
adm.href = hubHref("admin");
adm.setAttribute("data-hub-link", "admin");
adm.setAttribute("role", "menuitem");
adm.textContent = "Administration";
adm.appendChild(menuIcone("admin"));
adm.hidden = !compteAdmin;
compteMenu.appendChild(adm);
adminItem = adm;
var b = document.createElement("button");
b.type = "button";
b.className = "draco-menu-item";
@@ -734,6 +803,7 @@
var head = compteMenu.querySelector(".draco-compte-nom");
var name = displayName();
if (head) head.textContent = name || "Compte";
if (adminItem) adminItem.hidden = !compteAdmin;
refreshCompteHrefs();
}
function toggleCompteMenu(force) {
@@ -835,6 +905,32 @@
window.DRACO_BAR.urls = resolvedUrls;
applyUrls(resolvedUrls);
refreshCompteHrefs();
},
/* Administration (v0.6.0) : l'hôte pose le drapeau quand il a
* ÉTABLI que le compte courant est administrateur de suite
* (sa source d'autorité — voir l'en-tête et le README). Toute
* valeur autre que le booléen true masque l'entrée. */
setAdmin: function (v) {
compteAdmin = v === true;
if (adminItem) adminItem.hidden = !compteAdmin;
},
/* Identité (v0.7.0) : l'hôte pousse le nom du compte courant
* dès qu'il l'établit — même contrat de valeur que cfg.user
* (chaîne, ou objet display_name / name / username). L'avatar
* et l'en-tête du menu compte sont resynchronisés aussitôt,
* sans rechargement. setUser(null) rétablit l'état neutre. */
setUser: function (u) {
cfg.user = u || null;
if (!u) {
var av = document.querySelector(".draco-avatar");
if (av) {
av.textContent = "✦";
av.removeAttribute("title");
av.setAttribute("aria-label", "Compte");
}
}
syncAvatar();
refreshCompteMenu();
}
};
+18
View File
@@ -439,6 +439,24 @@
var av = document.getElementById("draco-avatar");
if (av) { av.textContent = nom.charAt(0).toUpperCase(); av.title = nom; }
}
/* Barre v0.6.0 (commons) : entrée « Administration »
du menu compte. Le rôle global du compte chez
ObsiGate EST la définition de l'administrateur
de suite (c'est lui que la garde /admin/* du Hub
vérifie, via ce même /api/auth/me) — ObsiGate le
connaît nativement, il l'affirme à la barre.
Tout autre rôle (ou échec) : pas d'affirmation,
l'entrée reste masquée. */
if (u && window.DRACO_BAR && window.DRACO_BAR.setAdmin) {
window.DRACO_BAR.setAdmin(u.role === "admin");
}
/* Barre v0.7.0 (commons, BUG-124) : le nom du
compte est aussi poussé par setUser — le
title seul ne resynchronisait ni l'aria-label
ni un menu compte déjà construit. */
if (u && nom && window.DRACO_BAR && window.DRACO_BAR.setUser) {
window.DRACO_BAR.setUser(nom);
}
}).catch(function () { /* avatar générique conservé */ });
});
</script>
+52 -1
View File
@@ -131,15 +131,17 @@ const AuthManager = {
_honorReturnTo() {
try {
const rt = new URLSearchParams(window.location.search).get("return_to");
if (!rt) return;
if (!rt) return false;
const u = new URL(rt);
const okHost = u.hostname === "dracodev.net"
|| u.hostname.endsWith(".dracodev.net");
if (u.protocol === "https:" && okHost
&& u.hostname !== window.location.hostname) {
window.location.replace(rt);
return true;
}
} catch (e) { /* return_to illisible : on reste sur place */ }
return false;
},
getToken() {
@@ -386,6 +388,17 @@ const AuthManager = {
const section = document.getElementById("sidebar-user");
if (!user || !this._authEnabled) {
if (section) section.hidden = true;
// Barre commune v0.6.0 (fusion #142) : sans identité
// établie, l'entrée « Administration » du menu compte
// reste masquée.
if (window.DRACO_BAR && window.DRACO_BAR.setAdmin) {
window.DRACO_BAR.setAdmin(false);
}
// Barre commune v0.7.0 (BUG-124) : symétriquement, le nom
// du compte est retiré de la barre (retour au neutre).
if (window.DRACO_BAR && window.DRACO_BAR.setUser) {
window.DRACO_BAR.setUser(null);
}
return;
}
if (!section) return;
@@ -427,6 +440,29 @@ const AuthManager = {
});
}
// Barre commune v0.6.0 (fusion #142) : l'entrée
// « Administration » du menu compte suit le rôle natif du
// compte — la définition même de l'administrateur de suite
// (garde /admin/* du Hub), même source que la rangée
// d'administration native ci-dessous. renderUserSection()
// est appelée par showApp() pour toutes les sessions
// établies (connexion, restauration, renouvellement
// silencieux) ; le fetch d'amorçage d'index.html ne couvre
// que les sessions déjà présentes au chargement de la page.
if (window.DRACO_BAR && window.DRACO_BAR.setAdmin) {
window.DRACO_BAR.setAdmin(user.role === "admin");
}
// Barre commune v0.7.0 (BUG-124) : pousser aussi le NOM du
// compte, pour le même motif — sans cet appel, la barre ne
// connaissait l'identité que par le fetch d'amorçage
// d'index.html (sessions déjà présentes au chargement) :
// après un login SPA sans rechargement, le menu compte
// affichait l'en-tête générique « Compte » et l'avatar
// neutre « ✦ » jusqu'au rechargement forcé.
if (window.DRACO_BAR && window.DRACO_BAR.setUser) {
window.DRACO_BAR.setUser(name || null);
}
const adminRow = document.getElementById("admin-menu-row");
if (adminRow) {
if (user.role === "admin") {
@@ -469,6 +505,11 @@ const AuthManager = {
// Auth enabled — check for existing session
if (this.hasSession() && !this.isTokenExpired()) {
// BUG-123 : session déjà établie au démarrage (jeton encore
// valide) — si la page a été ouverte par /sso/authorize avec
// un return_to, l'aller-retour SSO doit se refermer ici
// aussi, pas seulement après un login frais (saveToken).
if (this._honorReturnTo()) return true;
this.showApp();
return true;
}
@@ -485,6 +526,16 @@ const AuthManager = {
if (res.ok) {
const user = await res.json();
sessionStorage.setItem(this.USER_KEY, JSON.stringify(user));
// BUG-123 : même obligation après restauration par
// refresh silencieux au démarrage — refreshAccessToken()
// ne passe pas par saveToken, donc sans cet appel le
// return_to d'un /sso/authorize restait sans effet et
// l'utilisateur demeurait sur l'accueil ObsiGate au lieu
// de revenir, connecté, à l'application demanderesse.
// NB : seuls les chemins de démarrage d'initAuth honorent
// le retour ; les refresh silencieux en cours de session
// ne doivent jamais rediriger.
if (this._honorReturnTo()) return true;
this.showApp();
return true;
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "obsigate",
"version": "2.86.0",
"version": "2.86.2",
"description": "**Porte d'entrée web ultra-léger pour vos vaults Obsidian** — Accédez, naviguez et recherchez dans toutes vos notes Obsidian depuis n'importe quel appareil via une interface web moderne et responsive.",
"main": "patch.js",
"directories": {
+63
View File
@@ -101,6 +101,69 @@ def test_barre_v030_liens_hub_menu_compte():
assert "v0.3.0" in html
def test_barre_v060_entree_administration():
# Barre v0.6.0 (dracodev-commons, demande de Bruno du
# 2026-11-06) : entrée « Administration » du menu compte —
# lien Hub <hub>/admin à visibilité conditionnelle. Le JS
# vendangé la construit (data-hub-link="admin"), masquée tant
# que l'hôte n'affirme pas le statut d'administrateur
# (cfg.admin / DRACO_BAR.setAdmin) ; le CSS porte la règle
# [hidden] qui la masque malgré le display:flex des items.
# Branchement hôte : index.html affirme le statut depuis le
# rôle global de /api/auth/me — la définition même de
# l'administrateur de suite (garde /admin/* du Hub).
js = (FRONTEND / "draco-barre.js").read_text(encoding="utf-8")
assert 'data-hub-link", "admin"' in js
assert '"Administration"' in js
assert "setAdmin" in js
assert "compteAdmin = cfg.admin === true" in js
css = _css()
assert ".draco-menu-item[hidden]" in css
html = _index()
assert 'window.DRACO_BAR.setAdmin(u.role === "admin")' in html
# Le fetch d'amorçage d'index.html tourne avant toute
# connexion : le branchement qui couvre la connexion par le
# formulaire vit dans renderUserSection() (js/auth.js — le
# frontend servi est en modules ES ; le app.js monolithique
# à la racine n'est plus chargé), appelée par showApp() pour
# toutes les sessions établies, au même endroit que la
# rangée d'administration native.
auth_js = (FRONTEND / "js" / "auth.js").read_text(encoding="utf-8")
assert "window.DRACO_BAR.setAdmin(user.role === \"admin\")" in auth_js
assert "window.DRACO_BAR.setAdmin(false)" in auth_js
def test_barre_v070_setuser_et_retour_sso_restauration():
# Barre v0.7.0 (dracodev-commons) + BUG-123/BUG-124 (chantier
# « entrée SSO de bout en bout », écarts constatés par Bruno le
# 2026-11-08) :
# - BUG-124 : le JS vendangé expose DRACO_BAR.setUser ; l'hôte
# le branche dans renderUserSection() (toutes les sessions
# établies, comme setAdmin) et dans le fetch d'amorçage
# d'index.html — sans quoi le menu compte affichait
# « Compte » jusqu'au rechargement après un login SPA.
# - BUG-123 : _honorReturnTo signale la redirection (return)
# et initAuth() l'invoque sur ses deux chemins de
# restauration au démarrage (jeton valide, refresh
# silencieux) — sans quoi l'aller-retour SSO ne se refermait
# pas quand la session ObsiGate existait déjà.
js = (FRONTEND / "draco-barre.js").read_text(encoding="utf-8")
assert "setUser: function (u)" in js
assert "v0.7.0" in js
html = _index()
assert "window.DRACO_BAR.setUser(nom)" in html
auth_js = (FRONTEND / "js" / "auth.js").read_text(encoding="utf-8")
assert "window.DRACO_BAR.setUser(name || null)" in auth_js
assert "window.DRACO_BAR.setUser(null)" in auth_js
# BUG-123 : le retour est honoré à la restauration, et
# _honorReturnTo rapporte s'il a redirigé.
assert "if (this._honorReturnTo()) return true;" in auth_js
assert auth_js.count("if (this._honorReturnTo()) return true;") == 2
assert "window.location.replace(rt);\n return true;" in auth_js
# Le login frais continue de passer par saveToken.
assert "this._honorReturnTo();\n }," in auth_js
def test_panneau_spark_integre_218():
# #218 : le bouton Spark de la barre ouvre le panneau de l'agent
# commun (mêmes assets partagés que TikTak / Flowdeck / Kanbante).