Architecture globale
Ce document explique comment les composants communiquent entre eux. Pas le "quoi" (c'est dans intro.md) mais le "comment" et le "pourquoi cette organisation".
Flux d'une requête (diagramme de séquence)
Ce diagramme montre le parcours d'une requête de connexion. Pour une requête authentifiée classique (ex: récupérer les offres de stage), le flux est :
Séparation des responsabilités
Frontend (Next.js)
Le frontend est une application cliente qui ne prend aucune décision de sécurité.
| Responsabilité | Détail |
|---|---|
| Affichage | Composants React, pages, routing Next.js |
| UX | Interactions utilisateur, formulaires, feedback |
| Tokens | Access token en mémoire JS, refresh token en cookie httpOnly |
| Appels API | Via fetch avec header Authorization: Bearer {token} |
Ce qu'il NE fait PAS :
- Validation de permissions (c'est le backend qui décide)
- Accès direct à la base de données
- Stockage de secrets
Stockage des tokens (sécurité)
Danger : localStorage est vulnérable aux attaques XSS.
Si un attaquant arrive à injecter du JavaScript malveillant dans votre page (via XSS), il peut lire localStorage et voler les tokens :
// Depuis la console du navigateur, un script XSS peut exécuter :
const token = localStorage.getItem('access_token');
// → Vol de token immédiat
La bonne approche :
| Token | Stockage | Pourquoi |
|---|---|---|
access_token | Mémoire JS (React state/context) | Inaccessible au JavaScript externe, disappears au refresh |
refresh_token | Cookie httpOnly | Impossible à lire depuis le JS, protection XSS |
// Accès au token d'accès (via React context)
const { accessToken } = useAuth();
// Le refresh token est automatiquement envoyé par le browser via le cookie
// Le frontend n'a jamais accès au refresh_token en lecture
Conséquence : l'utilisateur doit se reconnecter si l'access_token expire, mais c'est le prix de la sécurité.
Backend (FastAPI)
Le backend est le point d'entrée unique de toute la plateforme.
| Responsabilité | Détail |
|---|---|
| Logique métier | Toutes les règles métier sont ici |
| Validation | Pydantic pour la validation des données entrantes |
| Permissions | Décide qui peut faire quoi (via JWT claims) |
| Accès DB | Seul composant à parler à Supabase |
Structure des routers (app/api/v1/api.py) :
| Router | Préfixe | Description |
|---|---|---|
auth | /auth | Login, register, refresh, logout |
users | /users | Gestion des utilisateurs |
students/* | /students | Dashboard étudiant (profile, skills, documents, applications, dashboard...) |
companies/* | /companies | Dashboard entreprise |
universities/* | /universities | Dashboard université |
internships/* | /internships | Gestion des offres de stage |
admin/* | /admin | Admin plateforme |
Pattern architectural : Le backend utilise le pattern Facade pour les services. Par exemple, AuthService délègue à :
AuthLoginService— gestion du loginAuthRegistrationService— gestion de l'inscriptionAuthUserManager— gestion des utilisateurs
Redis
Redis est utilisé pour le cache utilisateur, pas pour stocker les tokens JWT (qui sont stateless).
| Utilisation | Détail |
|---|---|
| Cache user data | Clé auth:token:{user_id}, TTL 15 minutes |
| Circuit breaker | Si Redis est down, le backend fallback directement sur la DB |
Important : ce que Redis ne fait PAS.
Redis est utilisé uniquement pour le cache de performance (accélérer les requêtes en cacheant les données utilisateur).
Redis ne gère PAS les sessions ni la révocation de tokens.
Conséquence : si un utilisateur est déconnecté (logout) ou suspendu, son token JWT reste valide jusqu'à son expiration naturelle. Il n'est pas possible de révoquer un token avant son expiration dans l'implémentation actuelle.
Cette lacune est documentée dans security/security-policy.md.
Fichier clé : app/core/redis_client.py
# Circuit breaker : si Redis down, return None immédiatement
if time.time() < _redis_down_until:
return None
Supabase (PostgreSQL)
Supabase gère à la fois l'authentification et la base de données.
| Composant | Rôle |
|---|---|
| Supabase Auth | Gestion des utilisateurs (inscription, login, mot de passe) |
| PostgreSQL | 40+ tables : users, students, companies, internships, applications... |
| Row Level Security (RLS) | Deux clients : normal (respecte RLS) vs admin (service role, bypass RLS) |
Deux clients Supabase (app/db/supabase.py) :
# Client normal — respecte les politiques RLS
supabase = create_client(SUPABASE_URL, SUPABASE_ANON_KEY)
# Client admin — bypass RLS (pour les opérations admin)
supabase_admin = create_client(SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY)
Cloudinary
Cloudinary est le stockage de fichiers (PDF rapports, photos de profil, etc.).
| Caractéristique | Détail |
|---|---|
| Accès | Uniquement via le backend |
| Flow | Frontend → Backend → Cloudinary |
Le frontend ne peut pas uploader directement sur Cloudinary — il passe toujours par le backend qui utilise le SDK Cloudinary.
Règles de communication inter-services
- Les frontends ne se parlent jamais entre eux
- Tout passe par le backend — point d'entrée unique
/api/v1/ - Les frontends ne lisent jamais la DB directement
- Le backend est le seul à écrire dans Redis
- Upload de fichiers : Frontend → Backend → Cloudinary
Middleware et sécurité
Le backend inclut plusieurs couches de sécurité (app/middleware/) :
| Middleware | Rôle |
|---|---|
| CORS | Liste blanche des origines autorisées |
| Security Headers | HSTS, X-Frame-Options, X-Content-Type-Options... |
| Rate Limiting | SlowAPI — limite les requêtes par IP |
| Global Exception Handler | Empêche les stack traces en production |