Rebuild du site sur Astro : i18n multilingue, faits verifies, Docker, CI, tests
- Astro statique, EN par defaut + /fr/ + /nl/, detection langue navigateur - i18n par fichiers JSON, ajouter une langue = ajouter des traductions - Contenu original porte a l'identique (diff d'inventaire par langue) - Chronologie legislative corrigee sur sources primaires (PE, Conseil, votes) - Timeline 23 precedents + observatoire 35 items + annuaire 66 outils FOSS, chaque affirmation verifiee et sourcee - Zero requete tierce (teste), lisible JS coupe, axe WCAG AA x2 themes - Version offline monofichier par langue a chaque build - Docker multi-stage vers nginx + CSP stricte auto-generee - CI GitHub Actions (SHA-pinnees) + verification hebdo des liens - CONTRIBUTING : divulgation d'affiliation obligatoire, allowlist de domaines testee en CI
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
// Post-build: compute the Content-Security-Policy for the built site.
|
||||
// Hashes every inline <script> found in dist/**/*.html (the theme/language
|
||||
// boot script, plus anything Astro chose to inline) so the CSP can stay
|
||||
// strict without 'unsafe-inline'. Emits docker/csp.conf (nginx snippet).
|
||||
// Regenerated on every build — the hash can never drift from the markup.
|
||||
import { createHash } from 'node:crypto'
|
||||
import { readdirSync, readFileSync, writeFileSync } from 'node:fs'
|
||||
import { join } from 'node:path'
|
||||
|
||||
const DIST = 'dist'
|
||||
|
||||
function htmlFiles(dir) {
|
||||
return readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
|
||||
const path = join(dir, entry.name)
|
||||
if (entry.isDirectory()) return htmlFiles(path)
|
||||
return entry.name.endsWith('.html') ? [path] : []
|
||||
})
|
||||
}
|
||||
|
||||
const hashes = new Set()
|
||||
const inlineScript = /<script(?![^>]*\bsrc=)[^>]*>([\s\S]*?)<\/script>/gi
|
||||
|
||||
for (const file of htmlFiles(DIST)) {
|
||||
const html = readFileSync(file, 'utf8')
|
||||
for (const [, body] of html.matchAll(inlineScript)) {
|
||||
if (!body.trim()) continue
|
||||
hashes.add(`'sha256-${createHash('sha256').update(body).digest('base64')}'`)
|
||||
}
|
||||
}
|
||||
|
||||
const scriptSrc = ["'self'", ...hashes].join(' ')
|
||||
const csp = [
|
||||
"default-src 'none'",
|
||||
`script-src ${scriptSrc}`,
|
||||
"style-src 'self'",
|
||||
"img-src 'self' data:",
|
||||
"font-src 'self'",
|
||||
"manifest-src 'self'",
|
||||
"base-uri 'none'",
|
||||
"form-action 'none'",
|
||||
"frame-ancestors 'none'",
|
||||
].join('; ')
|
||||
|
||||
writeFileSync('docker/csp.conf', `add_header Content-Security-Policy "${csp}" always;\n`)
|
||||
console.log(`[gen-csp] ${hashes.size} inline script hash(es) → docker/csp.conf`)
|
||||
Reference in New Issue
Block a user