L'admin (vaults: ["*"]) voyait le home de chaque utilisateur dans sa barre latérale : "*" ouvrait tous les vaults, home-* compris. - backend/auth/middleware.py : check_vault_access exige un octroi explicite pour tout vault home-* (nouveau is_home_vault()). - Filtres « * » en dur remplacés par check_vault_access : dashboard, conflits, liens retour, favoris, abonnements push. - /api/search : search_vaults(is_allowed=…) filtre les bruts avant pagination (total et page restent justes). - backend/user_home.py : _grant n'écarte plus les comptes « * » — l'admin reçoit son propre home-admin (auto-réparé au démarrage). - Tests : test_user_home.py +2, assertion API inversée dans test_auth_api.py (admin ne voit plus home-alice).
5.5 KiB
5.5 KiB
#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 :
- Un vault par utilisateur (retenu) —
<racine>/<user>devient le vaulthome-<user>, octroyé dansuser.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. - Un vault
Homeunique + ACL par sous-dossier (écartée) — il n'existe aucune ACL par chemin dans le code ; il aurait fallu la brancher dansfiles_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 queCreateUserRequest.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'indexvault::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 envVAULT_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éevault_config[vault]aprèsremove_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 parload_vault_config,release(dossier conservé), isolationcheck_vault_accessentre 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 sansOBSIGATE_HOME_ROOT.
UI — arborescence (incrément)
- Ordre : tri stable
home-*en premier, appliqué à la source (frontend/js/sidebar.js::_homeFirst, sur les deux sites qui alimententstate.allVaults) → arborescence, sélecteurs et refreshs homogènes. - Icône :
getVaultIcon()(frontend/js/viewer.js) renvoieicon("home", size)pourhome-*→ même pipeline lucide que le reste de l'app (safeCreateIcons,stroke: currentColor, suit le thème). - Test local :
docker-compose.test-win.ymlmontetest_home:/vaults/Home+OBSIGATE_HOME_ROOT=/vaults/Home(test_home/est dans.gitignore). - Vérifié live (instance 2020, Playwright) : ordre
home-admin, TestDir, TestVault(l'admin ne voit plus le home des autres),svg.lucide-homerendu, placeholder<i>remplacé.
Isolation — « * » ne couvre pas les dossiers persos
- Racine :
check_vault_access(backend/auth/middleware.py) renvoievault_name in vaultspour tout vaulthome-*, quel que soit le*. Toutes les routes (89 usages) passent par cette fonction. - Filtres « * » en dur remplacés par cet appel : dashboard
(
routers/config.py), conflits, liens retour, favoris, abonnements push. - Recherche :
services/search.search_vaults(is_allowed=…)filtre les bruts avant pagination —totalet page restent justes. - Octroi :
_grantn'écarte plus les comptes*→ l'admin reçoit son proprehome-admin(réparé au démarrage pour les comptes existants). - Reste ouvert (fuite pré-existante, hors #194) :
/api/search/advanced,/api/tags,/api/tags/suggest,/api/tree-searchet/api/suggestne filtrent pas par vault accessible — à traiter dans une tâche dédiée.
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é.