discovery-media-player 0.1.33 → 0.1.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -318,6 +318,19 @@ function createStandaloneContext(env = process.env) {
318
318
  *
319
319
  * Non configuré ⇒ l'en-tête est ignoré. Un hôte sans proxy est protégé sans rien faire.
320
320
  */
321
+ /**
322
+ * Comment LIRE votre utilisateur — nom, e-mail, avatar — à partir de ce que `verifyToken`
323
+ * a rendu. Le cœur s'en sert pour signer les messages et les lignes d'assistance : ce qui est
324
+ * prouvé remplace ce que l'appelant affirme.
325
+ *
326
+ * Défaut : les formes usuelles d'un utilisateur Supabase. Redéfinissez-le si le vôtre range
327
+ * son nom ailleurs — sans quoi le message d'un membre porterait le nom qu'il a tapé.
328
+ */
329
+ profileOf(u) {
330
+ const meta = (u && u.user_metadata) || {};
331
+ return { email: (u && u.email) || "", name: meta.name || meta.full_name || "", avatar: meta.avatarUrl || meta.avatar_url || "" };
332
+ },
333
+
321
334
  clientIp(req) {
322
335
  const sauts = Math.max(0, Math.min(10, Number(env.PLAYER_TRUSTED_PROXY_HOPS || 0) || 0));
323
336
  const socket = String((req && req.socket && req.socket.remoteAddress) || "");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "discovery-media-player",
3
- "version": "0.1.33",
3
+ "version": "0.1.34",
4
4
  "description": "Self-hosted document viewer: per-recipient tracked links, reading analytics, live presentation. The core knows nothing about the application hosting it — everything it borrows arrives through an injected context.",
5
5
  "keywords": [
6
6
  "pdf-viewer",
package/server/handler.js CHANGED
@@ -1344,11 +1344,38 @@ function cleSessionHote() {
1344
1344
  const CLE_SESSION_PLAYER = "dmp-live-auth";
1345
1345
  const CLE_INVITE = "dmp-present-me";
1346
1346
 
1347
- async function membreVerifie(req) {
1347
+ /**
1348
+ * Le profil PROUVÉ de l'appelant : ce que son jeton dit de lui, jamais ce qu'il affirme.
1349
+ *
1350
+ * ⚠️ `isPresenter` et `isMember` étaient vérifiés depuis 0.1.25/0.1.28, mais `name`, `email` et
1351
+ * `avatar` venaient toujours du corps de la requête — même quand un jeton valide accompagnait
1352
+ * l'appel. Un membre authentifié pouvait donc publier un message portant le nom et l'adresse d'un
1353
+ * collègue, AVEC le badge membre : l'attribution visuelle disait quelqu'un d'autre.
1354
+ *
1355
+ * ⚠️ Ça ne donnait aucun droit — modifier et supprimer s'autorisent par `author_hash`, pas par
1356
+ * l'e-mail (cf. `editMessage`). Le dommage est l'attribution, pas la prise de contrôle. C'est déjà
1357
+ * assez : dans une discussion, un message signé du nom d'un autre est le problème.
1358
+ *
1359
+ * L'hôte peut fournir `identity.profileOf` pour dire comment lire SON utilisateur. Sans ce
1360
+ * crochet, on lit les formes courantes — et l'e-mail, lui, est universel.
1361
+ *
1362
+ * Signalé par la seconde passe d'audit (P1-6).
1363
+ */
1364
+ async function profilDuJeton(req) {
1348
1365
  try {
1349
1366
  const u = await PLAYER.identity.verifyToken((req.headers && req.headers.authorization) || "");
1350
- return !!(u && u.email);
1351
- } catch { return false; }
1367
+ if (!u || !u.email) return null;
1368
+ if (typeof PLAYER.identity.profileOf === "function") {
1369
+ const p = PLAYER.identity.profileOf(u) || {};
1370
+ return { email: String(p.email || u.email), name: String(p.name || ""), avatar: String(p.avatar || "") };
1371
+ }
1372
+ const meta = (u && u.user_metadata) || {};
1373
+ return {
1374
+ email: String(u.email),
1375
+ name: String(u.name || meta.name || meta.full_name || ""),
1376
+ avatar: String(u.avatar || meta.avatarUrl || meta.avatar_url || ""),
1377
+ };
1378
+ } catch { return null; }
1352
1379
  }
1353
1380
 
1354
1381
  async function relayerFichier(res, r, disposition) {
@@ -2738,8 +2765,16 @@ async function handler(req, res) {
2738
2765
  const pres = await getPresentation(String(body.slug || ""));
2739
2766
  if (!pres) return jp(404, { ok: false });
2740
2767
  const estPresentateur = !!(body.control && require("crypto").createHash("sha256").update(String(body.control)).digest("hex") === pres.control_hash);
2741
- const estMembre = await membreVerifie(req);
2742
- const r = await recordAttendance(String(body.slug || ""), { key: body.key, name: body.name, email: body.email, avatar: body.avatar, isMember: estMembre, isPresenter: estPresentateur });
2768
+ // ⚠️ Une identité prouvée REMPLACE celle qu'on affirme — elle ne s'y ajoute pas. Sinon la
2769
+ // vérification ne servirait qu'à décorer une affirmation qu'on croit toujours.
2770
+ const profil = await profilDuJeton(req);
2771
+ const r = await recordAttendance(String(body.slug || ""), {
2772
+ key: body.key,
2773
+ name: (profil && profil.name) || body.name,
2774
+ email: profil ? profil.email : body.email,
2775
+ avatar: (profil && profil.avatar) || body.avatar,
2776
+ isMember: !!profil, isPresenter: estPresentateur,
2777
+ });
2743
2778
  return jp(r.ok ? 200 : (r.status || 400), r);
2744
2779
  } catch { return jp(500, { ok: false }); }
2745
2780
  }
@@ -2782,7 +2817,12 @@ async function handler(req, res) {
2782
2817
  // `isMember` restait l'affirmation du client, alors que `isPresenter` était vérifié juste
2783
2818
  // au-dessus. Deux poids sur la même ligne : le badge « présentateur » se méritait, celui
2784
2819
  // de collègue se réclamait.
2785
- const r = await addMessage(String(body.slug || ""), { name: body.name, email: body.email, avatar: body.avatar, isPresenter: validControl, isMember: await membreVerifie(req), body: body.body, replyTo: body.replyTo, replyName: body.replyName, replyText: body.replyText, authorToken: body.authorToken, attachment: body.attachment });
2820
+ const profil = await profilDuJeton(req);
2821
+ const r = await addMessage(String(body.slug || ""), {
2822
+ name: (profil && profil.name) || body.name,
2823
+ email: profil ? profil.email : body.email,
2824
+ avatar: (profil && profil.avatar) || body.avatar,
2825
+ isPresenter: validControl, isMember: !!profil, body: body.body, replyTo: body.replyTo, replyName: body.replyName, replyText: body.replyText, authorToken: body.authorToken, attachment: body.attachment });
2786
2826
  return jp(r.ok ? 200 : (r.status || 400), r);
2787
2827
  } catch { return jp(500, { ok: false }); }
2788
2828
  }