securite: #87 T5a supprime 16 handlers inline au profit de listeners (CSP inchangee)

This commit is contained in:
2026-09-26 22:32:01 -04:00
parent 330462e7a5
commit 36a4030c09
15 changed files with 91 additions and 59 deletions
+5 -1
View File
@@ -6,7 +6,7 @@ Format basé sur [Keep a Changelog](https://keepachangelog.com/fr/1.1.0/),
et [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
> **En cours de développement** : les changements à venir sont listés dans la section
> [Unreleased](#unreleased). La dernière version livrée est **2.28.5**.
> [Unreleased](#unreleased). La dernière version livrée est **2.28.6**.
---
@@ -14,6 +14,10 @@ et [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
---
## [2.28.6] — 2026-09-26
---
## [2.28.5] — 2026-09-26
---
+3 -3
View File
@@ -4,7 +4,7 @@
**Porte d'entrée web ultra-léger pour vos vaults Obsidian** — Accédez, naviguez et recherchez dans toutes vos notes Obsidian depuis n'importe quel appareil via une interface web moderne et responsive.
[![Version](https://img.shields.io/badge/Version-2.28.5-blue.svg)]()
[![Version](https://img.shields.io/badge/Version-2.28.6-blue.svg)]()
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Docker](https://img.shields.io/badge/Docker-Ready-blue.svg)](https://www.docker.com/)
[![Python](https://img.shields.io/badge/Python-3.11+-green.svg)](https://www.python.org/)
@@ -976,8 +976,8 @@ Ce projet est sous licence **MIT** — voir le fichier [LICENSE](LICENSE) pour l
## 📝 Changelog
Consultez le [CHANGELOG.md](./CHANGELOG.md) pour l'historique complet de toutes les versions (v1.0.0 → v2.28.5).
Consultez le [CHANGELOG.md](./CHANGELOG.md) pour l'historique complet de toutes les versions (v1.0.0 → v2.28.6).
---
*Projet : ObsiGate | Version : 2.28.5 | Dernière mise à jour : Septembre 2026*
*Projet : ObsiGate | Version : 2.28.6 | Dernière mise à jour : Septembre 2026*
+3 -3
View File
@@ -2,7 +2,7 @@
**Ultra-light web gateway for your Obsidian vaults** — Access, browse, and search all your Obsidian notes from any device via a modern, responsive web interface.
[![Version](https://img.shields.io/badge/Version-2.28.5-blue.svg)]()
[![Version](https://img.shields.io/badge/Version-2.28.6-blue.svg)]()
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Docker](https://img.shields.io/badge/Docker-Ready-blue.svg)](https://www.docker.com/)
[![Python](https://img.shields.io/badge/Python-3.11+-green.svg)](https://www.python.org/)
@@ -1151,8 +1151,8 @@ This project is licensed under the **MIT License** - see the [LICENSE](LICENSE)
## 📝 Changelog
See [CHANGELOG.md](./CHANGELOG.md) for the complete version history (v1.0.0 → v2.28.5).
See [CHANGELOG.md](./CHANGELOG.md) for the complete version history (v1.0.0 → v2.28.6).
---
*Project: ObsiGate | Version: 2.28.5 | Last updated: September 2026*
*Project: ObsiGate | Version: 2.28.6 | Last updated: September 2026*
+1 -1
View File
@@ -1 +1 @@
2.28.5
2.28.6
+1 -1
View File
@@ -2626,7 +2626,7 @@ dependencies = [
[[package]]
name = "obsigate-desktop"
version = "2.28.5"
version = "2.28.6"
dependencies = [
"chrono",
"env_logger",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "obsigate-desktop"
version = "2.28.5"
version = "2.28.6"
description = "ObsiGate Desktop — Porte d'entrée native pour vos vaults Obsidian"
authors = ["Bruno Charest"]
edition = "2021"
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://raw.githubusercontent.com/nicedoc/obsigate/main/desktop/tauri.conf.schema.json",
"productName": "ObsiGate",
"version": "2.28.5",
"version": "2.28.6",
"identifier": "com.obsigate.desktop",
"build": {
"frontendDist": "../frontend",
+1 -1
View File
@@ -1,6 +1,6 @@
# ObsiGate — Roadmap
> **Version :** 2.28.5 | **Dernière mise à jour :** 2026-09-26
> **Version :** 2.28.6 | **Dernière mise à jour :** 2026-09-26
> **Ce fichier ne contient que le travail à venir** (🔵 En cours + ⚪ Backlog) et un index compact
> vers les fonctionnalités livrées.
> - **Méthode de livraison à appliquer pour toute tâche : [DELIVERY_WORKFLOW.md](./DELIVERY_WORKFLOW.md)**
+1 -31
View File
@@ -1576,17 +1576,6 @@
class="help-search-clear"
id="config-search-clear"
title="Effacer"
onclick="
var s =
document.getElementById(
'config-nav-search',
);
if (s) {
s.value = '';
s.dispatchEvent(new Event('input'));
s.focus();
}
"
>
X
</button>
@@ -1698,7 +1687,7 @@
<input type="text" id="profile-name" class="config-input" placeholder="Votre nom" maxlength="60">
</div>
<button class="config-save-btn" id="profile-save" data-i18n="config.save">Enregistrer</button>
<button class="config-save-btn" id="profile-logout" style="background:var(--danger-bg);color:var(--danger);border-color:var(--danger);margin-left:8px" data-i18n="config.logout" onclick="if(window.handleLogout)window.handleLogout();else{doLogoutFallback()}">Déconnexion</button>
<button class="config-save-btn" id="profile-logout" style="background:var(--danger-bg);color:var(--danger);border-color:var(--danger);margin-left:8px" data-i18n="config.logout">Déconnexion</button>
<span class="profile-saved" id="profile-saved" style="display:none" data-i18n="config.saved">✓ Sauvegardé</span>
</div>
</section>
@@ -3005,14 +2994,6 @@
id="help-hamburger"
title="Sommaire"
aria-label="Afficher le sommaire"
onclick="
var n = document.getElementById('help-nav');
if (n) {
var d = n.style.display;
n.style.display =
d === 'none' || d === '' ? 'flex' : 'none';
}
"
>
<i
data-lucide="menu"
@@ -3077,17 +3058,6 @@
id="help-search-clear"
title="Effacer la recherche"
aria-label="Effacer"
onclick="
var s =
document.getElementById(
'help-nav-search',
);
if (s) {
s.value = '';
s.dispatchEvent(new Event('input'));
s.focus();
}
"
>
✕
</button>
+12 -2
View File
@@ -1305,8 +1305,7 @@ async function _startMfaSetup() {
// secret when the backend has no QR generator available.
const qrImg = data.qr_data_url
? `<img id="mfa-qr-img" alt="QR Code" class="mfa-qr-code-img"
src="${data.qr_data_url}"
onerror="this.style.display='none';document.getElementById('mfa-qr-fallback').style.display='block';">`
src="${data.qr_data_url}">`
: "";
const fallbackStyle = data.qr_data_url ? "display:none" : "";
flowArea.innerHTML = `
@@ -1335,6 +1334,17 @@ async function _startMfaSetup() {
codeInput.value = codeInput.value.replace(/[^0-9]/g, "");
});
// QR fallback (#87, ex-onerror inline) : si l'image ne charge pas,
// afficher la saisie manuelle du secret.
const qrImgEl = document.getElementById("mfa-qr-img");
if (qrImgEl) {
qrImgEl.addEventListener("error", () => {
qrImgEl.style.display = "none";
const fallback = document.getElementById("mfa-qr-fallback");
if (fallback) fallback.style.display = "block";
});
}
document.getElementById("mfa-confirm-btn").addEventListener("click", async () => {
const code = codeInput.value.trim();
if (code.length !== 6) return;
+39 -3
View File
@@ -363,6 +363,27 @@ function initHelpModal() {
}
});
// Help TOC hamburger + search clear (#87, ex-onclick inline in index.html).
var helpHamburger = document.getElementById("help-hamburger");
if (helpHamburger) {
helpHamburger.addEventListener("click", function() {
var n = document.getElementById("help-nav");
if (n) {
var d = n.style.display;
n.style.display = d === "none" || d === "" ? "flex" : "none";
}
});
}
var helpSearch = document.getElementById("help-nav-search");
var helpSearchClear = document.getElementById("help-search-clear");
if (helpSearchClear && helpSearch) {
helpSearchClear.addEventListener("click", function() {
helpSearch.value = "";
helpSearch.dispatchEvent(new Event("input"));
helpSearch.focus();
});
}
document.addEventListener("keydown", (e) => {
if (e.key === "Escape" && modal.classList.contains("active")) {
closeHelpModal();
@@ -883,7 +904,7 @@ function initConfigModal() {
});
}
// Logout button — handled inline in index.html (onclick)
// Logout button — wired here with addEventListener (#87, no inline onclick)
// Config nav search
var cfgSearch = document.getElementById("config-nav-search");
@@ -901,6 +922,16 @@ function initConfigModal() {
});
}
// Config search clear button (#87, ex-onclick inline).
var cfgSearchClear = document.getElementById("config-search-clear");
if (cfgSearchClear && cfgSearch) {
cfgSearchClear.addEventListener("click", function() {
cfgSearch.value = "";
cfgSearch.dispatchEvent(new Event("input"));
cfgSearch.focus();
});
}
// BUG-071/#114: mobile table of contents. #config-nav shares the .help-nav
// rule that hides it below 768px, but — unlike the help modal — the config
// modal had no toggle to reveal it, leaving mobile users with no way to
@@ -1575,13 +1606,15 @@ export async function openShareDialog(vault, path) {
<p style="font-size:0.85rem;color:var(--text-muted);margin-bottom:4px">${escapeHtml(vault)}/${escapeHtml(path)}</p>
${expiresInfo}
<p style="font-size:0.75rem;color:var(--text-muted);margin-bottom:8px">${existingShare.access_count} vue(s)</p>
<input type="text" class="share-url-input" value="${url}" readonly onclick="this.select()">
<input type="text" class="share-url-input" value="${url}" readonly>
<div class="share-dialog-actions">
<button class="share-copy-btn">📋 Copier le lien</button>
<button class="share-revoke-btn">🗑 Révoquer</button>
<button class="share-close-btn">Fermer</button>
</div>
</div>`;
const shareUrlInput = div.querySelector(".share-url-input");
if (shareUrlInput) shareUrlInput.addEventListener("click", function() { shareUrlInput.select(); });
div.querySelector(".share-copy-btn").addEventListener("click", async () => {
try {
await navigator.clipboard.writeText(url);
@@ -2525,7 +2558,10 @@ function initProfile() {
} catch(e) {}
});
// Logout button — handled inline in index.html (onclick)
// Logout button (#87, ex-onclick inline in index.html).
if (logoutBtn && window.handleLogout) {
logoutBtn.addEventListener('click', function() { window.handleLogout(); });
}
// ── Avatar (#113) ────────────────────────────────────────────────
var avatarField = document.getElementById('profile-avatar-field');
+7 -6
View File
@@ -478,8 +478,8 @@ function openTemplateModal() {
'</div>' +
'</div>' +
'<div class="modal-footer">' +
'<button class="btn btn-primary" data-i18n="common.close" onclick="this.closest(\'.modal-overlay\').remove()">Close</button>' +
'<button class="btn btn-secondary" onclick="copyTemplate()">Copy to Clipboard</button>' +
'<button class="btn btn-primary btn-modal-close" data-i18n="common.close">Close</button>' +
'<button class="btn btn-secondary btn-copy-template">Copy to Clipboard</button>' +
'</div>' +
'</div>';
@@ -487,11 +487,11 @@ function openTemplateModal() {
modal.querySelector('.modal-close')?.addEventListener('click', () => modal.remove());
modal.addEventListener('click', (e) => { if (e.target === modal) modal.remove(); });
window.copyTemplate = () => {
modal.querySelector('.btn-modal-close')?.addEventListener('click', () => modal.remove());
modal.querySelector('.btn-copy-template')?.addEventListener('click', () => {
navigator.clipboard.writeText(JSON.stringify(template, null, 2));
showToast('Template copied to clipboard', 'success');
};
});
});
}
@@ -508,12 +508,13 @@ function openCodeModal(name, code) {
'<pre class="code-block" style="max-height: 500px; overflow: auto;">' + escapeHtml(code) + '</pre>' +
'</div>' +
'<div class="modal-footer">' +
'<button class="btn btn-primary" data-i18n="common.close" onclick="this.closest(\'.modal-overlay\').remove()">Close</button>' +
'<button class="btn btn-primary btn-modal-close" data-i18n="common.close">Close</button>' +
'</div>' +
'</div>';
document.body.appendChild(modal);
modal.querySelector('.modal-close')?.addEventListener('click', () => modal.remove());
modal.querySelector('.btn-modal-close')?.addEventListener('click', () => modal.remove());
modal.addEventListener('click', (e) => { if (e.target === modal) modal.remove(); });
}
+8 -2
View File
@@ -543,10 +543,16 @@ function showUpdateNotification() {
message.innerHTML = `
<div class="pwa-update-content">
<span>Une nouvelle version d'ObsiGate est disponible !</span>
<button class="pwa-update-btn" onclick="window.location.reload()">Mettre à jour</button>
<button class="pwa-update-dismiss" onclick="this.parentElement.parentElement.remove()">×</button>
<button class="pwa-update-btn">Mettre à jour</button>
<button class="pwa-update-dismiss">×</button>
</div>
`;
message.querySelector(".pwa-update-btn").addEventListener("click", function() {
window.location.reload();
});
message.querySelector(".pwa-update-dismiss").addEventListener("click", function() {
message.remove();
});
document.body.appendChild(message);
// Auto-dismiss after 30 seconds
+7 -2
View File
@@ -1140,10 +1140,10 @@ export function renderFile(data) {
<div class="pdf-viewer-container">
<div class="pdf-toolbar">
<span class="pdf-info">PDF — ${pages} pages</span>
<button class="btn-action" onclick="window.open('${pdfUrl}', '_blank')">
<button class="btn-action" data-pdf-url="${pdfUrl}">
<i data-lucide="external-link" style="width:14px;height:14px"></i> Plein écran
</button>
<button class="btn-action" onclick="window.open('/api/file/${encodeURIComponent(data.vault)}/download?path=${encodeURIComponent(data.path)}', '_blank')">
<button class="btn-action" data-download-url="/api/file/${encodeURIComponent(data.vault)}/download?path=${encodeURIComponent(data.path)}">
<i data-lucide="download" style="width:14px;height:14px"></i> Télécharger
</button>
</div>
@@ -1152,6 +1152,11 @@ export function renderFile(data) {
<iframe src="${pdfUrl}" data-pdf-url="${pdfUrl}" class="pdf-iframe" title="${escapeHtml(data.title)}"></iframe>
</div>
</div>`;
area.querySelectorAll('.pdf-toolbar .btn-action').forEach((btn) => {
btn.addEventListener('click', () => {
window.open(btn.dataset.pdfUrl || btn.dataset.downloadUrl, '_blank');
});
});
area.querySelectorAll('.pdf-toc a[data-page]').forEach((link) => {
link.addEventListener('click', (e) => {
e.preventDefault();
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "obsigate",
"version": "2.28.5",
"version": "2.28.6",
"description": "**Porte d'entrée web ultra-léger pour vos vaults Obsidian** — Accédez, naviguez et recherchez dans toutes vos notes Obsidian depuis n'importe quel appareil via une interface web moderne et responsive.",
"main": "patch.js",
"directories": {