# #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) — `/` devient le vault `home-`, 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-`, 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é.