feat: dossier personnel par utilisateur — vault home-<user> + registre de vaults persistant #194
- backend/user_home.py : ensure_user_home()/release_user_home() idempotents (mkdir, vault, watcher, octroi dans user.vaults), rappelés à la création d'un compte et au démarrage (migration + réparation + sweep des vaults home-* orphelins), dossier conservé à la suppression. - backend/indexer.py : data/vaults.json, registre persistant des vaults ajoutés à la volée, fusionné par load_vault_config() — ils disparaissaient au premier reindex complet et au redémarrage. reload_single_vault remet vault_config[name] après remove_vault_from_index. - backend/auth/router.py + routers/vaults.py : appels ensure/release + persistance à l'ajout/retrait d'un vault. - docker-compose.yml : montage unique /NFS/OBSIDIAN_DOC/Home:/vaults/Home + OBSIGATE_HOME_ROOT (absente = fonctionnalité inactive). - Tests : tests/test_user_home.py (12) + tests/test_auth_api.py::TestUserHome (2).
This commit is contained in:
@@ -339,6 +339,8 @@ Avant de corriger quoi que ce soit, un agent IA doit :
|
||||
|
||||
| 2026-10-09 | #193 | Fonctionnalité | `backend/file_chat.py`, `backend/schemas.py`, `frontend/js/filechat.js`, `frontend/style.css`, `tests/test_file_chat.py`, `tests/frontend/filechat.test.mjs`, `docs/features/file-chat-169.md`, `CHANGELOG.md` | **#193 — un post du chat s'affiche comme un document markdown** : le serveur rend le texte de chaque message avec le **pipeline des documents** (`backend/render.py::_render_markdown` : mistune — tableaux, listes de tâches, notes —, wikilinks, masquage des secrets #188, **sanitizer BUG-021**) et le renvoie dans un champ `html` (ajouté à `ChatMessageItem`, sinon `response_model` le filtrait). Le rendu est calculé **à la lecture** (`get_messages`) et à l'ajout (`_append`) — il n'est donc **jamais persisté** dans `data/chats/*.json` — et l'écho SSE part avec : les autres clients voient le rendu sans refresh. Côté front, `_fillBody()` injecte le HTML dans un `<div class="md-content">` puis appelle `safeHighlight()` sur chaque `pre code` (highlight.js + alias de langages, exactement comme le viewer ; mermaid non déclenché). Repli intact si `html` manque (écho optimiste → texte brut + URL cliquable) et un échec de rendu n'emporte jamais le message. CSS : `.file-chat-md` annule le `pre-wrap` hérité de la bulle (les retours à la ligne du HTML source produisaient des lignes vides) et compacte `.md-content` (`pre`/`table` en `overflow-x: auto`). Vérifié : `test_file_chat.py` 54→**61** (`TestMarkdownRendering` ×7 : html à l'ajout et à la lecture, **absent du JSON**, classe `language-xxx`, listes, `<script>` neutralisé, routes POST/GET, broadcast SSE), `filechat.test.mjs` 25→**27**, suite 1689 passed, ruff 0, mypy 0 (113 fichiers), validate-imports 42 modules / 372 exports | ✅ livré (en attente vérif utilisateur) |
|
||||
|
||||
| 2026-10-09 | #194 | Fonctionnalité | `backend/user_home.py` (nouveau), `backend/indexer.py`, `backend/auth/router.py`, `backend/routers/vaults.py`, `backend/main.py`, `docker-compose.yml`, `tests/test_user_home.py` (nouveau), `tests/test_auth_api.py`, `docs/features/user-home-194.md` (nouveau), `docs/ROADMAP.md`, `CHANGELOG.md`, `README.md`, `README.fr.md` | **#194 — dossier personnel par utilisateur** : chaque compte reçoit `<OBSIGATE_HOME_ROOT>/<user>` monté comme vault propre `home-<user>` (option A validée) — l'isolation s'appuie sur l'ACL par vault **existante** (`check_vault_access`), aucune ACL par chemin inventée. `backend/user_home.py` : `ensure_user_home()` idempotent (mkdir → `add_vault_to_index` → `persist_vault` → watcher → SSE → octroi dans `user.vaults`) appelé à `POST /api/auth/admin/users` **et** au démarrage (`ensure_all_user_homes()` : migration des users existants, réparation des dossiers/registres manquants, sweep des vaults `home-*` orphelins dont l'utilisateur a disparu hors route — garde `Path.parent == racine` pour ne jamais toucher à un vault admin homonyme) ; `release_user_home()` à la suppression (index/watcher/registre refermés, **dossier conservé**). **Blocage levé** : registre persistant `data/vaults.json` fusionné par `load_vault_config()` — avant, tout vault ajouté à la volée (`/api/vaults/add`) disparaissait au premier reindex complet et au redémarrage. Correctif au passage : `reload_single_vault()` remet `vault_config[name]` après `remove_vault_from_index()`. Réponse du POST = vaults réactualisés (l'instantané de `create_user` était antérieur à l'octroi). Déploiement : montage unique `/NFS/OBSIDIAN_DOC/Home:/vaults/Home` + `OBSIGATE_HOME_ROOT` (absente = feature inactive, dev/tests/desktop inchangés). Vérifié : `test_user_home.py` 12/12 (création, idempotence, username invalide, persistance après `load_vault_config`, retrait, isolation `check_vault_access`, orphelins), `test_auth_api.py::TestUserHome` 2/2, suite **1703 passed / 2 skipped**, ruff backend 0, mypy 114 fichiers 0, `validate-imports` 42 modules / 372 exports, `unit.test.mjs` 13/13 | ✅ livré (en attente vérif utilisateur) |
|
||||
|
||||
---
|
||||
|
||||
## 📜 Historique des bugs résolus
|
||||
|
||||
+22
-1
@@ -1,6 +1,6 @@
|
||||
# ObsiGate — Roadmap
|
||||
|
||||
> **Version :** 2.63.0 | **Dernière mise à jour :** 2026-10-09
|
||||
> **Version :** 2.64.0 | **Dernière mise à jour :** 2026-10-10
|
||||
> **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)**
|
||||
@@ -30,6 +30,27 @@
|
||||
|
||||
## 🔵 En cours
|
||||
|
||||
### 194. Dossier personnel par utilisateur (`<racine>/Home/<user>`)
|
||||
|
||||
- **Effort :** 1-2 jours | **Impact :** 🟡 | **Ouvert :** 2026-10-09 | **Statut :** 🔵 en cours
|
||||
- **Cadrage :** chaque utilisateur créé reçoit un dossier personnel monté en vault propre
|
||||
(`home-<user>`), isolé par l'ACL par vault existante — pas d'ACL par chemin à inventer.
|
||||
- **Décisions validées :** option A (un vault par utilisateur), nom `home-<user>`,
|
||||
dossier **conservé** à la suppression du compte, racine `/NFS/OBSIDIAN_DOC/Home`
|
||||
(montée `/vaults/Home`, exposée par `OBSIGATE_HOME_ROOT`).
|
||||
- **Sous-tâches :**
|
||||
- [x] ID stable créé (avant codage, AGENTS.md)
|
||||
- [x] Registre persistant des vaults dynamiques `data/vaults.json` (les vaults ajoutés à la
|
||||
volée sont aujourd'hui perdus au redémarrage / reindex — `load_vault_config` ne lit
|
||||
que les env `VAULT_N_*`)
|
||||
- [x] Service `ensure_user_home()` idempotent : mkdir + vault + octroi, appelé à la création
|
||||
d'un user et au démarrage (réparation/migration)
|
||||
- [x] Retrait au suppression de compte (index/watcher/registre) sans supprimer le dossier
|
||||
- [x] Montage Docker + `OBSIGATE_HOME_ROOT` dans `docker-compose.yml`
|
||||
- [x] Tests (création, persistance après rebuild, isolation 403 entre users) + docs
|
||||
|
||||
---
|
||||
|
||||
### 77. Application Desktop native — Tauri (Windows / Linux / macOS)
|
||||
|
||||
- **Effort :** 8-12 jours | **Impact :** 🟡 | **Framework :** Tauri v2 (Rust + Webview)
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
# #194 — Dossier personnel par utilisateur
|
||||
|
||||
> **Statut :** livré | **Ouvert :** 2026-10-09 | **Type :** feature (auth + vaults)
|
||||
|
||||
## Problème
|
||||
|
||||
Un utilisateur créé dans ObsiGate n'avait aucun espace propre : il ne voyait
|
||||
que les vaults qu'un admin lui attribuait à la main, et rien n'était préparé
|
||||
sur le disque à sa création.
|
||||
|
||||
## Décision d'architecture
|
||||
|
||||
Deux formes étaient possibles :
|
||||
|
||||
1. **Un vault par utilisateur** (retenu) — `<racine>/<user>` devient le vault
|
||||
`home-<user>`, octroyé dans `user.vaults`. L'ACL par vault existante
|
||||
(`backend/auth/middleware.py::check_vault_access`, appliquée partout :
|
||||
API, MCP, search, push, tools) fait l'isolation **sans rien inventer**.
|
||||
2. Un vault `Home` unique + ACL par sous-dossier (écartée) — il n'existe
|
||||
aucune ACL par chemin dans le code ; il aurait fallu la brancher dans
|
||||
`files_read`, `files_media`, `history`, `backups`, `mcp/tools`, `push`…
|
||||
Surface de sécurité trop grande pour un gain marginal.
|
||||
|
||||
Décisions produit validées : nom `home-<user>`, dossier **conservé** à la
|
||||
suppression du compte, racine `/NFS/OBSIDIAN_DOC/Home` montée `/vaults/Home`.
|
||||
|
||||
## Mise en place
|
||||
|
||||
| Élément | Où |
|
||||
|---|---|
|
||||
| Service idempotent `ensure_user_home()` / `release_user_home()` / `ensure_all_user_homes()` | `backend/user_home.py` |
|
||||
| Registre persistant des vaults dynamiques `data/vaults.json`, fusionné par `load_vault_config()` | `backend/indexer.py` (`persist_vault` / `unpersist_vault`) |
|
||||
| Appel à la création et à la suppression d'un compte | `backend/auth/router.py` (`POST/DELETE /api/auth/admin/users`) |
|
||||
| Passe de réparation/migration au démarrage | `backend/main.py` (`lifespan` → après le watcher) |
|
||||
| Persistance des vaults ajoutés par `/api/vaults/add` + retrait | `backend/routers/vaults.py` |
|
||||
| Montage + variable d'environnement | `docker-compose.yml` (`/NFS/OBSIDIAN_DOC/Home:/vaults/Home`, `OBSIGATE_HOME_ROOT=/vaults/Home`) |
|
||||
|
||||
## Garde-fous
|
||||
|
||||
- **Fonctionnalité inactive sans `OBSIGATE_HOME_ROOT`** : dev, tests et
|
||||
desktop n'ont aucun comportement modifié.
|
||||
- **Validation du nom** : `^[a-zA-Z0-9_-]{2,32}$` (même grammaire que
|
||||
`CreateUserRequest.username_valid`), re-vérifiée avant de construire un
|
||||
chemin — défense en profondeur contre le path traversal.
|
||||
- **Nom de vault sans `/` ni `::`** : le nom sert de segment d'URL
|
||||
(`/api/file/{vault}/…`) et de clé d'index `vault::path`.
|
||||
- **Auto-réparation** : `ensure_all_user_homes()` au démarrage crée les
|
||||
dossiers manquants, ré-enregistre les vaults perdus et octroie l'accès aux
|
||||
users créés hors API. Toute erreur disque (NFS indisponible) est
|
||||
journalisée et rattrapée au boot.
|
||||
- **Suppression de compte** : index/watcher/registre refermés, dossier sur
|
||||
disque conservé.
|
||||
|
||||
## Bug corrigé au passage
|
||||
|
||||
- `load_vault_config()` ne lisait que les env `VAULT_N_*`/`DIR_N_*` : tout
|
||||
vault ajouté à la volée (y compris via `/api/vaults/add`) disparaissait au
|
||||
premier reindex complet et au redémarrage. → `data/vaults.json`.
|
||||
- `reload_single_vault()` perdait l'entrée `vault_config[vault]` après
|
||||
`remove_vault_from_index()`.
|
||||
|
||||
## Tests
|
||||
|
||||
- `tests/test_user_home.py` (10) : création (dossier + vault + octroi +
|
||||
registre), idempotence, username invalide, désactivation sans variable,
|
||||
relecture du registre par `load_vault_config`, `release` (dossier
|
||||
conservé), isolation `check_vault_access` entre users, passe de réparation.
|
||||
- `tests/test_auth_api.py::TestUserHome` (2) : provisionnement via l'API
|
||||
(création → dossier, vault visible, alice ne voit que son home, suppression
|
||||
→ vault refermé / dossier conservé) + absence de dossier sans
|
||||
`OBSIGATE_HOME_ROOT`.
|
||||
|
||||
## Impact utilisateur
|
||||
|
||||
Aucune nouvelle chaîne i18n : les noms de vault sont des données, la liste
|
||||
provient déjà de `GET /api/vaults`. Le dossier apparaît tout seul dans le
|
||||
sélecteur de vaults de l'utilisateur concerné.
|
||||
Reference in New Issue
Block a user