Aller au contenu principal

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
AffichageComposants React, pages, routing Next.js
UXInteractions utilisateur, formulaires, feedback
TokensAccess token en mémoire JS, refresh token en cookie httpOnly
Appels APIVia 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 :

TokenStockagePourquoi
access_tokenMémoire JS (React state/context)Inaccessible au JavaScript externe, disappears au refresh
refresh_tokenCookie httpOnlyImpossible à 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étierToutes les règles métier sont ici
ValidationPydantic pour la validation des données entrantes
PermissionsDécide qui peut faire quoi (via JWT claims)
Accès DBSeul composant à parler à Supabase

Structure des routers (app/api/v1/api.py) :

RouterPréfixeDescription
auth/authLogin, register, refresh, logout
users/usersGestion des utilisateurs
students/*/studentsDashboard étudiant (profile, skills, documents, applications, dashboard...)
companies/*/companiesDashboard entreprise
universities/*/universitiesDashboard université
internships/*/internshipsGestion des offres de stage
admin/*/adminAdmin plateforme

Pattern architectural : Le backend utilise le pattern Facade pour les services. Par exemple, AuthService délègue à :

  • AuthLoginService — gestion du login
  • AuthRegistrationService — gestion de l'inscription
  • AuthUserManager — gestion des utilisateurs

Redis

Redis est utilisé pour le cache utilisateur, pas pour stocker les tokens JWT (qui sont stateless).

UtilisationDétail
Cache user dataClé auth:token:{user_id}, TTL 15 minutes
Circuit breakerSi 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.

ComposantRôle
Supabase AuthGestion des utilisateurs (inscription, login, mot de passe)
PostgreSQL40+ 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éristiqueDétail
AccèsUniquement via le backend
FlowFrontend → 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

  1. Les frontends ne se parlent jamais entre eux
  2. Tout passe par le backend — point d'entrée unique /api/v1/
  3. Les frontends ne lisent jamais la DB directement
  4. Le backend est le seul à écrire dans Redis
  5. Upload de fichiers : Frontend → Backend → Cloudinary

Middleware et sécurité

Le backend inclut plusieurs couches de sécurité (app/middleware/) :

MiddlewareRôle
CORSListe blanche des origines autorisées
Security HeadersHSTS, X-Frame-Options, X-Content-Type-Options...
Rate LimitingSlowAPI — limite les requêtes par IP
Global Exception HandlerEmpêche les stack traces en production