- axum + sqlx (requêtes runtime, pas les macros query! à vérification compile-time - évite d'exiger une base joignable à chaque build). - Auth par session opaque côté serveur (cookie HttpOnly/Secure/SameSite, pas de JWT - révocation = suppression de ligne). Mots de passe Argon2. - Fichiers : upload multipart streamé chunk par chunk vers le disque (jamais accumulé en mémoire - condition réelle d'une progression d'upload non simulée), download streamé, blobs nommés par UUID (le nom d'origine ne vit qu'en métadonnée). - Stockage disque pour cette v1 (pas Object Storage - pas de bénéfice réel pour un unique VPS avec 55 Go libres, ajouté sans complexité inutile). - PostgreSQL dans un conteneur Docker dédié, séparé de celui d'iot-alarm-system (pas de couplage entre projets sans rapport). - Déployé en prod sous systemd (même utilisateur système que tripoli-edge), branché sur son upstream par défaut. Vérifié bout-en-bout via https://tripoli.cloud/api/... : inscription, connexion, upload, download, suppression, et les cas d'erreur (401 sans session, 409 email déjà pris, 401 mauvais mot de passe, 404 double suppression). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
83 lines
3.9 KiB
Markdown
83 lines
3.9 KiB
Markdown
# tripoli-api
|
|
|
|
Auth, fichiers, métadonnées PostgreSQL. Axum + sqlx (requêtes vérifiées
|
|
à l'exécution, pas les macros `query!` à vérification compile-time :
|
|
elles auraient exigé une base joignable à chaque `cargo build`, y
|
|
compris depuis un poste qui n'a pas le tunnel WireGuard monté - plus de
|
|
complexité que ce que ça apporte pour un projet de cette taille).
|
|
|
|
## Auth
|
|
|
|
Sessions opaques côté serveur (table `sessions`, cookie `tripoli_session`
|
|
`HttpOnly` + `Secure` + `SameSite=Lax`), pas de JWT : révoquer une session
|
|
est une suppression de ligne, pas une histoire de clé de signature à
|
|
faire tourner. Mots de passe hachés avec Argon2 (`argon2` + `password-hash`,
|
|
implémentations RustCrypto).
|
|
|
|
## Endpoints
|
|
|
|
| Route | Auth | Rôle |
|
|
|---|---|---|
|
|
| `GET /healthz` | non | ping PostgreSQL |
|
|
| `POST /api/auth/register` | non | `{email, password}` -> crée le compte + session |
|
|
| `POST /api/auth/login` | non | `{email, password}` -> session |
|
|
| `POST /api/auth/logout` | oui | invalide la session |
|
|
| `GET /api/auth/me` | oui | utilisateur courant |
|
|
| `GET /api/files` | oui | fichiers de l'utilisateur courant |
|
|
| `POST /api/files` | oui | upload multipart, champ `file` |
|
|
| `GET /api/files/{id}` | oui | téléchargement (vérifie la propriété) |
|
|
| `DELETE /api/files/{id}` | oui | suppression (vérifie la propriété) |
|
|
|
|
## Stockage
|
|
|
|
Chaque fichier est un blob sur disque nommé par son UUID
|
|
(`TRIPOLI_BLOB_DIR`, `/var/lib/tripoli-api/blobs` par défaut) - jamais
|
|
par le nom d'origine, qui ne vit qu'en métadonnée PostgreSQL. Ça évite
|
|
toute question de traversée de chemin ou de caractères spéciaux dans un
|
|
nom de fichier envoyé par un client.
|
|
|
|
Stockage disque, pas Hetzner Object Storage pour cette v1 : le brief
|
|
laissait le choix, et Object Storage aurait ajouté un client S3 et des
|
|
identifiants supplémentaires sans bénéfice pour un unique VPS avec 55 Go
|
|
disponibles. À revisiter si l'espace disque devient une contrainte
|
|
réelle.
|
|
|
|
L'upload multipart est écrit sur disque chunk par chunk
|
|
(`field.chunk().await` -> `AsyncWriteExt::write_all`), jamais accumulé
|
|
en mémoire : c'est ce qui rend la progression d'upload réelle côté
|
|
client - le corps de la requête part effectivement au fil de l'eau.
|
|
|
|
## Configuration
|
|
|
|
| Variable | Défaut | Rôle |
|
|
|---|---|---|
|
|
| `TRIPOLI_API_LISTEN` | `127.0.0.1:8081` | Adresse d'écoute (jamais publique - tripoli-edge est le seul appelant) |
|
|
| `DATABASE_URL` | — (requis) | `postgres://...` |
|
|
| `TRIPOLI_BLOB_DIR` | `/var/lib/tripoli-api/blobs` | Stockage des blobs |
|
|
| `TRIPOLI_SESSION_TTL_HOURS` | `720` (30 jours) | Durée de vie d'une session |
|
|
|
|
## Déploiement (fait manuellement pour cette phase, même remarque que tripoli-edge)
|
|
|
|
- PostgreSQL dans un conteneur Docker dédié (`/opt/tripoli-api/docker-compose.yml`),
|
|
**séparé de celui d'iot-alarm-system** : pas de couplage entre projets
|
|
qui n'ont rien à voir. Exposé uniquement sur `127.0.0.1:5433` - tripoli-api
|
|
tourne nativement sur l'hôte, pas besoin de l'exposer sur un réseau Docker.
|
|
Mot de passe généré côté serveur (`/opt/tripoli-api/.env`, jamais commité,
|
|
jamais affiché dans un terminal).
|
|
- tripoli-api tourne en systemd (`deploy/tripoli-api.service`) sous le même
|
|
utilisateur système sans privilège `tripoli` que tripoli-edge.
|
|
- Les migrations (`migrations/0001_init.sql`) s'appliquent automatiquement
|
|
au démarrage (`sqlx::migrate!`).
|
|
|
|
## Vérifié en production
|
|
|
|
```
|
|
curl https://tripoli.cloud/healthz -> {"status":"ok"}
|
|
curl -X POST https://tripoli.cloud/api/auth/register -> compte + session
|
|
curl https://tripoli.cloud/api/auth/me (avec cookie) -> utilisateur courant
|
|
curl -F file=@... https://tripoli.cloud/api/files -> upload
|
|
curl https://tripoli.cloud/api/files/{id} -> download
|
|
```
|
|
|
|
Testé aussi : accès sans cookie (401), email déjà pris (409), mauvais
|
|
mot de passe (401), double suppression (404 la seconde fois).
|