# Cellar — Collection de vins, bières et spiritueux Application web fullstack pour gérer une collection personnelle de boissons. Interface dark theme moderne, authentification JWT, upload de photos. ## Stack technique | Composant | Technologie | |-----------|-------------| | Frontend | React 18, TypeScript, Vite, Tailwind CSS | | Backend | Python 3.12, FastAPI, SQLAlchemy, SQLite | | Auth | JWT (python-jose), bcrypt, token versioning | | Infra | Podman/Docker, nginx, multi-stage builds | ## Prérequis - [Podman](https://podman.io/) (ou Docker) + podman-compose (ou docker-compose) Node.js et Python ne sont pas nécessaires — tout est géré dans les containers. ## Démarrage rapide ```bash # Cloner et configurer cp .env.example .env # Éditer .env avec un SECRET_KEY aléatoire et un ADMIN_PASSWORD fort # Build et lancement podman-compose up --build ``` L'app est accessible sur `http://localhost:8080`. ## Configuration (.env) | Variable | Description | Défaut | |----------|-------------|--------| | `SECRET_KEY` | Clé secrète pour signer les JWT (64+ caractères) | **Requis** — le backend refuse de démarrer sans | | `ADMIN_PASSWORD` | Mot de passe du compte admin | Généré aléatoirement si non défini | | `CORS_ORIGINS` | Origines autorisées pour CORS | `http://localhost:5173,http://localhost:3000` | | `BUILDAH_FORMAT` | Format OCI pour Buildah/Podman | `docker` | **Important :** Générez un `SECRET_KEY` aléatoire : ```bash python3 -c "import secrets; print(secrets.token_hex(32))" ``` ## Compte admin Au premier démarrage, un compte `admin` est créé automatiquement. Le mot de passe est soit `ADMIN_PASSWORD` (depuis `.env`), soit un mot de passe aléatoire généré automatiquement. ## Structure du projet ``` wine/ ├── backend/ │ ├── main.py # Point d'entrée FastAPI, startup, CORS │ ├── auth.py # JWT, hashage mot de passe, get_current_user │ ├── database.py # Engine SQLAlchemy, migration, session │ ├── models.py # Modèles: User, Drink, Invitation │ ├── schemas.py # Schémas Pydantic (validation) │ ├── ratelimit.py # Rate limiter in-memory │ ├── requirements.txt # Dépendances Python (pinnées) │ ├── requirements-dev.txt # Dépendances dev (pytest, httpx) │ ├── tests/ # Tests pytest │ ├── Dockerfile # Build multi-stage (Python 3.12-slim) │ └── routers/ │ ├── auth.py # Login, register, logout, invitations │ ├── drinks.py # CRUD boissons + upload images │ └── admin.py # Gestion utilisateurs (admin only) ├── frontend/ │ ├── src/ │ │ ├── api/ # Client API, types TypeScript │ │ ├── auth/ # Contexte auth, ProtectedRoute │ │ ├── components/ # DrinkCard, RatingStars, Layout │ │ └── pages/ # Home, Login, Register, Admin, etc. │ ├── nginx.conf # Config nginx (rate limiting, CSP, proxy) │ ├── nginx-main.conf # Override nginx.conf (user root pour podman) │ ├── Dockerfile # Build multi-stage (Node → nginx) │ └── package.json # Dépendances JS ├── docker-compose.yml # Orchestration backend + frontend ├── .env # Variables d'environnement └── .env.example # Template pour .env ``` ## Dépendances backend (Python) | Package | Version | Usage | |---------|---------|-------| | fastapi | * | Framework web | | uvicorn[standard] | * | Serveur ASGI | | sqlalchemy | * | ORM, accès SQLite | | pydantic | * | Validation des données | | python-multipart | * | Upload de fichiers | | Pillow | * | Vérification images uploadées | | aiofiles | * | Async file I/O | | python-jose[cryptography] | * | JWT encoding/decoding | | bcrypt | * | Hashage des mots de passe | ## Dépendances frontend (Node) | Package | Version | Usage | |---------|---------|-------| | react | ^18.3.1 | UI framework | | react-dom | ^18.3.1 | React DOM | | react-router-dom | ^7.1.1 | Routing SPA | | typescript | ^5.6.3 | Typage statique | | vite | ^6.0.3 | Bundler | | tailwindcss | ^3.4.16 | CSS utility-first | | @vitejs/plugin-react | ^4.3.4 | Plugin React pour Vite | ## Dev local (sans Docker) Si tu veux développer sans container, tu auras besoin de Python 3.12+ et Node.js 20+. ### Backend ```bash cd backend python -m venv venv && source venv/bin/activate pip install -r requirements.txt # Créer un .env ou exporter les variables : export SECRET_KEY=$(python3 -c "import secrets; print(secrets.token_hex(32))") export ADMIN_PASSWORD=monpassword uvicorn main:app --reload --port 8000 ``` ### Frontend ```bash cd frontend npm install npm run dev # Accessible sur http://localhost:5173 ``` ## API Endpoints ### Auth | Méthode | Route | Description | Auth | |---------|-------|-------------|------| | POST | `/api/auth/login` | Connexion | Non | | POST | `/api/auth/register` | Inscription (invitation requise) | Non | | GET | `/api/auth/me` | Profil utilisateur | Oui | | POST | `/api/auth/change-password` | Changer mot de passe | Oui | | POST | `/api/auth/logout` | Déconnexion (invalide le token) | Oui | | POST | `/api/auth/invitations` | Créer une invitation | Admin | | GET | `/api/auth/invitations` | Lister les invitations | Admin | ### Drinks | Méthode | Route | Description | Auth | |---------|-------|-------------|------| | GET | `/api/drinks` | Lister les boissons | Oui | | POST | `/api/drinks` | Ajouter une boisson | Oui | | GET | `/api/drinks/{id}` | Détail d'une boisson | Oui | | PUT | `/api/drinks/{id}` | Modifier une boisson | Oui | | DELETE | `/api/drinks/{id}` | Supprimer une boisson | Oui | | POST | `/api/drinks/{id}/upload-image` | Upload d'image | Oui | | GET | `/api/drinks/{id}/image?token=...` | Voir l'image | Oui (via query) | ### Admin | Méthode | Route | Description | Auth | |---------|-------|-------------|------| | GET | `/api/admin/users` | Lister les utilisateurs | Admin | | DELETE | `/api/admin/users/{id}` | Supprimer un utilisateur (+ données) | Admin | | POST | `/api/admin/users/{id}/reset-password` | Reset mot de passe d'un user | Admin | | POST | `/api/admin/users/{id}/toggle-admin` | Promouvoir/rétrograder admin | Admin | | GET | `/api/admin/stats` | Statistiques | Admin | ## Sécurité ### Authentification - JWT avec expiration 24h - Token versioning : le logout, le changement de mot de passe et le reset admin invalident tous les tokens existants - Tokens stockés en sessionStorage (pas de localStorage) - Mot de passe : bcrypt avec cost factor par défaut - `SECRET_KEY` vérifié au démarrage — le backend refuse de démarrer si manquant ou placeholder - Messages d'erreur génériques (pas d'énumération username/email) ### Rate Limiting - Login : 5 requêtes/min par IP - Register : 3 requêtes/5min par IP - Change-password : 5 requêtes/min par IP - Basé sur `X-Real-IP` (défini par nginx, non-spoofable), fallback `X-Forwarded-For` - Compteur incrémenté avant l'exécution (les échecs comptent aussi) - Protection nginx en supplément ### Uploads - Vérification MIME (magic bytes via Pillow + vérification du format réel) - Taille max 10 MB - Filenames UUID (pas de path traversal) - Endpoint image protégé par authentification (JWT via query param `?token=...` pour compatibilité ``) - Vérification que le répertoire de destination est bien dans `uploads/` ### Gestion admin des utilisateurs - L'admin peut reset le mot de passe de n'importe quel user (invalide ses tokens, min 8 caractères) - L'admin peut promouvoir/rétrograder un user en admin - L'admin ne peut pas se modifier lui-même (protection auto-destructrice) - Suppression d'un user : boissons + images + invitations associées ### Headers sécurité (nginx) - `X-Content-Type-Options: nosniff` - `X-Frame-Options: DENY` - `Content-Security-Policy` strict - `Referrer-Policy: strict-origin-when-cross-origin` - `Permissions-Policy` restrictive ### Docker - Container non-root (`appuser`) - `cap_drop: ALL` + `no-new-privileges` - 1 seul worker uvicorn (cohérence SQLite + rate limiter) - Limites ressources (512MB backend, 128MB frontend) - Logs avec rotation - Healthchecks pour orchestration ## Fonctionnalités - Gestion de vins, bières et spiritueux avec champs spécifiques par catégorie - Notation par étoiles (0-5) - Recherche full-text (nom, région, producteur, etc.) - Filtres par catégorie et note minimale - Upload de photos avec validation - Édition des boissons existantes (tous les champs modifiables) - Interface responsive dark theme - Panel admin : liste, suppression, reset password, toggle admin, statistiques - Système d'invitation pour l'inscription (lien copiable avec clipboard fallback) ## Licence Projet personnel.