# 📚 Documentation Complète API mcV3

## Table des matières

1. [Architecture générale](#architecture-générale)
2. [Authentification](#authentification)
3. [Endpoints API](#endpoints-api)
4. [Gestion des clés API](#gestion-des-clés-api)
5. [Rôles et permissions](#rôles-et-permissions)
6. [Codes HTTP et erreurs](#codes-http-et-erreurs)
7. [Intégrations](#intégrations)
8. [Configuration avancée](#configuration-avancée)
9. [FAQ et dépannage](#faq-et-dépannage)

---

## Architecture générale

### Vue d'ensemble

Votre API utilise une **authentification hybride** combinant deux modes:

1. **Token API (Bearer Token)** - Pour applications externes/services
2. **Session utilisateur** - Pour utilisateurs connectés au site web

```
┌─────────────────────────────────────────────┐
│            Requête /api/*                    │
└────────────────┬────────────────────────────┘
                 │
         ┌───────┴──────────┐
         │                  │
    ┌────▼─────┐    ┌───────▼──────┐
    │ Bearer    │    │   Session    │
    │ Token?    │    │   Active?    │
    └────┬─────┘    └───────┬──────┘
         │                  │
    ┌────▼──────────────────▼────┐
    │  ApiTokenHandler OR        │
    │  Session Storage           │
    └────┬──────────────────────┘
         │
    ┌────▼──────────────────────┐
    │  Load User + Roles         │
    └────┬──────────────────────┘
         │
    ┌────▼──────────────────────┐
    │  Verify @IsGranted()       │
    └────┬──────────────────────┘
         │
    ✅ 200 OK ou ❌ 401/403
```

### Flux de sécurité

1. **Firewall `/api`** - Accepte les requêtes `/api/*`
2. **ApiBearerTokenAuthenticator** - Vérifie les Bearer tokens
3. **http_basic** - Charge la session du context partagé
4. **Contrôles d'accès** - Vérifie `@IsGranted()` sur les routes
5. **ApiExceptionListener** - Convertit les erreurs en JSON

---

## Authentification

### Mode 1: Bearer Token (Clé API)

#### Génération du token

**Via CLI:**
```bash
php bin/console app:api-key:create pseudo_utilisateur "Nom de l'application"
```

Le token est généré automatiquement (64 caractères hexadécimaux, cryptographiquement sécurisé).

**Via API (utilisateur connecté):**
```http
POST /api/api-keys
Content-Type: application/json
Authorization: Bearer existing_token

{
  "name": "Application mobile",
  "expiresAt": "2027-04-03T23:59:59Z"  // optionnel
}
```

#### Utilisation du token

Le token doit être envoyé dans l'en-tête `Authorization`:

```http
GET /api/profile
Authorization: Bearer abc123def456...
```

#### Caractéristiques

- ✅ Unique par clé API
- ✅ Peut avoir une date d'expiration
- ✅ Peut être désactivé/révoqué
- ✅ Stateless (pas de session serveur)
- ✅ Audit (tracking du dernier accès)

### Mode 2: Session utilisateur

#### Établir une session

```http
POST /login
Content-Type: application/x-www-form-urlencoded

_username=john&_password=secret
```

Un cookie `PHPSESSID` est créé et stocké.

#### Utiliser la session

Tous les cookies sont envoyés automatiquement par le navigateur. Via cURL:

```bash
curl -b cookies.txt -X POST http://localhost:8000/login \
  -d "_username=john&_password=secret"

curl -b cookies.txt http://localhost:8000/api/profile
```

#### Caractéristiques

- ✅ Basée sur les cookies
- ✅ Automatique dans le navigateur
- ✅ Partage avec le firewall `main`
- ✅ Timeout configurable
- ✅ Plus sécurisée pour les applications web

### Détection du mode d'authentification

La réponse `/api/profile` indique le mode utilisé:

```json
{
  "id": 1,
  "pseudo": "john",
  "roles": ["ROLE_USER"],
  "authenticatedVia": "api_key"  // ou "session"
}
```

---

## Endpoints API

### GET /api/health

**Authentification**: ❌ Non requise

Vérifier l'état de l'API.

**Exemple:**
```bash
curl http://localhost:8000/api/health
```

**Réponse (200 OK):**
```json
{
  "status": "ok",
  "timestamp": "2026-04-03T10:30:00+00:00"
}
```

---

### GET /api/profile

**Authentification**: ✅ Requise (Bearer token ou session)

Récupérer le profil de l'utilisateur authentifié.

**Exemple avec Bearer token:**
```bash
curl -X GET http://localhost:8000/api/profile \
  -H "Authorization: Bearer your_token"
```

**Réponse (200 OK):**
```json
{
  "id": 1,
  "pseudo": "john",
  "roles": ["ROLE_USER", "ROLE_ADMIN"],
  "authenticatedVia": "api_key"
}
```

**Erreurs possibles:**
- `401 Unauthorized` - Token invalide/expiré ou pas de session

---

### GET /api/api-keys

**Authentification**: ✅ Requise (Bearer token ou session)

Lister les clés API.

- **ROLE_USER**: Voit uniquement SES propres clés
- **ROLE_ADMIN**: Voit TOUTES les clés du système

**Exemple:**
```bash
curl -X GET http://localhost:8000/api/api-keys \
  -H "Authorization: Bearer your_token"
```

**Réponse (200 OK):**
```json
{
  "apiKeys": [
    {
      "id": 1,
      "name": "Mobile App",
      "token": "a1b2c3d4e5...",  // masqué pour sécurité
      "createdAt": "2026-04-01T10:00:00+00:00",
      "lastUsedAt": "2026-04-03T15:30:00+00:00",
      "expiresAt": "2026-12-31T23:59:59+00:00",
      "isActive": true
    }
  ]
}
```

---

### POST /api/api-keys

**Authentification**: ✅ Requise (Bearer token ou session)

Créer une nouvelle clé API.

**Body (JSON):**
```json
{
  "name": "Nouvelle application",
  "expiresAt": "2027-04-03T23:59:59Z"  // optionnel
}
```

**Exemple:**
```bash
curl -X POST http://localhost:8000/api/api-keys \
  -H "Authorization: Bearer your_token" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "API Desktop",
    "expiresAt": "2026-12-31T23:59:59Z"
  }'
```

**Réponse (201 Created):**
```json
{
  "id": 5,
  "name": "API Desktop",
  "token": "abc123def456...xyz",  // Complet à la création uniquement!
  "createdAt": "2026-04-03T16:45:00+00:00",
  "message": "Copier le token en lieu sûr, il ne sera plus visible ultérieurement"
}
```

**⚠️ IMPORTANT**: Le token complet n'est affiché qu'à la création. Stockez-le immédiatement.

**Erreurs possibles:**
- `400 Bad Request` - Paramètre "name" manquant
- `401 Unauthorized` - Authentification invalide

---

### DELETE /api/api-keys/{id}

**Authentification**: ✅ Requise (Bearer token ou session)

Supprimer une clé API.

- **ROLE_USER**: Peut supprimer uniquement ses propres clés
- **ROLE_ADMIN**: Peut supprimer n'importe quelle clé

**Exemple:**
```bash
curl -X DELETE http://localhost:8000/api/api-keys/1 \
  -H "Authorization: Bearer your_token"
```

**Réponse (200 OK):**
```json
{
  "message": "API key deleted"
}
```

**Erreurs possibles:**
- `403 Forbidden` - Essai de supprimer la clé d'un autre utilisateur
- `404 Not Found` - Clé inexistante

---

### PATCH /api/api-keys/{id}/deactivate

**Authentification**: ✅ Requise (Bearer token ou session)

Désactiver une clé API sans la supprimer.

**Exemple:**
```bash
curl -X PATCH http://localhost:8000/api/api-keys/1/deactivate \
  -H "Authorization: Bearer your_token"
```

**Réponse (200 OK):**
```json
{
  "message": "API key deactivated"
}
```

---

## Gestion des clés API

### Cycle de vie d'une clé

```
Création
   │
   ├─→ Actif et utilisable
   │
   ├─→ Peut être désactivé (sans suppression)
   │
   ├─→ Peut avoir une expiration automatique
   │
   └─→ Peut être supprimé définitivement
```

### Bonnes pratiques

1. **Créer des clés par application** - Une clé par service/app
2. **Définir une expiration** - Rotation annuelle recommandée
3. **Révoquer régulièrement** - Supprimer les vieilles clés
4. **Ne jamais partager** - Chaque clé est personnelle
5. **Monitorer l'accès** - Vérifier `lastUsedAt`

### Exemple: Rotation de clés

```bash
# 1. Créer une nouvelle clé
curl -X POST http://localhost:8000/api/api-keys \
  -H "Authorization: Bearer old_token" \
  -H "Content-Type: application/json" \
  -d '{"name": "Mobile App v2"}'

# 2. Mettre à jour l'application avec la nouvelle clé
# ... déployer avec new_token ...

# 3. Désactiver l'ancienne clé
curl -X PATCH http://localhost:8000/api/api-keys/1/deactivate \
  -H "Authorization: Bearer new_token"

# 4. (Optionnel) Supprimer après confirmation
curl -X DELETE http://localhost:8000/api/api-keys/1 \
  -H "Authorization: Bearer new_token"
```

---

## Rôles et permissions

### Hiérarchie de rôles

```
ROLE_ADMIN
  └── ROLE_USER

ROLE_MODERATOR
  └── ROLE_USER

ROLE_USER (base)
```

### Permissions par rôle

| Action | ROLE_USER | ROLE_ADMIN |
|--------|-----------|-----------|
| Voir son profil | ✅ | ✅ |
| Voir ses clés | ✅ | ✅ |
| Voir les clés d'autres | ❌ | ✅ |
| Créer ses clés | ✅ | ✅ |
| Supprimer ses clés | ✅ | ✅ |
| Supprimer les clés d'autres | ❌ | ✅ |
| Accéder à /api/* | ✅ | ✅ |
| Accéder à /administration | ❌ | ✅ |

### Implémentation dans le code

```php
// Protéger une route par rôle
#[Route('/api/mon-endpoint')]
#[IsGranted('ROLE_USER')]
public function monEndpoint(): JsonResponse { }

// Logique conditionnelle selon le rôle
if ($this->isGranted('ROLE_ADMIN')) {
    // Admin seulement
    return $this->json($allData);
}
return $this->json($userData);
```

---

## Codes HTTP et erreurs

### Codes de succès

| Code | Signification | Utilisation |
|------|---------------|-------------|
| 200 | OK | Requête réussie (GET, POST, PATCH, DELETE) |
| 201 | Created | Ressource créée (POST) |
| 204 | No Content | Succès sans contenu (rare) |

### Codes d'erreur client (4xx)

#### 400 Bad Request
Requête invalide (paramètres manquants, JSON malformé).

**Exemple:**
```json
{
  "error": "Bad Request",
  "message": "name is required"
}
```

**Solutions:**
- Vérifier le format JSON
- Ajouter les paramètres requis

#### 401 Unauthorized
Non authentifié ou authentification invalide.

**Cas courants:**
- Token manquant: `curl http://localhost:8000/api/profile`
- Token invalide: `Authorization: Bearer wrong_token`
- Token expiré: Token dont la date d'expiration est passée
- Session expirée: Cookies invalides/écoulés

**Réponse:**
```json
{
  "error": "Unauthorized",
  "message": "Authentication required. Please provide a valid API token or be logged in."
}
```

**Solutions:**
- Ajouter le header `Authorization: Bearer YOUR_TOKEN`
- Renouveler le token expiré
- Se reconnecter (pour session)

#### 403 Forbidden
Authentifié mais permissions insuffisantes.

**Cas courants:**
- ROLE_USER essayant de supprimer la clé d'un autre
- Accès à un endpoint réservé aux admins

**Réponse:**
```json
{
  "error": "Forbidden",
  "message": "You do not have permission to access this resource"
}
```

**Solutions:**
- Utiliser un compte admin si nécessaire
- Vérifier les droits requis

#### 404 Not Found
Ressource inexistante.

**Cas courants:**
- Clé API inexistante: `DELETE /api/api-keys/99999`
- Endpoint invalide: `GET /api/nonexistent`

**Solutions:**
- Vérifier l'ID de la ressource
- Vérifier le chemin de la route

### Codes d'erreur serveur (5xx)

#### 500 Internal Server Error
Erreur serveur générique.

**Cause:** Bug ou exception non gérée.

**Solutions:**
- Vérifier les logs: `tail -f var/log/dev.log`
- Contacter le développeur

---

## Intégrations

### JavaScript / Fetch API

```javascript
// Configuration
const API_BASE_URL = 'http://localhost:8000';
const API_TOKEN = 'your_api_token';

// Helper pour les appels API
async function apiCall(endpoint, options = {}) {
  const url = `${API_BASE_URL}${endpoint}`;
  const headers = {
    'Authorization': `Bearer ${API_TOKEN}`,
    'Content-Type': 'application/json',
    ...options.headers,
  };

  const response = await fetch(url, {
    ...options,
    headers,
  });

  if (!response.ok) {
    const error = await response.json();
    throw new Error(`${response.status}: ${error.message}`);
  }

  return response.json();
}

// Utilisation
async function getProfile() {
  try {
    const profile = await apiCall('/api/profile');
    console.log('Profil:', profile);
  } catch (error) {
    console.error('Erreur:', error.message);
  }
}

async function createApiKey(name) {
  try {
    const key = await apiCall('/api/api-keys', {
      method: 'POST',
      body: JSON.stringify({ name }),
    });
    console.log('Clé créée:', key.token);
  } catch (error) {
    console.error('Erreur:', error.message);
  }
}

// Appels
getProfile();
createApiKey('Mon App');
```

### Python / Requests

```python
import requests
import json

class ApiClient:
    def __init__(self, base_url, api_token):
        self.base_url = base_url
        self.api_token = api_token
        self.session = requests.Session()
        self.session.headers.update({
            'Authorization': f'Bearer {api_token}',
            'Content-Type': 'application/json',
        })

    def request(self, method, endpoint, data=None):
        url = f"{self.base_url}{endpoint}"
        try:
            if method == 'GET':
                response = self.session.get(url)
            elif method == 'POST':
                response = self.session.post(url, json=data)
            elif method == 'DELETE':
                response = self.session.delete(url)
            elif method == 'PATCH':
                response = self.session.patch(url, json=data)
            
            response.raise_for_status()
            return response.json()
        except requests.exceptions.RequestException as e:
            print(f"Erreur: {e}")
            return None

    def get_profile(self):
        return self.request('GET', '/api/profile')

    def list_keys(self):
        return self.request('GET', '/api/api-keys')

    def create_key(self, name, expires_at=None):
        data = {'name': name}
        if expires_at:
            data['expiresAt'] = expires_at
        return self.request('POST', '/api/api-keys', data)

# Utilisation
client = ApiClient('http://localhost:8000', 'your_api_token')

profile = client.get_profile()
print(f"Profil: {profile}")

keys = client.list_keys()
print(f"Clés: {keys}")

new_key = client.create_key('Python App')
print(f"Nouvelle clé: {new_key['token']}")
```

### PHP / cURL

```php
class ApiClient {
    private $baseUrl;
    private $apiToken;

    public function __construct($baseUrl, $apiToken) {
        $this->baseUrl = rtrim($baseUrl, '/');
        $this->apiToken = $apiToken;
    }

    private function request($method, $endpoint, $data = null) {
        $url = $this->baseUrl . $endpoint;
        
        $options = [
            'http' => [
                'method' => $method,
                'header' => [
                    "Authorization: Bearer {$this->apiToken}",
                    'Content-Type: application/json',
                ],
                'timeout' => 10,
            ],
        ];

        if ($data && in_array($method, ['POST', 'PATCH'])) {
            $options['http']['content'] = json_encode($data);
        }

        $context = stream_context_create($options);
        $response = @file_get_contents($url, false, $context);

        if ($response === false) {
            return ['error' => 'Erreur de connexion'];
        }

        return json_decode($response, true);
    }

    public function getProfile() {
        return $this->request('GET', '/api/profile');
    }

    public function listKeys() {
        return $this->request('GET', '/api/api-keys');
    }

    public function createKey($name, $expiresAt = null) {
        $data = ['name' => $name];
        if ($expiresAt) {
            $data['expiresAt'] = $expiresAt;
        }
        return $this->request('POST', '/api/api-keys', $data);
    }
}

// Utilisation
$client = new ApiClient('http://localhost:8000', 'your_api_token');

$profile = $client->getProfile();
var_dump($profile);

$keys = $client->listKeys();
var_dump($keys);

$newKey = $client->createKey('PHP App');
echo "Nouvelle clé: " . $newKey['token'];
```

---

## Configuration avancée

### Ajouter des endpoints personnalisés

```php
// Dans src/Controller/ApiController.php

#[Route('/api/custom-endpoint')]
#[IsGranted('ROLE_USER')]
public function customEndpoint(): JsonResponse
{
    $user = $this->getUser();
    
    return $this->json([
        'message' => 'Données personnalisées',
        'user' => $user->getPseudo(),
    ]);
}
```

### Filtrer par rôle dans un endpoint

```php
#[Route('/api/admin-data')]
#[IsGranted('ROLE_USER')]
public function adminData(): JsonResponse
{
    if (!$this->isGranted('ROLE_ADMIN')) {
        return $this->json(
            ['error' => 'Accès admin requis'],
            JsonResponse::HTTP_FORBIDDEN
        );
    }

    return $this->json([
        'adminData' => 'Secret data',
    ]);
}
```

### Valider les requêtes

```php
#[Route('/api/create-item', methods: ['POST'])]
#[IsGranted('ROLE_USER')]
public function createItem(Request $request): JsonResponse
{
    $data = json_decode($request->getContent(), true);

    // Valider les paramètres requis
    if (empty($data['name'])) {
        return $this->json(
            ['error' => 'name is required'],
            JsonResponse::HTTP_BAD_REQUEST
        );
    }

    // Traiter les données...
    
    return $this->json(['id' => 1, 'name' => $data['name']]);
}
```

---

## FAQ et dépannage

### Q: Erreur "Invalid or expired API token"
**R:** Le token est invalide ou expiré.
- Vérifier que le token est correct
- Créer une nouvelle clé: `php bin/console app:api-key:create`
- Vérifier l'expiration

### Q: Comment mettre à jour ma clé?
**R:** Les clés ne peuvent pas être mises à jour. Solutions:
- Créer une nouvelle clé avec une date d'expiration plus lointaine
- Révoquer l'ancienne clé

### Q: Puis-je utiliser la même clé pour plusieurs applications?
**R:** Techniquement oui, mais **déconseillé** pour des raisons de sécurité:
- Une compromission affecte toutes les applications
- Impossible de révoquer une seule application
- Créer une clé par application

### Q: Comment changer l'expiration d'une clé?
**R:** Ce n'est pas possible directement. Options:
1. Créer une nouvelle clé avec expiration souhaitée
2. Révoquer l'ancienne
3. Mettre à jour l'application

### Q: L'API fonctionne-t-elle avec HTTPS?
**R:** Oui, recommandé en production. Assurez-vous que:
- Les certificats sont valides
- Les URLs sont en HTTPS
- Les cookies ont le flag `Secure`

### Q: Puis-je accéder à l'API depuis un autre domaine (CORS)?
**R:** CORS n'est pas configuré par défaut. Pour l'activer:
1. Installer bundle CORS: `composer require nelmio/cors-bundle`
2. Configurer les domaines autorisés

### Q: Comment monitorer l'utilisation de l'API?
**R:** Vérifier:
- `lastUsedAt` dans GET `/api/api-keys`
- Les logs: `var/log/dev.log`
- Implémenter un logging personnalisé

### Q: Erreur "403 Forbidden"
**R:** Vous n'avez pas les permissions. Vérifier:
- Votre rôle (GET `/api/profile`)
- Les droits requis (@IsGranted)
- Si vous essayez de modifier les données d'un autre

### Q: Comment réinitialiser toutes mes clés?
**R:** 
1. Lister les clés: `GET /api/api-keys`
2. Supprimer une par une: `DELETE /api/api-keys/{id}`
3. Créer de nouvelles clés

---

**Documentation à jour: 2026-04-03**

