feat: quick wins - dockerignore, env secrets, gzip, cache-control
- Add .dockerignore to exclude .git, __pycache__, docs, etc. from Docker context - Create .env.example template with documented env vars - Move OBSIGATE_ADMIN_PASSWORD from docker-compose.yml to env_file: .env - Add .env.* to .gitignore (excluding .env.example) - Enable GZipMiddleware for ~70% bandwidth reduction on text responses - Add Cache-Control: immutable for /static/ assets - Update ROADMAP: mark all 4 quick wins as done, add audit findings - Add comprehensive technical audit report (AUDIT_TECHNIQUE_2026-05-27.md)
This commit is contained in:
parent
d6cf2a1a7f
commit
58a0ffc76c
@ -1,12 +1,20 @@
|
|||||||
__pycache__/
|
.git
|
||||||
|
.gitignore
|
||||||
|
.gitattributes
|
||||||
|
__pycache__
|
||||||
*.pyc
|
*.pyc
|
||||||
*.pyo
|
*.pyo
|
||||||
.env
|
.venv
|
||||||
test_vault/
|
venv
|
||||||
.venv/
|
test_vault
|
||||||
venv/
|
docs
|
||||||
.git/
|
*.md
|
||||||
.gitignore
|
!README.md
|
||||||
README.md
|
|
||||||
build.sh
|
|
||||||
docker-compose.yml
|
docker-compose.yml
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
*.log
|
||||||
|
.obsigate-backup
|
||||||
|
.pytest_cache
|
||||||
|
htmlcov
|
||||||
|
.coverage
|
||||||
|
|||||||
34
.env.example
Normal file
34
.env.example
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
# ObsiGate — Environment variables
|
||||||
|
# Copiez ce fichier en .env et modifiez les valeurs
|
||||||
|
# Le fichier .env n'est JAMAIS commité (présent dans .gitignore)
|
||||||
|
|
||||||
|
# Auth (décommenter pour activer)
|
||||||
|
# OBSIGATE_AUTH_ENABLED=true
|
||||||
|
# OBSIGATE_ADMIN_USER=admin
|
||||||
|
# OBSIGATE_ADMIN_PASSWORD=change-me
|
||||||
|
|
||||||
|
# Sécurité des cookies (activer si derrière HTTPS)
|
||||||
|
# OBSIGATE_SECURE_COOKIES=false
|
||||||
|
|
||||||
|
# Tokens TTL en secondes
|
||||||
|
# OBSIGATE_ACCESS_TOKEN_TTL=900
|
||||||
|
# OBSIGATE_REFRESH_TOKEN_TTL=604800
|
||||||
|
|
||||||
|
# Rate limiting
|
||||||
|
# OBSIGATE_LOGIN_MAX_ATTEMPTS=10
|
||||||
|
# OBSIGATE_LOGIN_WINDOW_SECONDS=900
|
||||||
|
|
||||||
|
# Watcher
|
||||||
|
# OBSIGATE_WATCHER_ENABLED=true
|
||||||
|
# OBSIGATE_WATCHER_USE_POLLING=false
|
||||||
|
# OBSIGATE_WATCHER_POLLING_INTERVAL=5.0
|
||||||
|
# OBSIGATE_WATCHER_DEBOUNCE=2.0
|
||||||
|
|
||||||
|
# Ignored directories (séparés par des virgules)
|
||||||
|
# OBSIGATE_IGNORED_DIRS=.obsidian,.trash,.git,__pycache__,node_modules,.obsigate-backup
|
||||||
|
|
||||||
|
# Audit
|
||||||
|
# OBSIGATE_AUDIT_MAX_SIZE=10485760
|
||||||
|
|
||||||
|
# Backup
|
||||||
|
# OBSIGATE_BACKUP_DIR=.obsigate-backup
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
@ -2,6 +2,8 @@ __pycache__/
|
|||||||
*.pyc
|
*.pyc
|
||||||
*.pyo
|
*.pyo
|
||||||
.env
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
test_vault/
|
test_vault/
|
||||||
.venv/
|
.venv/
|
||||||
venv/
|
venv/
|
||||||
|
|||||||
@ -490,6 +490,9 @@ class SecurityHeadersMiddleware(BaseHTTPMiddleware):
|
|||||||
"connect-src 'self' https://esm.sh https://unpkg.com https://cdnjs.cloudflare.com https://fonts.googleapis.com https://fonts.gstatic.com; "
|
"connect-src 'self' https://esm.sh https://unpkg.com https://cdnjs.cloudflare.com https://fonts.googleapis.com https://fonts.gstatic.com; "
|
||||||
"font-src 'self' https://fonts.gstatic.com;"
|
"font-src 'self' https://fonts.gstatic.com;"
|
||||||
)
|
)
|
||||||
|
# Cache static assets aggressively (fingerprinted by PWA manifest hash)
|
||||||
|
if request.url.path.startswith("/static/"):
|
||||||
|
response.headers["Cache-Control"] = "public, max-age=31536000, immutable"
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
@ -552,6 +555,10 @@ async def lifespan(app: FastAPI):
|
|||||||
|
|
||||||
app = FastAPI(title="ObsiGate", version="1.4.0", lifespan=lifespan)
|
app = FastAPI(title="ObsiGate", version="1.4.0", lifespan=lifespan)
|
||||||
|
|
||||||
|
# GZip compression — reduces bandwidth by ~70% for text responses
|
||||||
|
from fastapi.middleware.gzip import GZipMiddleware
|
||||||
|
app.add_middleware(GZipMiddleware, minimum_size=1000)
|
||||||
|
|
||||||
# Security headers on all responses
|
# Security headers on all responses
|
||||||
app.add_middleware(SecurityHeadersMiddleware)
|
app.add_middleware(SecurityHeadersMiddleware)
|
||||||
|
|
||||||
|
|||||||
@ -41,8 +41,10 @@ services:
|
|||||||
- VAULT_5_PATH=/vaults/SessionsManager
|
- VAULT_5_PATH=/vaults/SessionsManager
|
||||||
- DIR_1_NAME=Bruno
|
- DIR_1_NAME=Bruno
|
||||||
- DIR_1_PATH=/vaults/bruno
|
- DIR_1_PATH=/vaults/bruno
|
||||||
# Auth configuration (uncomment to enable)
|
# Auth configuration (edit values in .env file)
|
||||||
- OBSIGATE_AUTH_ENABLED=true
|
- OBSIGATE_AUTH_ENABLED=true
|
||||||
- OBSIGATE_ADMIN_USER=admin
|
- OBSIGATE_ADMIN_USER=admin
|
||||||
- OBSIGATE_ADMIN_PASSWORD=chab30 # Leave empty = auto-generated (check logs)
|
# OBSIGATE_ADMIN_PASSWORD → définir dans .env (pas ici !)
|
||||||
# - OBSIGATE_SECURE_COOKIES=false # Set true if behind HTTPS reverse proxy
|
# - OBSIGATE_SECURE_COOKIES=false # Set true if behind HTTPS reverse proxy
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
|||||||
384
docs/AUDIT_TECHNIQUE_2026-05-27.md
Normal file
384
docs/AUDIT_TECHNIQUE_2026-05-27.md
Normal file
@ -0,0 +1,384 @@
|
|||||||
|
# RAPPORT D'AUDIT TECHNIQUE — OBSIGATE v1.4.0/1.5.0
|
||||||
|
|
||||||
|
**Projet :** Portail web ultra-léger pour vaults Obsidian
|
||||||
|
**Auteur :** Bruno Beloeil
|
||||||
|
**Date d'audit :** 27 mai 2026
|
||||||
|
**Auditeur :** Hermes Agent (DeepSeek V4 Pro)
|
||||||
|
**Méthodologie :** Revue de code exhaustive — 20,457 lignes analysées
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. VUE D'ENSEMBLE
|
||||||
|
|
||||||
|
### 1.1 Statistiques du codebase
|
||||||
|
|
||||||
|
| Composant | Fichier(s) | Lignes | Langage |
|
||||||
|
|-----------|-----------|--------|---------|
|
||||||
|
| Backend API | `backend/main.py` | 3,105 | Python/FastAPI |
|
||||||
|
| Moteur de recherche | `backend/search.py` | 1,211 | Python |
|
||||||
|
| Indexeur | `backend/indexer.py` | 987 | Python |
|
||||||
|
| Auth (5 modules) | `backend/auth/*.py` | ~600 | Python |
|
||||||
|
| Modules backend | 11 fichiers | ~1,200 | Python |
|
||||||
|
| Frontend SPA | `frontend/app.js` | 8,869 | Vanilla JS |
|
||||||
|
| Styles | `frontend/style.css` | 6,134 | CSS3 (variables) |
|
||||||
|
| Template HTML | `frontend/index.html` | 1,138 | HTML5 |
|
||||||
|
| **Total** | | **~20,457** | |
|
||||||
|
|
||||||
|
### 1.2 Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────┐
|
||||||
|
│ Navigateur (SPA vanilla JS — 8,869 lignes) │
|
||||||
|
│ • CodeMirror 6, Lucide Icons, Highlight.js │
|
||||||
|
│ • PWA (Service Worker, manifest.json) │
|
||||||
|
│ • dark/light theme via CSS variables │
|
||||||
|
└──────────────────┬──────────────────────────────────────┘
|
||||||
|
│ REST API + SSE
|
||||||
|
┌──────────────────▼──────────────────────────────────────┐
|
||||||
|
│ FastAPI (backend/main.py — 3,105 lignes) │
|
||||||
|
│ • 50+ endpoints REST │
|
||||||
|
│ • SSE Manager (Server-Sent Events) │
|
||||||
|
│ • Security middleware (CSP, X-Frame, etc.) │
|
||||||
|
│ • Auth JWT + Argon2id + rate limiting │
|
||||||
|
└──────┬───────────┬──────────┬──────────┬────────────────┘
|
||||||
|
│ │ │ │
|
||||||
|
┌────▼────┐ ┌───▼────┐ ┌───▼───┐ ┌───▼──────────┐
|
||||||
|
│ Indexer │ │ Search │ │ Auth │ │ Modules │
|
||||||
|
│(987 loc)│ │(1211) │ │(~600) │ │watcher,share │
|
||||||
|
│InMemory │ │TF-IDF │ │JWT │ │pdf,audit, │
|
||||||
|
│+ lookup │ │+ facets│ │users │ │webhooks,etc │
|
||||||
|
└─────────┘ └────────┘ └───────┘ └──────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. FORCES DU PROJET
|
||||||
|
|
||||||
|
### 2.1 Architecture (Excellent)
|
||||||
|
|
||||||
|
- **Séparation propre** backend/frontend sans framework lourd
|
||||||
|
- **In-memory indexing** performant — pas de base de données externe
|
||||||
|
- **Inverted Index TF-IDF** avec mise à jour incrémentale via hooks (plan.md implémenté)
|
||||||
|
- **Watchdog hot-reload** avec debounce (2s) et SSE broadcast
|
||||||
|
- **Multi-stage Docker** (~180MB) avec utilisateur non-root (UID 1000)
|
||||||
|
- **CodeMirror 6** en tant qu'éditeur intégré — choix moderne
|
||||||
|
- **PWA complète** — manifest, service worker, icônes multi-tailles
|
||||||
|
- **Zero dépendance npm** côté frontend — vanilla JS pur
|
||||||
|
|
||||||
|
### 2.2 Sécurité (Très bon)
|
||||||
|
|
||||||
|
- **Path traversal protection** (`_resolve_safe_path`) avec résolution symlink-aware
|
||||||
|
- **JWT + Argon2id** avec refresh tokens sur cookies httpOnly
|
||||||
|
- **Rate limiting** IP-based (10 tentatives/15min) + lockout par compte
|
||||||
|
- **Secret redaction** automatique (JWT, API keys, tokens, connection strings)
|
||||||
|
- **Audit logging** (JSON lines dans `data/audit.log`) avec rotation 10MB
|
||||||
|
- **Backup automatique** avant écriture/suppression (`.obsigate-backup/`)
|
||||||
|
- **Security headers** (CSP, X-Frame-Options, XSS-Protection, Referrer-Policy)
|
||||||
|
- **Safe atomic writes** (tmp + replace) pour shares.json, webhooks.json
|
||||||
|
|
||||||
|
### 2.3 Fonctionnalités (Riche)
|
||||||
|
|
||||||
|
- **Multi-vault** avec contrôle d'accès granulaire par utilisateur
|
||||||
|
- **Recherche avancée** : opérateurs `tag:`, `vault:`, `title:`, `path:`, `ext:`, regex
|
||||||
|
- **Autocomplétion intelligente** : historique + titres + tags avec debounce 150ms
|
||||||
|
- **Wikilinks** résolus avec backlink index bidirectionnel
|
||||||
|
- **Rendu d'images** 7 stratégies de résolution (Obsidian-compatible)
|
||||||
|
- **Mode onglets** (preview/persistant) avec drag-and-drop
|
||||||
|
- **Éditeur intégré** CodeMirror 6 avec syntax highlighting (10+ langages)
|
||||||
|
- **Dashboard** avec 4 panneaux (stats, bookmarks, récents, partagés)
|
||||||
|
- **Partage public** avec tokens 64-char hex, expiration configurable
|
||||||
|
- **Export PDF** via WeasyPrint
|
||||||
|
- **Webhooks** avec signature HMAC-SHA256
|
||||||
|
- **Find-in-page** avec TreeWalker (regex, case, whole word)
|
||||||
|
- **TOC/Outline** avec scroll spy et barre de progression
|
||||||
|
- **Vault settings** persistés (hideHiddenFiles)
|
||||||
|
- **Saved searches** avec CRUD API
|
||||||
|
|
||||||
|
### 2.4 Qualité du code (Bon)
|
||||||
|
|
||||||
|
- Docstrings Google-style sur toutes les fonctions publiques backend
|
||||||
|
- Annotations de type sur tous les paramètres et retours
|
||||||
|
- Modèles Pydantic documentés avec `Field(description=...)`
|
||||||
|
- Logging structuré par module (`obsigate.indexer`, `obsigate.search`, etc.)
|
||||||
|
- Gestion d'erreurs systématique (try/catch avec toast user-friendly)
|
||||||
|
- Frontend vanilla JS — zéro framework, zéro npm
|
||||||
|
- `"use strict"` + IIFE wrapper
|
||||||
|
- Conventions de nommage cohérentes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. PROBLÈMES ET VULNÉRABILITÉS
|
||||||
|
|
||||||
|
### 3.1 Critique (P0)
|
||||||
|
|
||||||
|
**Aucun problème critique détecté.** Les failles P0 identifiées précédemment (rate limiting, secret redaction) ont été corrigées.
|
||||||
|
|
||||||
|
### 3.2 Élevé (P1)
|
||||||
|
|
||||||
|
#### H1 — Pas de tests automatisés
|
||||||
|
|
||||||
|
Le projet n'a **aucun test** — pas de `pytest`, pas de `tests/`, pas de CI/CD. Avec 20k+ lignes de code, c'est le risque technique numéro 1. Chaque modification est un saut dans l'inconnu.
|
||||||
|
|
||||||
|
**Impact :** Régressions silencieuses, bugs non détectés, refactoring risqué, onboarding développeur difficile.
|
||||||
|
|
||||||
|
#### H2 — Pas de CI/CD pipeline
|
||||||
|
|
||||||
|
Aucun fichier `.github/workflows/` ou équivalent, pas de linting automatisé, pas de vérification de build Docker. Le `build.sh` vérifie les prérequis mais ne run aucun test.
|
||||||
|
|
||||||
|
#### H3 — Mot de passe admin dans `docker-compose.yml`
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- OBSIGATE_ADMIN_PASSWORD=chab30
|
||||||
|
```
|
||||||
|
|
||||||
|
C'est le fichier de configuration local — le risque est limité si le dépôt est privé, mais c'est une mauvaise pratique à corriger. **Recommandation :** utiliser un fichier `.env` + `env_file` dans docker-compose.
|
||||||
|
|
||||||
|
#### H4 — Pas de `.dockerignore`
|
||||||
|
|
||||||
|
Tout le dossier `.git` et les fichiers de documentation sont copiés dans le contexte Docker, augmentant inutilement la taille du contexte de build et risquant des fuites accidentelles.
|
||||||
|
|
||||||
|
### 3.3 Modéré (P2)
|
||||||
|
|
||||||
|
#### M1 — Frontend monolithique (8,869 lignes dans un seul fichier)
|
||||||
|
|
||||||
|
`app.js` est très gros pour un fichier unique. La navigation et la maintenance deviennent difficiles.
|
||||||
|
|
||||||
|
**Suggestion :** Split en modules ES (même sans build step, les `import`/`export` natifs fonctionnent dans tous les navigateurs modernes).
|
||||||
|
|
||||||
|
#### M2 — Content Security Policy permissive
|
||||||
|
|
||||||
|
La CSP actuelle autorise `'unsafe-inline'` et plusieurs CDN :
|
||||||
|
```
|
||||||
|
script-src 'self' 'unsafe-inline' https://cdnjs.cloudflare.com https://unpkg.com https://esm.sh
|
||||||
|
```
|
||||||
|
Le risque est théorique (pas d'input utilisateur qui génère du HTML), mais `'unsafe-inline'` pourrait être retiré avec des nonces/hashes.
|
||||||
|
|
||||||
|
#### M3 — Pas de compression/streaming des réponses
|
||||||
|
|
||||||
|
Les fichiers statiques sont servis bruts. Activer la compression gzip/brotli dans Uvicorn ou via middleware FastAPI réduirait la bande passante de ~70%.
|
||||||
|
|
||||||
|
#### M4 — Index chargé entièrement en mémoire
|
||||||
|
|
||||||
|
Avec 40k+ fichiers, l'index mémoire peut devenir conséquent. Un fichier de 100KB de contenu = 100KB en RAM. 40,000 × 100KB = 4GB théoriques. La limite `SEARCH_CONTENT_LIMIT = 100_000` (100KB par fichier) atténue le problème mais un lazy-loading pourrait être nécessaire à très grande échelle.
|
||||||
|
|
||||||
|
### 3.4 Faible (P3)
|
||||||
|
|
||||||
|
#### L1 — Pas de rate limiting sur les endpoints non-auth
|
||||||
|
|
||||||
|
Les endpoints comme `/api/health` ne sont pas limités. Faible risque mais un scraper pourrait saturer le serveur.
|
||||||
|
|
||||||
|
#### L2 — Pas de validation d'extension pour les uploads
|
||||||
|
|
||||||
|
La création de fichiers via API (`POST /api/file/{vault}`) ne valide pas l'extension. Un utilisateur pourrait créer un `.php` ou `.exe` dans un vault.
|
||||||
|
|
||||||
|
#### L3 — `handle_file_move` non atomique
|
||||||
|
|
||||||
|
`remove_single_file` puis `update_single_file` — si le processus crashe entre les deux, l'index est incohérent (fichier supprimé mais pas recréé).
|
||||||
|
|
||||||
|
#### L4 — Pas d'i18n structurée
|
||||||
|
|
||||||
|
Tout le texte UI est en dur en français dans le HTML et le JS. Pour une audience internationale, l'internationalisation serait bénéfique.
|
||||||
|
|
||||||
|
#### L5 — Duplication de `IGNORED_DIRS`
|
||||||
|
|
||||||
|
Les dossiers ignorés sont définis à deux endroits : `backend/indexer.py` (ligne 69) et `backend/watcher.py` (ligne 17). Une source unique serait préférable.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. RECOMMANDATIONS NEXT LEVEL
|
||||||
|
|
||||||
|
### 4.1 Tests et CI/CD (Impact : Très Élevé)
|
||||||
|
|
||||||
|
1. **Ajouter pytest** avec une couverture minimale de 70% sur le backend :
|
||||||
|
- Tests unitaires sur `search.py` (tokenizer, TF-IDF scoring, snippet extraction, regex search)
|
||||||
|
- Tests unitaires sur `indexer.py` (parsing frontmatter, extraction tags inline/frontmatter, wikilinks)
|
||||||
|
- Tests d'intégration sur l'API (FastAPI `TestClient`)
|
||||||
|
- Tests du rate limiter et de l'auth (JWT flow, lockout, permissions)
|
||||||
|
|
||||||
|
2. **Ajouter CI/CD** (GitHub Actions / Gitea Actions) :
|
||||||
|
```yaml
|
||||||
|
name: CI
|
||||||
|
on: [push, pull_request]
|
||||||
|
jobs:
|
||||||
|
lint:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: astral-sh/ruff-action@v1
|
||||||
|
- run: mypy backend/
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- run: pip install -r backend/requirements.txt
|
||||||
|
- run: pytest --cov=backend --cov-report=xml
|
||||||
|
security:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- run: pip-audit
|
||||||
|
- run: bandit -r backend/
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- run: docker build -t obsigate:ci .
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Ajouter Playwright** pour des tests E2E sur les flows critiques (login, recherche, navigation, ouverture de fichier)
|
||||||
|
|
||||||
|
### 4.2 Performance (Impact : Élevé)
|
||||||
|
|
||||||
|
4. **Activer la compression HTTP :**
|
||||||
|
```python
|
||||||
|
from fastapi.middleware.gzip import GZipMiddleware
|
||||||
|
app.add_middleware(GZipMiddleware, minimum_size=1000)
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **Ajouter des en-têtes de cache pour les assets statiques :**
|
||||||
|
```python
|
||||||
|
# Dans SecurityHeadersMiddleware ou via StaticFiles
|
||||||
|
# Cache-Control: public, max-age=31536000, immutable pour /static/
|
||||||
|
```
|
||||||
|
|
||||||
|
6. **Remplacer `bisect` par `sortedcontainers` :**
|
||||||
|
```python
|
||||||
|
from sortedcontainers import SortedList
|
||||||
|
self._sorted_tokens = SortedList() # O(log n) insert/remove au lieu de O(n)
|
||||||
|
```
|
||||||
|
|
||||||
|
7. **Pagination de l'API browse** pour les très grands dossiers (>1000 entrées)
|
||||||
|
|
||||||
|
### 4.3 Architecture (Impact : Élevé)
|
||||||
|
|
||||||
|
8. **Splitter `app.js` en modules ES :**
|
||||||
|
```
|
||||||
|
frontend/
|
||||||
|
js/
|
||||||
|
core.js (init, state global, api helper)
|
||||||
|
search.js (QueryParser, AutocompleteDropdown, SearchHistory)
|
||||||
|
tabs.js (TabManager)
|
||||||
|
dashboard.js (showWelcome, widgets)
|
||||||
|
editor.js (CodeMirror wrapper)
|
||||||
|
auth.js (AuthManager, login screen)
|
||||||
|
find.js (FindInPageManager)
|
||||||
|
ui.js (helpers: el(), icon(), toast, etc.)
|
||||||
|
```
|
||||||
|
Puis dans `index.html` :
|
||||||
|
```html
|
||||||
|
<script type="module" src="/static/js/app.js"></script>
|
||||||
|
```
|
||||||
|
|
||||||
|
9. **Extraire la config sensible vers `.env` :**
|
||||||
|
```bash
|
||||||
|
# .env (gitignored)
|
||||||
|
OBSIGATE_ADMIN_PASSWORD=***
|
||||||
|
# .env.example (commité, sans secrets)
|
||||||
|
OBSIGATE_ADMIN_PASSWORD=
|
||||||
|
```
|
||||||
|
|
||||||
|
10. **Ajouter `.dockerignore` :**
|
||||||
|
```
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
__pycache__
|
||||||
|
*.pyc
|
||||||
|
.venv
|
||||||
|
test_vault
|
||||||
|
docs
|
||||||
|
*.md
|
||||||
|
!README.md
|
||||||
|
docker-compose.yml
|
||||||
|
.env
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.4 Fonctionnalités différenciantes (Impact : Moyen-Élevé)
|
||||||
|
|
||||||
|
11. **Full-text search avec stemming français :**
|
||||||
|
```python
|
||||||
|
from snowballstemmer import FrenchStemmer
|
||||||
|
stemmer = FrenchStemmer()
|
||||||
|
# "mangé" → "mang", "chevaux" → "cheval"
|
||||||
|
```
|
||||||
|
Actuellement TF-IDF tokenise mais ne stemme pas, donc "manger" et "mangé" sont des tokens différents.
|
||||||
|
|
||||||
|
12. **Mode hors-ligne PWA complet :** Mettre en cache l'index via IndexedDB pour une recherche offline avec synchronisation lors du retour en ligne.
|
||||||
|
|
||||||
|
13. **Plugin système :** Permettre des extensions utilisateur (comme Obsidian plugins) — custom renderers, custom search operators, custom dashboard widgets.
|
||||||
|
|
||||||
|
14. **Graph view interactive** (code backend déjà présent !) : Les modèles `GraphNode`, `GraphEdge`, `GraphResponse` existent. Finaliser l'endpoint et l'UI pour une vue graphique des wikilinks.
|
||||||
|
|
||||||
|
15. **Collaboration temps réel :** Via WebSocket — édition simultanée avec CRDT (Yjs/Automerge), présence utilisateur, commentaires.
|
||||||
|
|
||||||
|
### 4.5 Sécurité & Opérations (Impact : Moyen)
|
||||||
|
|
||||||
|
16. **Rotation des logs Python** avec `RotatingFileHandler` — actuellement pas de limite de taille sur les logs.
|
||||||
|
|
||||||
|
17. **OAuth2/OIDC** en plus du JWT local (Google, GitHub, authentification SSO).
|
||||||
|
|
||||||
|
18. **Health check enrichi :** Ajouter l'état de l'index (size, readiness), mémoire utilisée, uptime, nombre de clients SSE.
|
||||||
|
|
||||||
|
### 4.6 UX/UI (Impact : Moyen)
|
||||||
|
|
||||||
|
19. **Palette de commandes** (`Ctrl+P` style VS Code) pour navigation rapide.
|
||||||
|
|
||||||
|
20. **Drag & drop de fichiers** dans l'arborescence pour déplacer/réorganiser.
|
||||||
|
|
||||||
|
21. **Diff viewer** pour comparer les versions backupées (`.obsigate-backup/`).
|
||||||
|
|
||||||
|
22. **Fichiers récents par vault** dans le dashboard (filtrer par vault actif).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. MATRICE DE PRIORITÉ
|
||||||
|
|
||||||
|
| Rang | Action | Effort | Impact | Risque si ignoré |
|
||||||
|
|------|--------|--------|--------|------------------|
|
||||||
|
| 1 | Tests unitaires backend (pytest) | 3-5 jours | 🔴 Critique | Régressions silencieuses |
|
||||||
|
| 2 | CI/CD pipeline | 1-2 jours | 🔴 Critique | Bugs en production |
|
||||||
|
| 3 | `.dockerignore` | 15 min | 🟠 Élevé | Fuite de code |
|
||||||
|
| 4 | `.env` pour secrets | 10 min | 🟠 Élevé | Secret dans le repo |
|
||||||
|
| 5 | Compression HTTP (GZip) | 5 min | 🟠 Élevé | Bande passante gaspillée |
|
||||||
|
| 6 | Cache-Control headers | 30 min | 🟠 Élevé | Requêtes inutiles |
|
||||||
|
| 7 | Splitter `app.js` en modules | 2-3 jours | 🟡 Moyen | Dette technique |
|
||||||
|
| 8 | `sortedcontainers` pour index | 2 heures | 🟡 Moyen | Performance à l'échelle |
|
||||||
|
| 9 | Stemming français | 2-4 heures | 🟡 Moyen | Qualité recherche |
|
||||||
|
| 10 | Mode hors-ligne PWA | 3-5 jours | 🟡 Moyen | Feature gap |
|
||||||
|
| 11 | OAuth2/OIDC | 2-3 jours | 🟢 Faible | Adoption entreprise |
|
||||||
|
| 12 | Plugin système | 5-10 jours | 🟢 Faible | Extensibilité |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. VERDICT GLOBAL
|
||||||
|
|
||||||
|
ObsiGate est un projet **impressionnant par sa complétude et sa qualité** pour une codebase de cette taille développée en solo. Les fondamentaux sont solides :
|
||||||
|
|
||||||
|
- ✅ Sécurité bien traitée (path traversal, JWT, Argon2id, rate limiting, audit, backup)
|
||||||
|
- ✅ Architecture propre et cohérente (séparation backend/frontend, modules bien découpés côté backend)
|
||||||
|
- ✅ Fonctionnalités riches (50+ endpoints, recherche TF-IDF, PWA, éditeur intégré)
|
||||||
|
- ✅ Attention aux détails (docstrings, type hints, Pydantic models documentés, logging structuré)
|
||||||
|
- ✅ Choix techniques judicieux (FastAPI, CodeMirror 6, vanilla JS, multi-stage Docker)
|
||||||
|
|
||||||
|
Les axes d'amélioration sont clairs et actionnables. Le passage de "bon projet" à "projet professionnel de niveau production" passe par **trois piliers :**
|
||||||
|
|
||||||
|
1. **Tests** — c'est le maillon faible critique, zéro test aujourd'hui
|
||||||
|
2. **CI/CD** — automatiser la qualité pour chaque commit
|
||||||
|
3. **Modularisation du frontend** — préparer la scalabilité du code
|
||||||
|
|
||||||
|
Le reste (OAuth, stemming, mode hors-ligne) sont des fonctionnalités qui différencieront ObsiGate des alternatives comme Obsidian Publish.
|
||||||
|
|
||||||
|
### Note technique
|
||||||
|
|
||||||
|
| Dimension | Note | Commentaire |
|
||||||
|
|-----------|------|-------------|
|
||||||
|
| Architecture | 8/10 | Propre, bien pensée, scalable |
|
||||||
|
| Sécurité | 8/10 | Excellentes bases, quelques améliorations possibles |
|
||||||
|
| Qualité du code | 7/10 | Bon backend, frontend monolithique |
|
||||||
|
| Performance | 7/10 | Index mémoire efficace, compression manquante |
|
||||||
|
| Tests | 0/10 | **Aucun test** — priorité absolue |
|
||||||
|
| Documentation | 7/10 | README excellent, docs/ fourni, manque CHANGELOG |
|
||||||
|
| DevOps | 6/10 | Docker OK, pas de CI/CD, pas de .dockerignore |
|
||||||
|
| **Global** | **7.5/10** | Excellent pour un projet solo, potentiel 9/10 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Rapport généré le 27 mai 2026 par audit automatisé Hermes Agent.*
|
||||||
|
*Ce document complète `ANALYSE_REVIEW.md` (bugs dashboard/search) et `ROADMAP.md` (fonctionnalités planifiées).*
|
||||||
195
docs/ROADMAP.md
195
docs/ROADMAP.md
@ -1,6 +1,7 @@
|
|||||||
# ObsiGate — Roadmap
|
# ObsiGate — Roadmap
|
||||||
|
|
||||||
> **Date :** 2026-05-25 | **Version :** 1.4.0 / 1.5.0
|
> **Date :** 2026-05-27 | **Version :** 1.4.0 / 1.5.0
|
||||||
|
> **Dernier audit :** [AUDIT_TECHNIQUE_2026-05-27.md](./AUDIT_TECHNIQUE_2026-05-27.md) — Note globale 7.5/10
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -13,6 +14,28 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Quick Wins (15-30 min cumulées) — Implémenter immédiatement
|
||||||
|
|
||||||
|
Ces 4 actions prennent moins de 30 minutes au total et ont un impact élevé.
|
||||||
|
|
||||||
|
### ✅ `.dockerignore` — Réduire le contexte de build Docker
|
||||||
|
|
||||||
|
**Impact :** Sécurité (pas de fuite de `.git`), performance (build plus rapide)
|
||||||
|
|
||||||
|
### ✅ `.env` pour les secrets — Ne jamais commiter de mots de passe
|
||||||
|
|
||||||
|
**Impact :** Sécurité (pas de secret dans le repo)
|
||||||
|
|
||||||
|
### ✅ Compression GZip — Réduire la bande passante de ~70%
|
||||||
|
|
||||||
|
**Impact :** Performance (pages plus rapides, moins de data transférée)
|
||||||
|
|
||||||
|
### ✅ Cache-Control pour assets statiques — Éviter les re-téléchargements
|
||||||
|
|
||||||
|
**Impact :** Performance (cache navigateur sur CSS/JS/icons)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Nouvelles fonctionnalités (historique TODO.md)
|
## Nouvelles fonctionnalités (historique TODO.md)
|
||||||
|
|
||||||
- ✅ **Indexation incrémentale automatique** — Le watcher détecte les nouveaux fichiers et met à jour l'index sans réindexation complète. Utilise `_on_vault_change` → `update_single_file` / `remove_single_file` / `handle_file_move`.
|
- ✅ **Indexation incrémentale automatique** — Le watcher détecte les nouveaux fichiers et met à jour l'index sans réindexation complète. Utilise `_on_vault_change` → `update_single_file` / `remove_single_file` / `handle_file_move`.
|
||||||
@ -31,7 +54,7 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Améliorations prioritaires (voir ANALYSE_REVIEW.md §4)
|
## Améliorations complétées (voir ANALYSE_REVIEW.md §4 et AUDIT_TECHNIQUE)
|
||||||
|
|
||||||
### 🔴 Sécurité — P0
|
### 🔴 Sécurité — P0
|
||||||
|
|
||||||
@ -59,16 +82,166 @@
|
|||||||
- ✅ **Documentation OpenAPI enrichie** — Tous les modèles Pydantic (`FileContentResponse`, `SearchResponse`, `AdvancedSearchResponse`, etc.) ont maintenant des `Field(description=...)` documentés visibles dans `/docs` (Swagger UI) et `/redoc`.
|
- ✅ **Documentation OpenAPI enrichie** — Tous les modèles Pydantic (`FileContentResponse`, `SearchResponse`, `AdvancedSearchResponse`, etc.) ont maintenant des `Field(description=...)` documentés visibles dans `/docs` (Swagger UI) et `/redoc`.
|
||||||
- ✅ **Gestion fichiers non-supportés** — Message explicite avec nom du fichier, taille et bouton de téléchargement pour les fichiers binaires. Backend : réponse structurée avec `unsupported: true`. Frontend : interface `unsupported-file`.
|
- ✅ **Gestion fichiers non-supportés** — Message explicite avec nom du fichier, taille et bouton de téléchargement pour les fichiers binaires. Backend : réponse structurée avec `unsupported: true`. Frontend : interface `unsupported-file`.
|
||||||
|
|
||||||
### ⬜ Qualité & Polish — P5+
|
---
|
||||||
|
|
||||||
- ⬜ **Tests unitaires** (pytest)
|
## 🔴 Problèmes identifiés par l'audit (P1) — Priorité immédiate
|
||||||
- ⬜ **Tests E2E** (Playwright)
|
|
||||||
- ⬜ **CI/CD pipeline** (GitHub Actions)
|
### ⬜ Tests unitaires backend (pytest) — Risque N°1
|
||||||
- ⬜ **i18n** (support anglais + français)
|
|
||||||
- ⬜ **CHANGELOG.md**
|
**Impact :** Sans tests, chaque changement = risque de régression. 20k lignes sans filet.
|
||||||
- ⬜ **Documentation utilisateur enrichie**
|
|
||||||
- ⬜ **Authentification multi-facteurs** (TOTP/WebAuthn)
|
**Comment :**
|
||||||
|
```bash
|
||||||
|
# 1. Installer pytest
|
||||||
|
pip install pytest pytest-cov pytest-asyncio httpx
|
||||||
|
|
||||||
|
# 2. Créer tests/conftest.py avec fixtures (TestClient, vaults de test)
|
||||||
|
# 3. Tests à créer en priorité :
|
||||||
|
# - backend/tests/test_search.py : tokenizer, TF-IDF scoring, snippets, regex
|
||||||
|
# - backend/tests/test_indexer.py : parsing frontmatter, extraction tags, wikilinks
|
||||||
|
# - backend/tests/test_auth.py : JWT flow, login, permissions, rate limiting
|
||||||
|
# - backend/tests/test_api.py : health, vaults, browse, search (intégration)
|
||||||
|
|
||||||
|
# 4. Lancer avec couverture
|
||||||
|
pytest --cov=backend --cov-report=term --cov-report=html
|
||||||
|
```
|
||||||
|
|
||||||
|
### ⬜ CI/CD pipeline (GitHub/Gitea Actions)
|
||||||
|
|
||||||
|
**Impact :** Automatiser lint, test, security scan et build Docker à chaque push.
|
||||||
|
|
||||||
|
**Comment :**
|
||||||
|
```yaml
|
||||||
|
# Créer .github/workflows/ci.yml (ou .gitea/workflows/ci.yml)
|
||||||
|
name: CI
|
||||||
|
on: [push, pull_request]
|
||||||
|
jobs:
|
||||||
|
lint:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: astral-sh/ruff-action@v1
|
||||||
|
- run: pip install mypy && mypy backend/ --ignore-missing-imports
|
||||||
|
test:
|
||||||
|
needs: lint
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- run: pip install -r backend/requirements.txt pytest pytest-cov pytest-asyncio httpx
|
||||||
|
- run: pytest --cov=backend --cov-report=xml
|
||||||
|
security:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- run: pip install pip-audit bandit && pip-audit && bandit -r backend/
|
||||||
|
build:
|
||||||
|
needs: test
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- run: docker build -t obsigate:ci .
|
||||||
|
```
|
||||||
|
|
||||||
|
### ✅ Mot de passe admin hors de `docker-compose.yml`
|
||||||
|
|
||||||
|
**Impact :** Secret dans le fichier de config = risque si le repo est partagé.
|
||||||
|
|
||||||
|
**Fait :** Mot de passe retiré de `docker-compose.yml`, remplacé par `env_file: .env`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
*Document généré le 2026-05-25 — Remplace l'ancien TODO.md*
|
## 🟠 Problèmes modérés (P2) — Cette semaine
|
||||||
|
|
||||||
|
### ⬜ Splitter `app.js` en modules ES (8,869 lignes → ~8 modules)
|
||||||
|
|
||||||
|
**Impact :** Maintenabilité, onboarding développeur, réduction de la dette technique.
|
||||||
|
|
||||||
|
**Comment :** Voir la section Architecture dans `AUDIT_TECHNIQUE_2026-05-27.md` §4.3.8.
|
||||||
|
Approche : extraction progressive module par module, en commençant par `ui.js` (helpers).
|
||||||
|
|
||||||
|
### ⬜ Remplacer `bisect` par `sortedcontainers.SortedList` dans l'InvertedIndex
|
||||||
|
|
||||||
|
**Impact :** O(log n) insert/remove au lieu de O(n) pour le vocabulaire de tokens.
|
||||||
|
|
||||||
|
**Comment :**
|
||||||
|
```bash
|
||||||
|
pip install sortedcontainers
|
||||||
|
```
|
||||||
|
```python
|
||||||
|
# backend/search.py — dans InvertedIndex.__init__ :
|
||||||
|
from sortedcontainers import SortedList
|
||||||
|
self._sorted_tokens = SortedList()
|
||||||
|
|
||||||
|
# add_document : remplacer bisect.insort par self._sorted_tokens.add(token)
|
||||||
|
# _remove_doc_internals : remplacer bisect.bisect_left + pop par self._sorted_tokens.discard(token)
|
||||||
|
```
|
||||||
|
|
||||||
|
### ⬜ Stemming français pour la recherche TF-IDF
|
||||||
|
|
||||||
|
**Impact :** "manger" trouve "mangé", "mangeons", "mangeait". Qualité de recherche ×2.
|
||||||
|
|
||||||
|
**Comment :**
|
||||||
|
```bash
|
||||||
|
pip install snowballstemmer
|
||||||
|
```
|
||||||
|
```python
|
||||||
|
# backend/search.py — dans tokenize() :
|
||||||
|
from snowballstemmer import FrenchStemmer
|
||||||
|
_stemmer = FrenchStemmer()
|
||||||
|
|
||||||
|
def tokenize(text: str) -> List[str]:
|
||||||
|
words = _WORD_RE.findall(normalize_text(text))
|
||||||
|
return _stemmer.stemWords(words) # stemming français
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🟡 Améliorations UX (P2/P3) — Ce mois-ci
|
||||||
|
|
||||||
|
- ⬜ **Palette de commandes** (`Ctrl+P`) — Navigation rapide style VS Code
|
||||||
|
- ⬜ **Drag & drop de fichiers** dans l'arborescence pour déplacer/réorganiser
|
||||||
|
- ⬜ **Diff viewer** pour comparer les versions backupées (`.obsigate-backup/`)
|
||||||
|
- ⬜ **Fichiers récents par vault** dans le dashboard (filtrage par vault actif)
|
||||||
|
- ⬜ **Finaliser la vue Graphique** — Le backend a déjà `GraphNode`/`GraphEdge`/`GraphResponse`, finaliser l'UI
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🟢 Fonctionnalités next-level (P3/P4) — Long terme
|
||||||
|
|
||||||
|
- ⬜ **Mode hors-ligne PWA complet** — IndexedDB pour recherche offline + synchro au retour en ligne
|
||||||
|
- ⬜ **OAuth2/OIDC** — Google, GitHub, authentification SSO en plus du JWT local
|
||||||
|
- ⬜ **Plugin système** — Extensions utilisateur (custom renderers, search operators, widgets)
|
||||||
|
- ⬜ **Collaboration temps réel** — WebSocket + CRDT (Yjs) pour édition simultanée
|
||||||
|
- ⬜ **i18n** — Support anglais + français avec système de traduction
|
||||||
|
- ⬜ **Authentification multi-facteurs** — TOTP/WebAuthn
|
||||||
|
- ⬜ **Health check enrichi** — État de l'index, mémoire, uptime, clients SSE
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Qualité & Polish (P5+) — Fondations
|
||||||
|
|
||||||
|
- ⬜ **Tests unitaires** (pytest) — *remonté en P1*
|
||||||
|
- ⬜ **Tests E2E** (Playwright)
|
||||||
|
- ⬜ **CI/CD pipeline** (GitHub Actions) — *remonté en P1*
|
||||||
|
- ⬜ **CHANGELOG.md**
|
||||||
|
- ⬜ **Documentation utilisateur enrichie**
|
||||||
|
- ⬜ **Déduplication de `IGNORED_DIRS`** — indexer.py et watcher.py ont des définitions séparées
|
||||||
|
- ⬜ **Rotation des logs Python** — `RotatingFileHandler` pour éviter les logs infinis
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Chronologie suggérée
|
||||||
|
|
||||||
|
| Semaine | Actions |
|
||||||
|
|---------|---------|
|
||||||
|
| **S27** (cette semaine) | Quick Wins ✅ (.dockerignore, .env, GZip, Cache-Control) |
|
||||||
|
| **S27-S28** | Tests unitaires backend — objectif 70% coverage |
|
||||||
|
| **S28** | CI/CD pipeline |
|
||||||
|
| **S29** | Split `app.js` en modules (commencer par ui.js, puis search.js) |
|
||||||
|
| **S30** | Stemming français + sortedcontainers |
|
||||||
|
| **S31+** | Fonctionnalités P3/P4 au fil de l'eau |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Document généré le 2026-05-27 — Intègre les recommandations de l'audit technique.*
|
||||||
|
*Voir aussi : [AUDIT_TECHNIQUE_2026-05-27.md](./AUDIT_TECHNIQUE_2026-05-27.md) pour le rapport complet.*
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user