> ## Documentation Index
> Fetch the complete documentation index at: https://docs.wethehivers.com/llms.txt
> Use this file to discover all available pages before exploring further.

# URLs pré-signées (R2 / S3)

> Génération, durée de vie et révocation des URLs d'accès aux fichiers privés

# URLs pré-signées

Les fichiers sont stockés sur **Cloudflare R2** (compatible S3). Les fichiers publics (logos, photos profil) sont servis directement via CDN. Les fichiers privés (CV, KYC) nécessitent une **URL pré-signée** temporaire.

## Architecture

```mermaid theme={null}
flowchart TB
    C[Client] -->|GET /api/files/cv/17| API[Backend]
    API --> AUTH{Auth + owner?}
    AUTH -->|non| R403[403 Forbidden]
    AUTH -->|oui| DB[(files_metadata)]
    DB --> KEY[key: private/cv/17.pdf]
    KEY --> SIG[R2 presign S3v4]
    SIG --> URL[Signed URL\n expires=15min]
    URL --> API
    API -->|302 redirect\n ou 200 JSON| C
    C -->|GET signed URL| R2[Cloudflare R2]
    R2 -->|200 + fichier| C
```

## Flow complet

```mermaid theme={null}
sequenceDiagram
    autonumber
    participant C as Client
    participant B as Backend
    participant DB as PostgreSQL
    participant R2 as Cloudflare R2

    C->>B: POST /v1/api/candidats/me/cv\n multipart: file=@cv.pdf
    B->>B: Valider type, taille, scan AV
    B->>R2: PUT private/cv/17.pdf\n avec clés R2
    R2-->>B: 200 ETag
    B->>DB: INSERT files_metadata\n (id=17, key, size, scan_ok)
    B-->>C: 200 {cvId: 17, downloadUrl: ...}

    Note over C,B: Plus tard...
    C->>B: GET /v1/api/files/cv/17\n Authorization: Bearer
    B->>DB: Vérifier propriétaire ou recruteur + candidature
    B->>R2: Presign GET private/cv/17.pdf\n expires=900s
    R2-->>B: https://...X-Amz-Signature=...
    B-->>C: 200 {url, expiresAt}
    C->>R2: GET url signée
    R2-->>C: 200 + PDF
```

## Buckets

| Bucket              | Visibilité      | Contenu                                                 |
| ------------------- | --------------- | ------------------------------------------------------- |
| `wethehive-public`  | Public          | Logos entreprise, photos profil, bannières, images blog |
| `wethehive-private` | Privé (presign) | CV, documents KYC, justificatifs                        |

## Durées de validité

| Type                       | TTL URL signée |
| -------------------------- | -------------- |
| Download CV                | 15 min         |
| Download KYC admin         | 15 min         |
| Upload direct-to-R2 (rare) | 5 min          |

<Info>
  Les URLs pré-signées **ne sont jamais cachées côté client**. À chaque besoin d'accès, le client doit redemander une nouvelle URL.
</Info>

## Règles d'accès aux CV privés

```mermaid theme={null}
flowchart TD
    REQ[GET /files/cv/17] --> WHO{Qui demande?}
    WHO -->|Propriétaire| OK1[Signé: OK]
    WHO -->|ADMIN| OK2[Signé: OK]
    WHO -->|Recruteur] --> C{Candidature active\n du propriétaire\n vers une offre du recruteur?}
    C -->|oui| OK3[Signé: OK]
    C -->|non| R403[403 Forbidden]
    WHO -->|autre| R403
```

## Format de réponse

Deux modes :

### Mode JSON (recommandé)

```http theme={null}
GET /v1/api/files/cv/17 HTTP/1.1
Accept: application/json
```

```json theme={null}
{
  "url": "https://r2.wethehivers.com/wethehive-private/cv/17.pdf?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Signature=...",
  "expiresAt": "2026-04-18T14:15:00Z",
  "fileName": "cv-jean-ngassa.pdf",
  "fileSize": 324512,
  "mimeType": "application/pdf"
}
```

### Mode redirect (compat navigateur)

```http theme={null}
GET /v1/api/files/cv/17 HTTP/1.1
Accept: */*
```

```http theme={null}
HTTP/1.1 302 Found
Location: https://r2.wethehivers.com/wethehive-private/cv/17.pdf?X-Amz-Signature=...
```

## Upload direct depuis le navigateur

Pour éviter de faire transiter les octets par le backend, un flow d'upload direct-to-R2 est disponible :

```mermaid theme={null}
sequenceDiagram
    participant C as Browser
    participant B as Backend
    participant R2 as R2

    C->>B: POST /v1/api/files/cv/presigned-upload\n {fileName, contentType, size}
    B->>B: Valider quota, format
    B->>R2: Presign PUT
    R2-->>B: URL + fields
    B-->>C: {uploadUrl, fields, cvId}
    C->>R2: PUT uploadUrl (fichier brut)
    R2-->>C: 200 ETag
    C->>B: POST /v1/api/files/cv/confirm/17
    B->>R2: HEAD (vérifier présence)
    B->>B: Scan AV + extraction métadonnées
    B-->>C: 200 {cvId}
```

## Révocation

Les URLs pré-signées R2 ne peuvent être révoquées **avant expiration** que par deux moyens :

1. Rotation de la paire `R2_ACCESS_KEY` / `R2_SECRET_KEY` (invalide TOUTES les signatures actives)
2. Suppression du fichier sous-jacent

```mermaid theme={null}
flowchart LR
    EVT[Événement critique\n fuite, compromission] --> ROT[Rotation R2 keys]
    ROT --> INVAL[Toutes URLs signées\n deviennent 403]
    INVAL --> NEW[Génération nouvelles\n URLs à la demande]
```

## Scan anti-virus

Tout fichier uploadé passe par **ClamAV** avant d'être exposé :

```mermaid theme={null}
sequenceDiagram
    participant B as Backend
    participant AV as ClamAV
    participant R2 as R2
    participant DB as DB

    B->>R2: PUT file (status=PENDING_SCAN)
    B->>AV: Scan stream
    AV-->>B: OK / INFECTED
    alt OK
        B->>DB: UPDATE status=ACTIVE
    else INFECTED
        B->>R2: DELETE
        B->>DB: UPDATE status=QUARANTINED
        B->>B: Log + alerte
    end
```

## Quotas de stockage

| Rôle      | Quota                                    | Action si dépassé       |
| --------- | ---------------------------------------- | ----------------------- |
| CANDIDAT  | 50 Mo total (CV + photo)                 | `413 Payload Too Large` |
| RECRUTEUR | 100 Mo (logo + staff photos + bannières) | `413`                   |
| ADMIN     | Illimité                                 | –                       |

## CORS R2

```
AllowedOrigins: https://wethehivers.com, https://recruteur.wethehivers.com, https://admin.wethehivers.com
AllowedMethods: GET, PUT
AllowedHeaders: Content-Type, Content-Length, Authorization
MaxAgeSeconds: 3600
```
