-
Notifications
You must be signed in to change notification settings - Fork 4
Auth Service
Le service d'authentification gรจre l'ensemble des fonctionnalitรฉs liรฉes ร l'authentification des utilisateurs, incluant l'inscription, la connexion, l'authentification ร deux facteurs (2FA/TOTP), et la gestion des tokens JWT.
- Architecture
- Technologies utilisรฉes
- Configuration
- Structure du projet
- Fonctionnalitรฉs
- API Endpoints
- Authentification JWT
- 2FA / TOTP
- Sรฉcuritรฉ
- Base de donnรฉes
- Tests et validation
Le service Auth suit une architecture en couches (layered architecture) :
Request โ Routes โ Controllers โ Services โ Database
- Routes : Dรฉfinition des endpoints et validation des inputs (schรฉmas JSON)
- Controllers : Gestion des requรชtes/rรฉponses HTTP, orchestration
- Services : Logique mรฉtier (business logic)
- Database : Accรจs aux donnรฉes (SQLite via better-sqlite3)
| Technologie | Usage | Documentation |
|---|---|---|
| Fastify | Framework web haute performance | Fastify |
| TypeScript | Typage statique | TypeScript |
| better-sqlite3 | Base de donnรฉes embarquรฉe | npm |
| bcrypt | Hashing de mots de passe | npm |
| @fastify/jwt | Gรฉnรฉration et validation JWT | GitHub |
| @fastify/cookie | Gestion des cookies | GitHub |
| @fastify/rate-limit | Protection contre les abus | GitHub |
| otplib | TOTP (Time-based OTP) | npm |
| qrcode | Gรฉnรฉration de QR codes pour 2FA | npm |
Fichier : srcs/.env.auth
# Environnement
NODE_ENV=production # development | test | production | staging
# Logging
LOG_ENABLED=true
LOG_LEVEL=info # debug | info | warn | error
# JWT Configuration - CRITIQUE SรCURITร
JWT_SECRET=your-super-secret-key-here-min-32-chars
# Service Configuration
AUTH_SERVICE_PORT=3001
AUTH_SERVICE_NAME=auth-service
AUTH_DB_PATH=/data/auth.db # Chemin vers la base SQLite
# Redis Configuration
REDIS_HOST=redis-broker
REDIS_PORT=6379
REDIS_PASSWORD=
# User Management Service
UM_SERVICE_NAME=user-service
UM_SERVICE_PORT=3002
# Admin User Configuration (utilisateur crรฉรฉ au dรฉmarrage)
ADMIN_USERNAME=admin
ADMIN_EMAIL=admin@transcendence.local
ADMIN_PASSWORD=Admin123! # โ ๏ธ ร CHANGER EN PRODUCTION
# Invite User Configuration (utilisateur invitรฉ)
INVITE_USERNAME=invite
INVITE_EMAIL=invite@transcendence.local
INVITE_PASSWORD=Invite123! # โ ๏ธ ร CHANGER EN PRODUCTION
# Application Configuration
APP_NAME=Transcendence # Nom affichรฉ dans l'app d'authentification 2FAJWT_SECRET doit รชtre :
- Au minimum 32 caractรจres (validation stricte au dรฉmarrage)
- Gรฉnรฉrรฉ alรฉatoirement :
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" - Jamais committรฉ dans Git
- Diffรฉrent entre dev et production
- Le service refuse de dรฉmarrer si le secret est invalide ou trop court
srcs/auth/
โโโ src/
โ โโโ index.ts # Point d'entrรฉe, configuration Fastify
โ โโโ config/
โ โ โโโ env.ts # Validation des variables d'environnement
โ โ โโโ logger.config.ts # Configuration Pino logger
โ โโโ controllers/
โ โ โโโ auth.controller.ts # Handlers HTTP (login, register, 2FA, etc.)
โ โโโ routes/
โ โ โโโ auth.routes.ts # Dรฉfinition des routes et rate limiting
โ โโโ services/
โ โ โโโ auth.service.ts # Logique mรฉtier authentification
โ โ โโโ jwt.service.ts # Gรฉnรฉration et validation JWT
โ โ โโโ totp.service.ts # Service TOTP/2FA
โ โ โโโ database.ts # Accรจs base de donnรฉes
โ โ โโโ external/
โ โ โโโ um.service.ts # Appels au service Users
โ โโโ types/
โ โ โโโ dto.ts # Data Transfer Objects
โ โ โโโ errors.ts # Classes d'erreurs personnalisรฉes
โ โ โโโ logger.ts # Types pour le logger
โ โ โโโ models.ts # Modรจles de donnรฉes
โ โโโ utils/
โ โโโ constants.ts # Constantes (codes d'erreur, config)
โ โโโ error-catalog.ts # Catalogue d'erreurs
โ โโโ init-users.ts # Initialisation utilisateurs admin
โ โโโ validation.ts # Schรฉmas de validation Zod
โโโ Dockerfile # Image de production
โโโ Dockerfile.dev # Image de dรฉveloppement
โโโ package.json
โโโ tsconfig.json
- Validation des donnรฉes (email, username, password)
- Vรฉrification de l'unicitรฉ (email et username)
- Hashing du mot de passe avec bcrypt (10 rounds)
- Crรฉation de l'utilisateur dans la base
- Gรฉnรฉration d'un JWT
- Retour du token dans un cookie HTTP-only
- Vรฉrification des credentials (username/email + password)
- Support de l'authentification 2FA
- Si 2FA activรฉ : retour d'un token temporaire
- Si 2FA dรฉsactivรฉ : connexion directe
- Gรฉnรฉration du JWT
- Rate limiting strict (5 tentatives / minute)
- Invalidation du cookie token
- Retour d'un cookie expirรฉ
- Gรฉnรฉration d'un secret TOTP unique
- Crรฉation d'une URL
otpauth://compatible Google Authenticator - Gรฉnรฉration d'un QR code (base64)
- Stockage temporaire du secret (2 minutes)
- Cookie de session setup
- Validation du code TOTP ร 6 chiffres
- Activation permanente du 2FA dans le profil
- Stockage du secret TOTP
- Aprรจs login initial : token temporaire (2 minutes)
- Validation du code TOTP
- Maximum 3 tentatives
- Gรฉnรฉration du JWT final aprรจs validation
- Par l'utilisateur (nรฉcessite mot de passe)
- Par un admin (endpoint dรฉdiรฉ)
- Endpoint pour vรฉrifier la validitรฉ d'un JWT
- Utilisรฉ par les autres services
- Liste de tous les utilisateurs
- Crรฉation d'utilisateurs
- Mise ร jour d'utilisateurs
- Suppression d'utilisateurs
- Dรฉsactivation forcรฉe du 2FA
Important
Tous les endpoints sont prรฉfixรฉs par /api/auth
Exemple complet : https://URL:PORT/api/auth/login
| Mรฉthode | Route | Description | Rate Limit | Body |
|---|---|---|---|---|
| GET | / |
Health check basique | - | - |
| GET | /health |
Health check dรฉtaillรฉ | - | - |
| POST | /register |
Inscription d'un nouvel utilisateur | 5/5min (dev: 100) | { username, email, password } |
| POST | /login |
Connexion | 5/5min (dev: 100) | { username ou email, password } |
Dรฉtails /register:
-
Validation stricte avec Zod (username 4-20 chars alphanumรฉriques +
_, email valide, password 8+ chars avec maj, min, chiffre et caractรจre spรฉcial!@#$%^&*) - Vรฉrifie l'unicitรฉ du username et de l'email
- Hash du password avec bcrypt (10 rounds)
- Crรฉe le profil utilisateur via le service UM (User Management)
- Retourne les infos utilisateur (pas de token automatique - nรฉcessite login)
-
Codes de retour:
-
201 CREATED: Utilisateur crรฉรฉ -
400 BAD_REQUEST: Validation รฉchouรฉe -
409 CONFLICT: Username ou email dรฉjร pris -
500 INTERNAL_SERVER_ERROR: Erreur serveur
-
Dรฉtails /login:
- Accepte
usernameOUemail+password - Si 2FA activรฉ : retourne
require2FA: true+ cookie temporaire2fa_login_token(2 min) - Si 2FA dรฉsactivรฉ : retourne JWT dans cookie
token(1h) -
Codes de retour:
-
200 OK: Login rรฉussi (avec ou sans 2FA) -
400 BAD_REQUEST: Validation รฉchouรฉe -
401 UNAUTHORIZED: Credentials invalides -
500 INTERNAL_SERVER_ERROR: Erreur serveur
-
Ces endpoints nรฉcessitent un token JWT valide dans le cookie token OU le header Authorization: Bearer <token>.
Le JWT est validรฉ et les headers x-user-id et x-user-name sont injectรฉs par la Gateway.
| Mรฉthode | Route | Description | Headers injectรฉs |
|---|---|---|---|
| POST | /logout |
Dรฉconnexion (efface le cookie) | x-user-name |
| GET | /me |
Rรฉcupรฉrer infos utilisateur connectรฉ |
x-user-id, x-user-name
|
| POST | /heartbeat |
Met ร jour le statut "en ligne" dans Redis (TTL: 30s) | x-user-id |
Dรฉtails /me:
- Retourne :
{ id, username, email, role, is2FAEnabled } - Note: Endpoint marquรฉ "DEV ONLY" - ร supprimer en production
Dรฉtails /heartbeat:
- Rate limit: 10/10s
- Utilisรฉ par le frontend pour maintenir le statut "en ligne"
- Stocke un heartbeat dans Redis avec TTL de 30 secondes
- Si pas de heartbeat pendant 30s, l'utilisateur est considรฉrรฉ offline
| Mรฉthode | Route | Description | Rate Limit | Cookies requis |
|---|---|---|---|---|
| POST | /2fa/setup |
Dรฉmarrer la configuration 2FA (gรฉnรจre QR code) | 5/5min (dev: 100) | token |
| POST | /2fa/setup/verify |
Valider le code et activer la 2FA | 5/5min (dev: 100) | 2fa_setup_token |
| POST | /2fa/verify |
Valider le code lors du login | 5/5min (dev: 100) | 2fa_login_token |
| POST | /2fa/disable |
Dรฉsactiver la 2FA | - | token |
Flow complet 2FA Setup:
-
POST /2fa/setup(JWT requis)- Vรฉrifie que 2FA n'est pas dรฉjร activรฉe
- Gรฉnรจre un secret TOTP unique
- Crรฉe une URL
otpauth://totp/Transcendence:username?secret=... - Gรฉnรจre un QR code en base64
- Stocke le secret temporairement (2 min) avec un token de session
- Retourne :
{ qrCode: "data:image/png;base64,...", expiresIn: 120 } - Cookie
2fa_setup_tokencrรฉรฉ (secure, httpOnly, 2 min)
-
POST /2fa/setup/verify(cookie2fa_setup_tokenrequis)- Body:
{ code: "123456" }(6 chiffres) - Valide le code TOTP avec le secret temporaire
- Max 3 tentatives, sinon doit recommencer le setup
- Si valide : active la 2FA dรฉfinitivement (stocke secret dans DB)
- Gรฉnรจre un nouveau JWT et retourne cookie
token - Supprime le cookie
2fa_setup_token
- Body:
Flow complet 2FA Login:
-
POST /loginโ retournerequire2FA: true+ cookie2fa_login_token(2 min) -
POST /2fa/verify(cookie2fa_login_tokenrequis)- Body:
{ code: "123456" }(6 chiffres) - Valide le code TOTP avec le secret permanent
- Max 3 tentatives, sinon doit se reconnecter
- Si valide : gรฉnรจre JWT final et retourne cookie
token - Supprime le cookie
2fa_login_token
- Body:
Dรฉtails /2fa/disable:
- Nรฉcessite JWT valide
- Vรฉrifie que 2FA est activรฉe
- Supprime le secret TOTP de la DB
- Dรฉsactive
is_2fa_enableddans le profil utilisateur
Tous ces endpoints nรฉcessitent un JWT valide + rรดle ADMIN.
La vรฉrification est faite via le prรฉ-handler verifyAdminRole qui lit les headers x-user-id et x-user-name.
| Mรฉthode | Route | Description | Rate Limit | Body |
|---|---|---|---|---|
| GET | /admin/users |
Liste tous les utilisateurs | 50/min | - |
| PUT | /admin/users/:id |
Modifier un utilisateur | 20/min | { username, email, role } |
| DELETE | /admin/users/:id |
Supprimer un utilisateur | 10/min | - |
| POST | /admin/users/:id/disable-2fa |
Forcer dรฉsactivation 2FA | 10/min | - |
Dรฉtails /admin/users (GET):
- Retourne tous les utilisateurs avec :
- Infos de base :
id,username,email,role - รtat 2FA :
is2FAEnabled - Statut en ligne :
online(via Redis bulk check)
- Infos de base :
-
Response:
{ users: [{ id, username, email, role, is2FAEnabled, online }, ...] }
Dรฉtails /admin/users/:id (PUT):
- Permet de modifier
username,emailetrole - Vรฉrifie l'unicitรฉ du username et de l'email
- Role doit รชtre
userouadmin -
Codes de retour:
-
200 OK: Utilisateur modifiรฉ -
400 BAD_REQUEST: Donnรฉes invalides -
403 FORBIDDEN: Non admin -
404 NOT_FOUND: Utilisateur inexistant -
409 CONFLICT: Username ou email dรฉjร utilisรฉ
-
Dรฉtails /admin/users/:id (DELETE):
- Empรชche l'auto-suppression (admin ne peut pas se supprimer)
- Supprime l'utilisateur de la DB Auth + appelle le service UM pour cleanup
-
Codes de retour:
-
200 OK: Utilisateur supprimรฉ -
400 BAD_REQUEST: Tentative d'auto-suppression -
403 FORBIDDEN: Non admin -
404 NOT_FOUND: Utilisateur inexistant
-
Dรฉtails /admin/users/:id/disable-2fa (POST):
- Dรฉsactive la 2FA d'un utilisateur (pour dรฉblocage)
- Vรฉrifie que la 2FA est activรฉe avant de la dรฉsactiver
-
Codes de retour:
-
200 OK: 2FA dรฉsactivรฉe -
400 BAD_REQUEST: 2FA dรฉjร dรฉsactivรฉe -
403 FORBIDDEN: Non admin -
404 NOT_FOUND: Utilisateur inexistant
-
| Mรฉthode | Route | Description | Rate Limit | Params |
|---|---|---|---|---|
| GET | /is-online/:name |
Vรฉrifie si un utilisateur est en ligne | 200/min (dev: 1000) |
name (username) |
Dรฉtails /is-online/:name:
- Retourne :
{ username: "...", isOnline: true/false } - Vรฉrifie dans Redis si un heartbeat existe (< 30s)
-
Codes de retour:
-
200 OK: Status retournรฉ -
400 BAD_REQUEST: Username manquant -
404 NOT_FOUND: Utilisateur inexistant
-
Le JWT contient les informations suivantes dans le payload :
{
"sub": 123, // User ID (subject)
"username": "alice", // Nom d'utilisateur
"role": "user", // Rรดle (user | admin)
"iat": 1640000000, // Issued at (timestamp)
"exp": 1640003600 // Expiration (timestamp)
}- Gรฉnรฉration : Lors du login rรฉussi ou aprรจs validation 2FA
-
Stockage : Cookie HTTP-only nommรฉ
token - Validation : Par la Gateway avant chaque requรชte protรฉgรฉe
-
Headers injectรฉs :
x-user-idetx-user-nameajoutรฉs par la Gateway aprรจs validation -
Expiration : 1 heure (dรฉfini dans
AUTH_CONFIG.JWT_EXPIRATION = '1h')
{
httpOnly: true, // Pas accessible en JavaScript (protection XSS)
secure: true, // HTTPS uniquement (production, ou si FORCE_SECURE_COOKIE=true)
sameSite: 'strict', // Protection CSRF (note: pas 'lax')
path: '/', // Disponible sur toutes les routes
maxAge: 3600 // 1 heure (sync avec JWT expiration)
}Note: En dรฉveloppement, secure: false est autorisรฉ si NODE_ENV=development ET FORCE_SECURE_COOKIE n'est pas dรฉfini.
La Gateway valide les JWT pour des raisons de performance :
// La Gateway :
// 1. Extrait le token du cookie 'token' ou du header 'Authorization: Bearer <token>'
// 2. Vรฉrifie la signature JWT avec JWT_SECRET (identique cรดtรฉ Auth et Gateway)
// 3. Dรฉcode le payload
// 4. Injecte les headers x-user-id, x-user-name, x-user-role
// 5. Proxyfy la requรชte vers le service cibleโ Bonnes pratiques implรฉmentรฉes :
- Secret d'au moins 32 caractรจres (validation stricte au dรฉmarrage avec
envalid) - Algorithme HS256 (HMAC + SHA256) via
@fastify/jwt - Validation stricte du format et de la signature
- Vรฉrification de l'expiration automatique par
@fastify/jwt - Cookie HTTP-only (protection XSS)
- Cookie SameSite=strict (protection CSRF)
- Cookie Secure en production (HTTPS only)
- Validation locale par la Gateway
- Refresh tokens : pour prolonger la session sans re-login complet
- Rรฉvocation de tokens : blacklist Redis pour logout forcรฉ/invalidation immรฉdiate
- Rotation du secret JWT : permettre le changement du secret sans casser toutes les sessions
- Token version/jti : invalider tous les tokens d'un utilisateur en une fois (ex: aprรจs changement de password)
TOTP (Time-based One-Time Password) gรฉnรจre des codes ร 6 chiffres basรฉs sur :
- Un secret partagรฉ (stockรฉ dans la DB Auth et dans l'app authenticator de l'utilisateur)
- Le temps actuel (fenรชtres de 30 secondes)
Code = HMAC-SHA1(secret, floor(timestamp / 30))
Paramรจtres TOTP:
- Algorithme : SHA1 (standard TOTP)
-
Pรฉriode : 30 secondes (dรฉfini dans
AUTH_CONFIG.TOTP_STEP = 30) -
Digits : 6 chiffres (dรฉfini dans
AUTH_CONFIG.TOTP_DIGITS = 6) -
Window : ยฑ1 pรฉriode (ยฑ30s, dรฉfini dans
AUTH_CONFIG.TOTP_WINDOW = 1) -
Issuer : "Transcendence" (dรฉfini dans
AUTH_CONFIG.TOTP_ISSUERviaAPP_NAME)
// utils/constants.ts
TOTP_WINDOW: 1, // ยฑ30 secondes de tolรฉrance
TOTP_STEP: 30, // Pรฉriode de rotation (standard)
TOTP_DIGITS: 6, // Code ร 6 chiffres
TOTP_ISSUER: 'Transcendence',
TOTP_SETUP_EXPIRATION_SECONDS: 120, // Expiration du secret temporairesequenceDiagram
participant User
participant Frontend
participant Auth Service
participant SQLite DB
participant Authenticator App
User->>Frontend: Clique "Activer 2FA"
Frontend->>Auth Service: POST /2fa/setup (JWT token)
Auth Service->>Auth Service: Vรฉrifie que 2FA pas dรฉjร activรฉe
Auth Service->>Auth Service: Gรฉnรจre secret TOTP alรฉatoire
Auth Service->>Auth Service: Crรฉe URL otpauth://totp/...
Auth Service->>Auth Service: Gรฉnรจre QR code (base64)
Auth Service->>SQLite DB: INSERT INTO totp_setup_secrets (token, user_id, secret, expires_at)
Auth Service-->>Frontend: Cookie 2fa_setup_token (2 min) + QR code
Frontend->>User: Affiche QR code
User->>Authenticator App: Scanne QR code
Authenticator App->>Authenticator App: Stocke secret localement
Authenticator App->>User: Affiche code 6 chiffres
User->>Frontend: Entre code
Frontend->>Auth Service: POST /2fa/setup/verify + code
Auth Service->>SQLite DB: Rรฉcupรจre secret temporaire via token
Auth Service->>Auth Service: Valide code TOTP (max 3 tentatives)
Auth Service->>SQLite DB: UPDATE users SET is_2fa_enabled=1, totp_secret=...
Auth Service->>SQLite DB: DELETE FROM totp_setup_secrets
Auth Service->>Auth Service: Gรฉnรจre nouveau JWT
Auth Service-->>Frontend: Cookie token (1h)
Frontend->>User: "2FA activรฉe โ"
sequenceDiagram
participant User
participant Frontend
participant Auth Service
participant SQLite DB
participant Authenticator App
User->>Frontend: Entre username + password
Frontend->>Auth Service: POST /login
Auth Service->>SQLite DB: Vรฉrifie credentials
Auth Service->>SQLite DB: Vรฉrifie si is_2fa_enabled=1
Auth Service->>Auth Service: Gรฉnรจre loginToken temporaire
Auth Service->>SQLite DB: INSERT INTO login_tokens
Auth Service-->>Frontend: Cookie 2fa_login_token (2 min) + require2FA: true
Frontend->>User: Demande code 2FA
User->>Authenticator App: Ouvre app
Authenticator App->>User: Affiche code 6 chiffres
User->>Frontend: Entre code
Frontend->>Auth Service: POST /2fa/verify + code
Auth Service->>SQLite DB: Rรฉcupรจre user_id via loginToken
Auth Service->>SQLite DB: Rรฉcupรจre totp_secret de l'utilisateur
Auth Service->>Auth Service: Valide code TOTP (max 3 tentatives)
Auth Service->>SQLite DB: DELETE FROM login_tokens
Auth Service->>Auth Service: Gรฉnรจre JWT final
Auth Service-->>Frontend: Cookie token (1h)
Frontend->>User: Connectรฉ โ
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
email TEXT UNIQUE NOT NULL,
password_hash TEXT NOT NULL,
role TEXT DEFAULT 'user', -- 'user' | 'admin'
is_2fa_enabled INTEGER DEFAULT 0, -- Boolean (0 ou 1)
totp_secret TEXT, -- Secret TOTP chiffrรฉ (ou NULL si 2FA dรฉsactivรฉe)
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);Stockage temporaire pendant la configuration (2 minutes) :
CREATE TABLE totp_setup_secrets (
token TEXT PRIMARY KEY, -- Token UUID alรฉatoire
user_id INTEGER NOT NULL,
secret TEXT NOT NULL, -- Secret TOTP temporaire
expires_at DATETIME NOT NULL, -- Timestamp d'expiration (now + 120s)
attempts INTEGER DEFAULT 0, -- Compteur de tentatives (max 3)
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
);Nettoyage: Supprimรฉ automatiquement aprรจs validation rรฉussie ou expiration.
Tokens temporaires pour le login 2FA (2 minutes) :
CREATE TABLE login_tokens (
token TEXT PRIMARY KEY, -- Token UUID alรฉatoire
user_id INTEGER NOT NULL,
expires_at DATETIME NOT NULL, -- Timestamp d'expiration (now + 120s)
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
);Compteur de tentatives (max 3) pour les login tokens :
CREATE TABLE login_token_attempts (
token TEXT PRIMARY KEY, -- Rรฉfรฉrence au login_token
attempts INTEGER DEFAULT 0, -- Nombre de tentatives รฉchouรฉes (max 3)
FOREIGN KEY(token) REFERENCES login_tokens(token) ON DELETE CASCADE
);Logique de sรฉcuritรฉ:
- Max 3 tentatives de code TOTP
- Au-delร : suppression du login_token โ utilisateur doit se reconnecter avec username/password
Le service effectue un nettoyage pรฉriodique des sessions expirรฉes :
// index.ts
setInterval(() => {
totpService.cleanupExpiredSessions();
authService.cleanupExpiredTokens();
}, AUTH_CONFIG.CLEANUP_INTERVAL_MS); // 5 minutesรlรฉments nettoyรฉs:
-
totp_setup_secretsexpirรฉs (> 2 min) -
login_tokensexpirรฉs (> 2 min) - Entrรฉes orphelines dans
login_token_attempts - Au dรฉmarrage du service
- Toutes les 5 minutes (maintenance automatique)
Limites adaptรฉes selon l'environnement (dev/test vs production) :
| Endpoint | Limite Prod | Limite Dev/Test | Fenรชtre |
|---|---|---|---|
| Global | 1000 req | 10000 req | 1 minute |
/register |
5 req | 100 req | 5 minutes |
/login |
5 req | 100 req | 5 minutes |
/2fa/setup |
5 req | 100 req | 5 minutes |
/2fa/setup/verify |
5 req | 100 req | 5 minutes |
/2fa/verify |
5 req | 100 req | 5 minutes |
/heartbeat |
10 req | 10 req | 10 secondes |
/is-online/:name |
200 req | 1000 req | 1 minute |
/admin/users (GET) |
50 req | 50 req | 1 minute |
/admin/users/:id (PUT) |
20 req | 20 req | 1 minute |
/admin/users/:id (DELETE) |
10 req | 10 req | 1 minute |
/admin/users/:id/disable-2fa |
10 req | 10 req | 1 minute |
Configuration:
// utils/constants.ts
const isTestOrDev = ['test', 'development'].includes(authenv.NODE_ENV);
RATE_LIMIT: {
LOGIN: {
max: isTestOrDev ? 100 : 5,
timeWindow: '5 minutes',
},
// ...
}Implรฉmentation: Plugin @fastify/rate-limit configurรฉ par route.
-
Algorithme : bcrypt (via
bcryptnpm package) -
Rounds : 10 (dรฉfini dans
AUTH_CONFIG.BCRYPT_ROUNDS = 10) - Jamais de stockage en clair
- Salt : gรฉnรฉrรฉ automatiquement par bcrypt (intรฉgrรฉ au hash)
import bcrypt from 'bcrypt';
// Hash lors de l'inscription
const hashedPassword = await bcrypt.hash(password, AUTH_CONFIG.BCRYPT_ROUNDS);
// Vรฉrification lors du login
const isValid = await bcrypt.compare(password, user.password_hash);Utilisation de Zod pour valider tous les inputs utilisateur :
// utils/validation.ts
const registerSchema = z.object({
username: z.string()
.min(4, 'Username must be at least 4 characters')
.max(20, 'Username must be at most 20 characters')
.regex(/^[a-zA-Z0-9_]+$/, 'Username must contain only letters, numbers and underscores')
.refine(val => !RESERVED_USERNAMES.includes(val.toLowerCase()),
{ message: 'This username is reserved' }),
email: z.string()
.email('Invalid email format')
.max(100, 'Email too long'),
password: z.string()
.min(8, 'Password must be at least 8 characters')
.max(100, 'Password too long')
.regex(/^(?=.*[a-z])/, 'Must contain lowercase letter')
.regex(/^(?=.*[A-Z])/, 'Must contain uppercase letter')
.regex(/^(?=.*\d)/, 'Must contain a number')
.regex(/^(?=.*[!@#$%^&*])/, 'Must contain special character (!@#$%^&*)')
});Usernames rรฉservรฉs:
// utils/constants.ts
const RESERVED_USERNAMES = [
'admin', 'root', 'system', 'administrator',
'superuser', 'guest', 'support', 'service', 'daemon'
];Champs sensibles jamais loggรฉs en clair:
// utils/constants.ts
const SENSITIVE_FIELDS = [
'password', 'token', 'secret', 'totp_secret',
'jwt', 'authorization', 'cookie', 'loginToken',
'2fa_login_token', '2fa_setup_token',
'access_token', 'refresh_token',
];
const REDACT_PATHS = [
'req.headers.authorization',
'req.headers.cookie',
...SENSITIVE_FIELDS.map(field => `*.${field}`),
];Configuration Pino logger:
// config/logger.config.ts
import pino from 'pino';
const logger = pino({
redact: {
paths: REDACT_PATHS,
censor: '[REDACTED]',
},
});Configuration stricte des cookies JWT:
-
httpOnly: trueโ Pas accessible en JavaScript (protection XSS) -
secure: trueโ HTTPS uniquement en production -
sameSite: 'strict'โ Protection CSRF -
path: '/'โ Disponible sur toutes les routes - Pas de cookie persistent โ Expiration synchronisรฉe avec JWT (1h)
Headers automatiquement ajoutรฉs par Fastify (via plugins ou configuration) :
-
X-Content-Type-Options: nosniffโ Empรชche le MIME sniffing -
X-Frame-Options: DENYโ Empรชche l'iframe (protection clickjacking) -
X-XSS-Protection: 1; mode=blockโ Active la protection XSS du navigateur
- Max 3 tentatives de code TOTP (setup et login)
- Expiration courte des tokens temporaires (2 minutes)
- Nettoyage automatique des sessions expirรฉes (toutes les 5 minutes)
- Window TOTP de ยฑ30s pour compenser le dรฉcalage horaire
-
Vรฉrification du rรดle via prรฉ-handler
verifyAdminRole - Empรชche l'auto-suppression (admin ne peut pas se supprimer)
- Logs dรฉtaillรฉs de toutes les actions admin (avec user_id et username)
-
Requรชtes prรฉparรฉes : utilisation de
better-sqlite3avec placeholders (?) - Transactions : pour les opรฉrations critiques (register + crรฉation profil UM)
-
Contraintes DB :
UNIQUEsur username et email,FOREIGN KEYavecON DELETE CASCADE - Isolation : base SQLite locale, pas d'accรจs direct depuis l'extรฉrieur
Le service Auth gรจre le statut "en ligne" des utilisateurs via Redis.
Frontend โ POST /heartbeat (toutes les 10-20s)
โ Auth Service โ Redis SET user:{userId}:online EX 30
Autre service โ GET /is-online/:name
โ Auth Service โ Redis GET user:{userId}:online
Endpoint: POST /heartbeat
// services/online.service.ts
async function recordHeartbeat(userId: number): Promise<void> {
const key = `user:${userId}:online`;
await redis.setex(key, 30, Date.now().toString());
// TTL: 30 secondes
}Logique:
- Le frontend envoie un heartbeat toutes les 10-20 secondes
- Redis stocke une clรฉ avec TTL de 30 secondes
- Si pas de heartbeat pendant 30s โ clรฉ expirรฉe โ utilisateur offline
Rate limit: 10 requรชtes / 10 secondes (pour รฉviter le spam)
Endpoint: GET /is-online/:name
async function isUserOnline(userId: number): Promise<boolean> {
const key = `user:${userId}:online`;
const result = await redis.get(key);
return result !== null;
}Bulk check (pour /admin/users) :
async function getBulkOnlineStatus(userIds: number[]): Promise<Map<number, boolean>> {
const pipeline = redis.pipeline();
userIds.forEach(id => pipeline.get(`user:${id}:online`));
const results = await pipeline.exec();
const statusMap = new Map<number, boolean>();
userIds.forEach((id, index) => {
statusMap.set(id, results[index][1] !== null);
});
return statusMap;
}Redis gรจre automatiquement l'expiration des clรฉs (TTL 30s).
Un nettoyage supplรฉmentaire est effectuรฉ toutes les 60 secondes :
// index.ts
setInterval(() => {
onlineService.cleanupStaleHeartbeats();
}, AUTH_CONFIG.ONLINE_STATUS_CLEANUP_INTERVAL_MS); // 60 secondes// config/env.ts
REDIS_HOST: str({ default: 'redis-broker' }),
REDIS_PORT: port({ default: 6379 }),
REDIS_PASSWORD: str({ default: '' }),Connexion:
// services/online.service.ts
import Redis from 'ioredis';
const redis = new Redis({
host: authenv.REDIS_HOST,
port: authenv.REDIS_PORT,
password: authenv.REDIS_PASSWORD,
lazyConnect: true,
retryStrategy: (times) => Math.min(times * 50, 2000),
});-
Moteur : SQLite via
better-sqlite3 -
Chemin :
/data/auth.db(configurable viaAUTH_DB_PATH) - Mode : WAL (Write-Ahead Logging) pour de meilleures performances
- Contraintes : Foreign keys activรฉes
// services/database.ts
import Database from 'better-sqlite3';
const db = new Database(authenv.AUTH_DB_PATH);
db.pragma('journal_mode = WAL');
db.pragma('foreign_keys = ON');CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
email TEXT UNIQUE NOT NULL,
password_hash TEXT NOT NULL,
role TEXT DEFAULT 'user' CHECK(role IN ('user', 'admin')),
is_2fa_enabled INTEGER DEFAULT 0 CHECK(is_2fa_enabled IN (0, 1)),
totp_secret TEXT,
created_at TEXT DEFAULT (datetime('now')),
updated_at TEXT DEFAULT (datetime('now'))
);
CREATE INDEX idx_users_username ON users(username);
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_users_role ON users(role);Colonnes:
-
id: ID unique auto-incrรฉmentรฉ -
username: Nom d'utilisateur unique (4-20 chars) -
email: Email unique (validรฉ cรดtรฉ app) -
password_hash: Hash bcrypt du mot de passe (60 chars) -
role: Rรดle ('user' | 'admin') -
is_2fa_enabled: Boolean (0 ou 1) -
totp_secret: Secret TOTP chiffrรฉ (NULL si 2FA dรฉsactivรฉe) -
created_at: Date de crรฉation (ISO 8601) -
updated_at: Date de derniรจre modification (ISO 8601)
CREATE TABLE totp_setup_secrets (
token TEXT PRIMARY KEY,
user_id INTEGER NOT NULL,
secret TEXT NOT NULL,
expires_at TEXT NOT NULL,
attempts INTEGER DEFAULT 0,
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
);
CREATE INDEX idx_totp_setup_expires ON totp_setup_secrets(expires_at);
CREATE INDEX idx_totp_setup_user ON totp_setup_secrets(user_id);Usage: Stockage temporaire du secret TOTP pendant la configuration (2 min).
CREATE TABLE login_tokens (
token TEXT PRIMARY KEY,
user_id INTEGER NOT NULL,
expires_at TEXT NOT NULL,
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
);
CREATE INDEX idx_login_tokens_expires ON login_tokens(expires_at);
CREATE INDEX idx_login_tokens_user ON login_tokens(user_id);Usage: Tokens temporaires pour le login 2FA (2 min).
CREATE TABLE login_token_attempts (
token TEXT PRIMARY KEY,
attempts INTEGER DEFAULT 0,
FOREIGN KEY(token) REFERENCES login_tokens(token) ON DELETE CASCADE
);Usage: Compteur de tentatives de validation TOTP (max 3).
Toutes les requรชtes utilisent des prepared statements pour รฉviter les injections SQL :
// โ
Bon (prepared statement)
const stmt = db.prepare('SELECT * FROM users WHERE username = ?');
const user = stmt.get(username);
// โ Mauvais (injection SQL possible)
const user = db.prepare(`SELECT * FROM users WHERE username = '${username}'`).get();Les opรฉrations critiques utilisent des transactions :
// services/auth.service.ts
function createUser(data: CreateUserInput): number {
const transaction = db.transaction(() => {
// 1. Crรฉer utilisateur dans Auth DB
const result = db.prepare(`
INSERT INTO users (username, email, password_hash, role)
VALUES (?, ?, ?, ?)
`).run(data.username, data.email, data.password_hash, 'user');
const userId = result.lastInsertRowid as number;
// 2. Crรฉer profil dans User Management Service
await umService.createProfile(userId, data.username, data.email);
return userId;
});
return transaction();
}Au dรฉmarrage, le service :
- Crรฉe les tables si elles n'existent pas
- Crรฉe un utilisateur admin par dรฉfaut (si pas dรฉjร existant)
- Crรฉe un utilisateur invite par dรฉfaut (si pas dรฉjร existant)
// utils/init-users.ts
export function initializeDefaultUsers() {
// Admin user
if (!authService.findByUsername(authenv.ADMIN_USERNAME)) {
authService.createUser({
username: authenv.ADMIN_USERNAME,
email: authenv.ADMIN_EMAIL,
password: authenv.ADMIN_PASSWORD,
role: UserRole.ADMIN,
});
}
// Invite user
if (!authService.findByUsername(authenv.INVITE_USERNAME)) {
authService.createUser({
username: authenv.INVITE_USERNAME,
email: authenv.INVITE_EMAIL,
password: authenv.INVITE_PASSWORD,
role: UserRole.USER,
});
}
}-
Volume Docker :
/datamontรฉ sur l'hรดte (data/database/auth.db) -
Mode WAL : fichiers
-shmet-walcrรฉรฉs automatiquement - Pas de migration automatique : schรฉma stable, migrations manuelles si nรฉcessaire
Le projet fournit des scripts de test bash :
# Test du service Auth complet
./test-auth.sh
# Test cross-service (Auth + Users)
./test-auth-cross.shcurl -X POST http://localhost:3001/register \
-H "Content-Type: application/json" \
-d '{
"username": "testuser",
"email": "test@example.com",
"password": "Test123!@#"
}'curl -X POST http://localhost:3001/login \
-H "Content-Type: application/json" \
-c cookies.txt \
-d '{
"username": "testuser",
"password": "Test123!@#"
}'curl -X POST http://localhost:3001/heartbeat \
-b cookies.txtcurl -X GET http://localhost:3001/admin/users \
-b cookies.txt-
Vรฉrifier le statut en ligne :
GET /is-online/:name
Headers injectรฉs par la Gateway aprรจs validation :
-
x-user-id: ID de l'utilisateur -
x-user-name: Username de l'utilisateur -
x-user-role: Rรดle de l'utilisateur
Le service Auth utilise Redis pour :
- Heartbeats : Statut en ligne des utilisateurs
- Futures features : Token blacklist, session store, rate limiting distribuรฉ
- Refresh tokens : Prolonger la session sans re-login
- Token blacklist : Invalider les JWT (logout forcรฉ)
- Account recovery : Reset password par email
- Email verification : Valider l'email lors de l'inscription
- Tests unitaires : Vitest pour chaque service/controller
- Rotation JWT secret : Permettre le changement du secret sans casser les sessions
- Session management : Vue admin des sessions actives
- IP whitelisting : Restreindre les connexions admin par IP
- Audit logs : Logs persistรฉs pour compliance
- CAPTCHA : Protection brute force avancรฉe
- OAuth providers : Login avec Google, GitHub, 42
- Multi-device 2FA : Backup codes, SMS
- Rate limiting distribuรฉ : Via Redis pour scale horizontale
- Fastify : Framework web
- TypeScript : Langage
- Zod : Validation de schรฉmas
- Logging and Error management : Systรจme de logs
- Gateway Service : Intรฉgration avec la Gateway
- Fastify Official Docs
- JWT Best Practices
- TOTP RFC 6238
- OWASP Authentication Cheat Sheet
- better-sqlite3 Docs
Rom9
Derniรจre mise ร jour: 27 janvier 2026
- Gateway Service - API Gateway & JWT validation
- Auth Service - Authentication & 2FA/TOTP
- AI Service - AI opponent
- API Documentation - OpenAPI/Swagger
- Fastify - Web framework
- Prisma - ORM
- WebSockets - Real-time communication
- Restful API - API standards
- React - UI library
- CSS - Styling
- Tailwind - CSS framework
- Accessibility - WCAG compliance
- TypeScript - Language
- Zod - Schema validation
- Nginx - Reverse proxy
- Logging and Error management - Observability
- OAuth 2.0 - Authentication flows
- Two-factor authentication - 2FA/TOTP
- Avalanche - Blockchain network
- Hardhat - Development framework
- Solidity - Smart contracts language
- Open Zeppelin - Security standards
- ESLint - Linting
- Vitest - Testing
- GitHub Actions - CI/CD
- Husky, Commit lints and git hooks - Git hooks
- ELK - Logging stack
๐ Page model