Partage en lecture seule par lien signé

- Lien /s/<jeton> : jeton = id aléatoire 128 bits + HMAC-SHA256 tronqué par la
  clé du serveur (SHARE_SECRET, sinon DATA_DIR/.share-secret générée en 0600).
  Valide si la signature est correcte, le lien présent dans .shares.json et non
  expiré (1, 7, 30 jours ou sans expiration).
- API : GET/POST/DELETE /api/shares, POST /api/shares/revoke-all (renouvelle la
  clé), GET /api/public/{jeton} hors authentification (nom, titre, logo, scène).
- Auth Basic : /s/, /api/public/, /healthz et les fichiers statiques du build
  sont publics ; le reste de l'API et l'index restent protégés. Le serveur
  statique ne liste plus les répertoires. X-Robots-Tag: noindex sur les liens.
- Les liens suivent renommages et déplacements, disparaissent à la suppression.
- Interface : dialogue Partager (barre du haut, menu de l'explorateur), section
  Liens de partage dans Paramètres, vue publique Excalidraw en lecture seule
  avec téléchargement et bascule clair/sombre.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H6J7vKwXx7uopxKhWfFJpm
This commit is contained in:
claude BotandClaude Opus 5.5 committed 2026-10-07 18:10:09 +02:00
1 parent c1ea1e84a7
commit 50d40cd929
18 files changed
+1076 -12

No files matched your search

+63
View File
@@ -1227,3 +1227,66 @@ button.chip.on,
display: flex;
gap: 6px;
}
/* --- Partage en lecture seule ---------------------------------------------- */
.share-view {
display: grid;
grid-template-rows: var(--topbar-h) 1fr;
grid-template-columns: 1fr;
height: 100dvh;
}
.share-view .canvas {
grid-area: 2 / 1;
}
.share-view .brand {
cursor: default;
}
.status.readonly {
display: inline-flex;
align-items: center;
gap: 5px;
color: var(--ex-primary);
}
.share-dialog {
width: min(520px, 100%);
}
.share-create {
display: flex;
flex-wrap: wrap;
gap: 8px;
align-items: center;
justify-content: space-between;
}
.share-create .radio-group button {
min-width: 0;
padding: 6px 10px;
}
.share-list {
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
gap: 8px;
overflow-y: auto;
}
.share-list li {
display: flex;
flex-direction: column;
gap: 2px;
}
.share-list .empty {
font-size: 13px;
}
.share-list .cmd code {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
overflow-wrap: normal;
}
@media (max-width: 768px) {
.share-view .status.readonly {
display: inline-flex;
}
}