From 36d1e307bd0bc137a9b588b38c5f5ba8657b0716 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20FAMIBELLE-PRONZOLA?= Date: Tue, 7 Jul 2026 23:56:56 +0400 Subject: [PATCH] =?UTF-8?q?security(headers):=20ajoute=20les=20en-t=C3=AAt?= =?UTF-8?q?es=20HTTP=20de=20s=C3=A9curit=C3=A9=20et=20refactorise=20les=20?= =?UTF-8?q?scripts=20inline=20pour=20CSP?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Ajoute .htaccess avec HSTS, CSP, X-Frame-Options, X-Content-Type-Options, Referrer-Policy et Permissions-Policy - Déplace les scripts inline (thème, redirection de langue) vers des fichiers JS externes - Remplace les onclick inline par des écouteurs d'événements dans main.js - Configure Eleventy pour copier .htaccess à la racine du site --- eleventy.config.js | 3 +++ src/.htaccess | 19 ++++++++++++++ src/_includes/layouts/base.njk | 41 +++--------------------------- src/_includes/partials/faq.njk | 2 +- src/_includes/partials/nav.njk | 4 +-- src/_includes/partials/support.njk | 4 +-- src/assets/js/lang-redirect.js | 15 +++++++++++ src/assets/js/main.js | 27 ++++++++++++++++++++ src/assets/js/theme.js | 17 +++++++++++++ 9 files changed, 89 insertions(+), 43 deletions(-) create mode 100644 src/.htaccess create mode 100644 src/assets/js/lang-redirect.js create mode 100644 src/assets/js/theme.js diff --git a/eleventy.config.js b/eleventy.config.js index 7df2a2d..ed0c39e 100644 --- a/eleventy.config.js +++ b/eleventy.config.js @@ -20,6 +20,9 @@ module.exports = function(eleventyConfig) { eleventyConfig.addPassthroughCopy("src/robots.txt"); eleventyConfig.addPassthroughCopy("src/sitemap.xml"); + // Copy Apache security headers config to the site root + eleventyConfig.addPassthroughCopy({ "src/.htaccess": ".htaccess" }); + // Watch CSS files for changes eleventyConfig.addWatchTarget("src/assets/css/"); diff --git a/src/.htaccess b/src/.htaccess new file mode 100644 index 0000000..08a5a01 --- /dev/null +++ b/src/.htaccess @@ -0,0 +1,19 @@ + + # Force HTTPS for one year + Header always set Strict-Transport-Security "max-age=31536000" + + # Prevent MIME sniffing + Header always set X-Content-Type-Options "nosniff" + + # Prevent clickjacking (also enforced via CSP frame-ancestors) + Header always set X-Frame-Options "DENY" + + # Control referrer leakage + Header always set Referrer-Policy "strict-origin-when-cross-origin" + + # Restrict browser features + Header always set Permissions-Policy "geolocation=(), microphone=(), camera=(), payment=(), usb=(), magnetometer=(), gyroscope=()" + + # Content Security Policy (no unsafe-inline/unsafe-eval in script-src) + Header always set Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; img-src 'self' https://organisationka.b-cdn.net; font-src 'self' https://fonts.gstatic.com; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'; object-src 'none'" + diff --git a/src/_includes/layouts/base.njk b/src/_includes/layouts/base.njk index 321a32b..4693055 100644 --- a/src/_includes/layouts/base.njk +++ b/src/_includes/layouts/base.njk @@ -1,5 +1,5 @@ - + @@ -100,43 +100,8 @@ } - - - + + {% include "partials/nav.njk" %} diff --git a/src/_includes/partials/faq.njk b/src/_includes/partials/faq.njk index a0e9a69..311bcd7 100644 --- a/src/_includes/partials/faq.njk +++ b/src/_includes/partials/faq.njk @@ -8,7 +8,7 @@
{% for item in t.faq.items %}
- diff --git a/src/_includes/partials/nav.njk b/src/_includes/partials/nav.njk index 33fe559..435e677 100644 --- a/src/_includes/partials/nav.njk +++ b/src/_includes/partials/nav.njk @@ -31,9 +31,9 @@ {% endfor %} {% if locale == 'en' %} - {{ t.ui.lang_switch_label }} + {{ t.ui.lang_switch_label }} {% else %} - {{ t.ui.lang_switch_label }} + {{ t.ui.lang_switch_label }} {% endif %}
@@ -49,7 +49,7 @@
{{ t.support.monthly_suffix }} -
diff --git a/src/assets/js/lang-redirect.js b/src/assets/js/lang-redirect.js new file mode 100644 index 0000000..cf22011 --- /dev/null +++ b/src/assets/js/lang-redirect.js @@ -0,0 +1,15 @@ +(function() { + // Redirection automatique selon la langue du navigateur, sauf choix explicite déjà mémorisé + var pref = localStorage.getItem('oki-lang-pref'); + if (pref) return; + + var browserLang = (navigator.language || navigator.userLanguage || '').toLowerCase(); + var wantsEnglish = browserLang.indexOf('en') === 0; + var currentLocale = document.documentElement.dataset.locale || 'fr'; + + if (wantsEnglish && currentLocale !== 'en') { + window.location.replace('/en/' + window.location.hash); + } else if (!wantsEnglish && currentLocale === 'en') { + window.location.replace('/' + window.location.hash); + } +})(); diff --git a/src/assets/js/main.js b/src/assets/js/main.js index a22b8f2..de4ea30 100644 --- a/src/assets/js/main.js +++ b/src/assets/js/main.js @@ -67,6 +67,18 @@ // Ajouter l'événement au bouton themeToggle?.addEventListener('click', toggleTheme); + + // Language switcher links + document.querySelectorAll('.lang-switch').forEach(function(link) { + link.addEventListener('click', function(e) { + e.preventDefault(); + var lang = this.dataset.lang; + if (lang) { + localStorage.setItem('oki-lang-pref', lang); + switchLanguage(lang); + } + }); + }); })(); // Language switch with page mapping @@ -377,3 +389,18 @@ function handleCustomDonation(type) { window.location.href = stripeUrl; } + +// FAQ accordion toggle +document.querySelectorAll('.faq-question').forEach(button => { + button.addEventListener('click', () => { + button.parentElement.classList.toggle('active'); + }); +}); + +// Custom donation buttons +document.querySelectorAll('.custom-donation-btn').forEach(button => { + button.addEventListener('click', () => { + const type = button.dataset.donationType; + if (type) handleCustomDonation(type); + }); +}); diff --git a/src/assets/js/theme.js b/src/assets/js/theme.js new file mode 100644 index 0000000..b5516b9 --- /dev/null +++ b/src/assets/js/theme.js @@ -0,0 +1,17 @@ +(function() { + // Récupérer le thème stocké ou détecter le thème système + const storedTheme = localStorage.getItem('oki-theme'); + const systemPrefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; + + let theme = storedTheme; + + // Si pas de préférence stockée, utiliser le thème système + if (!storedTheme) { + theme = systemPrefersDark ? 'dark' : 'light'; + } + + // Appliquer le thème immédiatement + if (theme === 'light') { + document.documentElement.classList.add('light-theme'); + } +})();