From 0962c2cc1f5c86dbdb9f4613dd74decd2afce5be Mon Sep 17 00:00:00 2001 From: Nicolas Date: Sun, 13 Sep 2026 20:49:21 +0200 Subject: [PATCH] =?UTF-8?q?Page=20de=20connexion=20distincte=20avant=20la?= =?UTF-8?q?=20page=20de=20d=C3=A9p=C3=B4t?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `GET /` décide côté serveur, à chaque requête, laquelle des deux pages servir : `public/login.html` (aucune session valide — juste le bouton « Se connecter », aucune zone de dépôt dans le HTML) ou `public/upload.html` (session valide — nom d'utilisateur injecté et échappé, bouton « Se déconnecter »). Nouveau `src/routes/pages.ts`. Simplifications côté page de dépôt, permises par cette garantie serveur : plus de fetch `/auth/me` ni de bascule `authenticated` en JS, plus de retrait/réinsertion de la zone de dépôt dans le DOM — elle est toujours présente puisque la page n'est servie qu'aux sessions valides. Une session qui expire en cours d'usage (401 sur /targets ou /upload*) renvoie vers `/?login=expired`, qui affiche alors la vraie page de connexion avec le message adapté. Co-Authored-By: Claude Sonnet 5 --- README.md | 55 +++++++------ public/login.html | 119 +++++++++++++++++++++++++++++ public/{index.html => upload.html} | 75 +++++------------- src/app.ts | 18 +---- src/routes/pages.ts | 47 ++++++++++++ tests/pages.test.ts | 97 +++++++++++++++++++++++ 6 files changed, 320 insertions(+), 91 deletions(-) create mode 100644 public/login.html rename public/{index.html => upload.html} (91%) create mode 100644 src/routes/pages.ts create mode 100644 tests/pages.test.ts diff --git a/README.md b/README.md index db19445..f4fa37c 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,12 @@ # mediaDrop Petit serveur pour **déposer des fichiers médias** (images / vidéos / audio) sur une -machine : une page web de glisser-déposer et un endpoint `POST /upload`. -Authentification **OpenID Connect gérée côté serveur** : le serveur effectue le flux -Authorization Code + PKCE, puis maintient une **session** (cookie chiffré). Les -fichiers sont écrits dans un dossier local. Pas de base de données, pas de listing. +machine : une page de connexion, une page de glisser-déposer, et un endpoint +`POST /upload`. Authentification **OpenID Connect gérée côté serveur** : le serveur +effectue le flux Authorization Code + PKCE, puis maintient une **session** (cookie +chiffré). `GET /` sert l'une ou l'autre page selon la session — jamais les deux à la +fois. Les fichiers sont écrits dans un dossier local. Pas de base de données, pas de +listing. ## Prérequis @@ -49,8 +51,9 @@ pointeur. npm run dev ``` -Ouvrir , cliquer **Se connecter** (redirection vers le -fournisseur), puis glisser des fichiers. +Ouvrir : page de connexion, cliquer **Se connecter** +(redirection vers le fournisseur) ; de retour sur `/`, la page de dépôt s'affiche — +il ne reste plus qu'à glisser des fichiers. En production : @@ -108,21 +111,27 @@ pouvant écrire les paquets de l'organisation. ## Endpoints -| Route | Auth | Rôle | -| -------------------------------------- | ------- | ------------------------------------------------------------------------ | -| `GET /` | non | Page de dépôt | -| `GET /health` | non | Sonde : `{ "status": "ok", "uptime": 12.3 }` | -| `GET /auth/login` | non | Démarre le flux OIDC (redirige vers le fournisseur) | -| `GET /auth/callback` | non | Fin du flux : ouvre la session, redirige vers `/` | -| `POST /auth/logout` | session | Ferme la session (+ déconnexion RP si disponible) | -| `GET /auth/me` | non | `{ "authenticated": bool, "name"?: string }` | -| `GET /config` | non | Réglages client : `{ "chunkPoolSize": number }` | -| `GET /targets` | session | Cibles : `[{ "name", "label", "allowDirectories", "allowedMimeTypes" }]` | -| `POST /upload?target=[&path=

]` | session | Dépôt direct d'un fichier `multipart/form-data`, champ `file` | -| `POST /upload/init` | session | Démarre un upload fragmenté → `{ id, chunkSize, chunkCount }` | -| `PUT /upload/:id/:index` | session | Envoie un morceau (corps brut `application/octet-stream`) | -| `POST /upload/:id/complete` | session | Assemble et finalise → `201` + métadonnées | -| `DELETE /upload/:id` | session | Abandonne (supprime le fichier partiel) | +| Route | Auth | Rôle | +| -------------------------------------- | ------- | ------------------------------------------------------------------------------- | +| `GET /` | non | Page de connexion (`login.html`), ou de dépôt (`upload.html`) si session valide | +| `GET /health` | non | Sonde : `{ "status": "ok", "uptime": 12.3 }` | +| `GET /auth/login` | non | Démarre le flux OIDC (redirige vers le fournisseur) | +| `GET /auth/callback` | non | Fin du flux : ouvre la session, redirige vers `/` | +| `POST /auth/logout` | session | Ferme la session (+ déconnexion RP si disponible) | +| `GET /auth/me` | non | `{ "authenticated": bool, "name"?: string }` | +| `GET /config` | non | Réglages client : `{ "chunkPoolSize": number }` | +| `GET /targets` | session | Cibles : `[{ "name", "label", "allowDirectories", "allowedMimeTypes" }]` | +| `POST /upload?target=[&path=

]` | session | Dépôt direct d'un fichier `multipart/form-data`, champ `file` | +| `POST /upload/init` | session | Démarre un upload fragmenté → `{ id, chunkSize, chunkCount }` | +| `PUT /upload/:id/:index` | session | Envoie un morceau (corps brut `application/octet-stream`) | +| `POST /upload/:id/complete` | session | Assemble et finalise → `201` + métadonnées | +| `DELETE /upload/:id` | session | Abandonne (supprime le fichier partiel) | + +`GET /` décide **côté serveur**, à chaque requête : sans session valide, `login.html` +(rien d'autre que le bouton **Se connecter**, aucune zone de dépôt dans le HTML) ; +avec une session valide, `upload.html` avec le nom d'utilisateur injecté (échappé) et +`Se déconnecter`. Une session qui expire pendant l'utilisation de la page (401 sur +`/targets` ou `/upload*`) renvoie le navigateur sur `/?login=expired`. `POST /upload` — `?target=` choisit la cible (absent → **première** cible). `?path=` (URL-encodé) dépose dans un **sous-dossier** recréé sous @@ -317,6 +326,7 @@ src/ errors.ts classes d'erreur (AppError + dérivées) error-handler.ts gestionnaire d'erreurs global routes/ + pages.ts GET / (login.html ou upload.html selon la session, nom échappé) auth.ts GET /auth/login, /auth/callback, /auth/me ; POST /auth/logout health.ts GET /health upload.ts GET /targets ; POST /upload (dépôt direct) @@ -329,5 +339,6 @@ src/ media-types.ts liste blanche des types + extensions + jokers paths.ts assainissement des chemins relatifs (dépôt de dossier) public/ - index.html page de dépôt (login = lien vers /auth/login, sans build) + login.html page de connexion (bouton -> /auth/login, sans build) + upload.html page de dépôt, servie uniquement avec une session valide ``` diff --git a/public/login.html b/public/login.html new file mode 100644 index 0000000..2addcda --- /dev/null +++ b/public/login.html @@ -0,0 +1,119 @@ + + + + + + mediaDrop — Connexion + + + +

+
+

mediaDrop

+

Connectez-vous pour déposer des fichiers médias.

+
+ +
+ + Se connecter +
+
+ + + + diff --git a/public/index.html b/public/upload.html similarity index 91% rename from public/index.html rename to public/upload.html index 252c114..dc7769f 100644 --- a/public/index.html +++ b/public/upload.html @@ -217,14 +217,13 @@

mediaDrop

-

Connectez-vous, puis glissez des fichiers médias pour les déposer.

+

Glissez des fichiers médias pour les déposer.

- Chargement… - -
@@ -259,13 +258,9 @@
' }); + const res = await app.inject({ + method: 'GET', + url: '/', + cookies: { [SESSION_COOKIE]: cookie }, + }); + expect(res.body).not.toContain(''); + expect(res.body).toContain('<script>alert(1)</script>'); + }); + + it('session invalide (cookie illisible) : retombe sur la page de connexion', async () => { + const res = await app.inject({ + method: 'GET', + url: '/', + cookies: { [SESSION_COOKIE]: 'cookie-bidon' }, + }); + expect(res.statusCode).toBe(200); + expect(res.body).toContain('href="/auth/login"'); + expect(res.body).not.toContain('id="dropzone"'); + }); +});