fix: partage dirigé — ACL court-circuitée après résolution Partage/ + chemin canonique au token #196
CI / lint (push) Successful in 3m0s
CI / security (push) Failing after 1m38s
CI / test (push) Successful in 4m58s
CI / build (push) Successful in 1m36s
CI / e2e (push) Successful in 19m55s

- le partage EST l'autorisation: plus de 403 'Accès refusé à la vault' pour un
  destinataire sans accès au vault émetteur (api_file, raw, download)
- chemin virtuel canonique Partage/<token>/<nom> — désambiguïse les homonymes,
  fallback par nom conservé
- 'reçu' = partagé_avec contient l'utilisateur: un admin ne voit plus les
  partages des autres comme reçus (fix File not found: Partage/…)
- +3 tests (14 total)
This commit is contained in:
2026-10-10 23:11:12 -04:00
parent de6bde1613
commit 2be0b15bcf
14 changed files with 139 additions and 35 deletions
+5 -1
View File
@@ -62,7 +62,11 @@ const DashboardSharedWidget = {
if (empty) empty.style.display = "none";
const me = (AuthManager.getUser() || {}).username;
grid.innerHTML = shares.map(s => {
const received = me && s.created_by && s.created_by !== me;
// Reçu = adressé à moi (et pas créé par moi). Un admin voit TOUS les
// partages via /api/shares — ceux des autres comptes ne sont pas
// « reçus » pour lui : ils ne se résolvent pas dans son Partage/.
const received = me && s.created_by && s.created_by !== me
&& Array.isArray(s.shared_with) && s.shared_with.includes(me);
const actions = received
? `<button class="shared-open-btn">${t('dashboard.open_btn')}</button>`
: `<button class="shared-copy-btn" data-url="${window.location.origin}/s/${s.token}">${t('dashboard.copy_btn')}</button>
+16 -11
View File
@@ -32,7 +32,10 @@ async function _loadReceivedShares() {
if (!me) return [];
try {
const shares = await api("/api/shares");
return shares.filter(s => s.created_by && s.created_by !== me);
// Adressés à moi uniquement : un admin voit tous les partages via
// /api/shares, ceux des autres ne sont pas dans SON Partage/.
return shares.filter(s => s.created_by && s.created_by !== me
&& Array.isArray(s.shared_with) && s.shared_with.includes(me));
} catch { return []; }
}
@@ -366,16 +369,17 @@ async function incrementalLoadDirectory(vaultName, dirPath, container) {
const shareSub = el("div", { class: "tree-children collapsed", id: `dir-${vaultName}-Partage` });
item.shares.forEach((s) => {
const name = (s.path || "").split("/").pop().replace(/\.md$/i, "");
const fItem = el("div", { class: "tree-item", "data-vault": vaultName, "data-path": `Partage/${s.path.split("/").pop()}`, "data-type": "file", "data-share-token": s.token }, [_sharedIcon(), el("span", { class: "tree-item-text" }, [document.createTextNode(name)])]);
const virtualPath = `Partage/${s.token}/${s.path.split("/").pop()}`;
const fItem = el("div", { class: "tree-item", "data-vault": vaultName, "data-path": virtualPath, "data-type": "file", "data-share-token": s.token }, [_sharedIcon(), el("span", { class: "tree-item-text" }, [document.createTextNode(name)])]);
fItem.title = t("dashboard.shared_by", { user: s.created_by || "" });
fItem.addEventListener("click", () => {
scrollTreeItemIntoView(fItem, false);
// #196 : ouverture en onglet applicatif — /api/file résout Partage/
// vers le fichier source (lecture seule).
// #196 : ouverture en onglet applicatif — /api/file résout
// Partage/<token>/<nom> vers le fichier source (lecture seule).
if (window.innerWidth <= 768) {
TabManager.openPersistent(vaultName, `Partage/${(s.path || "").split("/").pop()}`);
TabManager.openPersistent(vaultName, virtualPath);
} else {
TabManager.openPreview(vaultName, `Partage/${(s.path || "").split("/").pop()}`);
TabManager.openPreview(vaultName, virtualPath);
}
closeMobileSidebar();
});
@@ -736,16 +740,17 @@ async function loadDirectory(vaultName, dirPath, container) {
const shareSub = el("div", { class: "tree-children collapsed", id: `dir-${vaultName}-Partage` });
item.shares.forEach((s) => {
const name = (s.path || "").split("/").pop().replace(/\.md$/i, "");
const fItem = el("div", { class: "tree-item", "data-vault": vaultName, "data-path": `Partage/${s.path.split("/").pop()}`, "data-type": "file", "data-share-token": s.token }, [_sharedIcon(), el("span", { class: "tree-item-text" }, [document.createTextNode(name)])]);
const virtualPath = `Partage/${s.token}/${s.path.split("/").pop()}`;
const fItem = el("div", { class: "tree-item", "data-vault": vaultName, "data-path": virtualPath, "data-type": "file", "data-share-token": s.token }, [_sharedIcon(), el("span", { class: "tree-item-text" }, [document.createTextNode(name)])]);
fItem.title = t("dashboard.shared_by", { user: s.created_by || "" });
fItem.addEventListener("click", () => {
scrollTreeItemIntoView(fItem, false);
// #196 : ouverture en onglet applicatif — /api/file résout Partage/
// vers le fichier source (lecture seule).
// #196 : ouverture en onglet applicatif — /api/file résout
// Partage/<token>/<nom> vers le fichier source (lecture seule).
if (window.innerWidth <= 768) {
TabManager.openPersistent(vaultName, `Partage/${(s.path || "").split("/").pop()}`);
TabManager.openPersistent(vaultName, virtualPath);
} else {
TabManager.openPreview(vaultName, `Partage/${(s.path || "").split("/").pop()}`);
TabManager.openPreview(vaultName, virtualPath);
}
closeMobileSidebar();
});