Files
Classeo/.github/workflows/ci.yml
Mathias STRASSER b9d9f48305 feat: Connexion utilisateur avec sécurité renforcée
Implémente la Story 1.4 du système d'authentification avec plusieurs
couches de protection contre les attaques par force brute.

Sécurité backend :
- Authentification JWT avec access token (15min) + refresh token (7j)
- Rotation automatique des refresh tokens avec détection de replay
- Rate limiting progressif par IP (délai Fibonacci après échecs)
- Intégration Cloudflare Turnstile CAPTCHA après 5 tentatives
- Alerte email à l'utilisateur après blocage temporaire
- Isolation multi-tenant (un utilisateur ne peut se connecter que sur
  son établissement)

Frontend :
- Page de connexion avec feedback visuel des délais et erreurs
- Composant TurnstileCaptcha réutilisable
- Gestion d'état auth avec stockage sécurisé des tokens
- Tests E2E Playwright pour login, tenant isolation, et activation

Infrastructure :
- Configuration Symfony Security avec json_login + jwt
- Cache pools séparés (filesystem en test, Redis en prod)
- NullLoginRateLimiter pour environnement de test (évite blocage CI)
- Génération des clés JWT en CI après démarrage du backend
2026-02-01 14:43:12 +01:00

272 lines
8.1 KiB
YAML

name: CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
jobs:
# =============================================================================
# Backend Tests - PHP 8.5, PHPStan, PHPUnit
# =============================================================================
test-backend:
name: Backend Tests
runs-on: ubuntu-latest
defaults:
run:
working-directory: backend
services:
postgres:
image: postgres:18.1-alpine
env:
POSTGRES_DB: classeo_test
POSTGRES_USER: classeo
POSTGRES_PASSWORD: classeo
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
redis:
image: redis:7.4-alpine
ports:
- 6379:6379
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.5'
extensions: intl, pdo_pgsql, amqp, redis, zip
coverage: xdebug
- name: Get Composer cache directory
id: composer-cache
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache Composer dependencies
uses: actions/cache@v4
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: ${{ runner.os }}-composer-
- name: Install dependencies
run: composer install --prefer-dist --no-progress
- name: Run PHP CS Fixer (check)
run: composer cs-check
- name: Run PHPStan
run: composer phpstan
- name: Run PHPUnit
run: composer test
env:
DATABASE_URL: postgresql://classeo:classeo@localhost:5432/classeo_test?serverVersion=18
REDIS_URL: redis://localhost:6379
- name: Run BC Isolation Check
working-directory: .
run: ./scripts/check-bc-isolation.sh
# =============================================================================
# Frontend Tests - Vitest, Playwright
# =============================================================================
test-frontend:
name: Frontend Tests
runs-on: ubuntu-latest
defaults:
run:
working-directory: frontend
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
- name: Setup pnpm
uses: pnpm/action-setup@v3
with:
version: 9
- name: Get pnpm store directory
id: pnpm-cache
run: echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
- name: Cache pnpm dependencies
uses: actions/cache@v4
with:
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: ${{ runner.os }}-pnpm-store-
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Run linter
run: pnpm run lint
- name: Run TypeScript check
run: pnpm run check
- name: Run unit tests
run: pnpm run test
# =============================================================================
# E2E Tests - Playwright with Docker backend
# =============================================================================
test-e2e:
name: E2E Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
- name: Setup pnpm
uses: pnpm/action-setup@v3
with:
version: 9
- name: Get pnpm store directory
id: pnpm-cache
run: echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
- name: Cache pnpm dependencies
uses: actions/cache@v4
with:
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: ${{ runner.os }}-pnpm-store-
- name: Install frontend dependencies
working-directory: frontend
run: pnpm install --frozen-lockfile
- name: Install Playwright browsers
working-directory: frontend
run: pnpm exec playwright install --with-deps
- name: Build and start backend services
run: |
# Build images first (with Docker layer caching)
docker compose build php
# Start services (includes db, redis, rabbitmq dependencies)
# Use null mailer transport since mailpit is not available in CI
# Use test environment to disable rate limiting for E2E tests
APP_ENV=test MAILER_DSN="null://null" docker compose up -d php
timeout-minutes: 10
- name: Wait for backend to be ready
run: |
echo "Waiting for backend to be ready (composer install + app startup)..."
# Wait up to 5 minutes for the backend to respond
# Using /api/docs which is a public endpoint (no auth required)
timeout 300 bash -c 'until curl -sf http://localhost:18000/api/docs > /dev/null 2>&1; do
echo "Waiting for backend..."
sleep 5
done'
echo "Backend is ready!"
- name: Generate JWT keys for authentication
run: |
# Generate JWT keys if they don't exist (required for login/token endpoints)
docker compose exec -T php php bin/console lexik:jwt:generate-keypair --skip-if-exists
- name: Show backend logs on failure
if: failure()
run: docker compose logs php
- name: Configure hosts for multi-tenant testing
run: |
echo "127.0.0.1 classeo.local" | sudo tee -a /etc/hosts
echo "127.0.0.1 ecole-alpha.classeo.local" | sudo tee -a /etc/hosts
echo "127.0.0.1 ecole-beta.classeo.local" | sudo tee -a /etc/hosts
cat /etc/hosts
- name: Reset rate limiter before E2E tests
run: docker compose exec -T php php bin/console app:dev:reset-rate-limit
- name: Run E2E tests
working-directory: frontend
run: pnpm run test:e2e
env:
# Frontend serves on 4173 (preview mode), backend on 18000 (Docker)
PUBLIC_API_PORT: "18000"
PUBLIC_API_URL: http://localhost:18000/api
- name: Upload Playwright report
uses: actions/upload-artifact@v4
if: failure()
with:
name: playwright-report
path: frontend/playwright-report/
retention-days: 7
- name: Stop backend services
if: always()
run: docker compose down
# =============================================================================
# Naming Conventions Check
# =============================================================================
check-naming:
name: Naming Conventions
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Naming Check
run: ./scripts/check-naming.sh
# =============================================================================
# Build Check
# =============================================================================
build:
name: Build Check
runs-on: ubuntu-latest
needs: [test-backend, test-frontend, test-e2e]
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build backend image
uses: docker/build-push-action@v6
with:
context: ./backend
push: false
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Build frontend image
uses: docker/build-push-action@v6
with:
context: ./frontend
push: false
cache-from: type=gha
cache-to: type=gha,mode=max