fix nginx
This commit is contained in:
parent
093a92c831
commit
fc5bd0fa6d
14
README.md
14
README.md
@ -130,7 +130,19 @@ Les traductions sont centralisées dans `src/utils/translations.ts`. Pour ajoute
|
|||||||
6. **etsidemain.com** - Site vitrine pour conseil en transformation régénérative
|
6. **etsidemain.com** - Site vitrine pour conseil en transformation régénérative
|
||||||
7. **avopieces.fr** - Plateforme juridique IA pour procédures de divorce
|
7. **avopieces.fr** - Plateforme juridique IA pour procédures de divorce
|
||||||
|
|
||||||
## 📱 Responsive Design
|
## <20>️ Sécurité
|
||||||
|
|
||||||
|
Ce portfolio implémente des pratiques de sécurité avancées pour protéger contre les vulnérabilités web courantes :
|
||||||
|
|
||||||
|
- **Content Security Policy (CSP)** - Protection contre XSS
|
||||||
|
- **HSTS** - Forçage HTTPS avec preload
|
||||||
|
- **COOP/CORP/COEP** - Isolation cross-origin
|
||||||
|
- **X-Frame-Options** - Protection contre le clickjacking
|
||||||
|
- **Permissions Policy** - Contrôle des fonctionnalités du navigateur
|
||||||
|
|
||||||
|
Pour plus de détails, consultez [SECURITY.md](./SECURITY.md).
|
||||||
|
|
||||||
|
## <20>📱 Responsive Design
|
||||||
|
|
||||||
Le portfolio est entièrement responsive avec des breakpoints optimisés :
|
Le portfolio est entièrement responsive avec des breakpoints optimisés :
|
||||||
- Mobile : < 640px
|
- Mobile : < 640px
|
||||||
|
|||||||
34
nginx.conf
34
nginx.conf
@ -6,14 +6,36 @@ server {
|
|||||||
index index.html;
|
index index.html;
|
||||||
|
|
||||||
# Security headers
|
# Security headers
|
||||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
||||||
add_header X-Content-Type-Options "nosniff" always;
|
|
||||||
add_header X-XSS-Protection "1; mode=block" always;
|
|
||||||
add_header Referrer-Policy "no-referrer-when-downgrade" always;
|
|
||||||
|
|
||||||
# Performance headers
|
# Content Security Policy (CSP) - Protection contre XSS
|
||||||
|
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://www.googletagmanager.com https://www.google-analytics.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: https:; connect-src 'self' https://www.google-analytics.com https://www.googletagmanager.com; frame-ancestors 'none'; base-uri 'self'; form-action 'self'; require-trusted-types-for 'script';" always;
|
||||||
|
|
||||||
|
# HTTP Strict Transport Security (HSTS) - Force HTTPS
|
||||||
|
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
|
||||||
|
|
||||||
|
# Cross-Origin-Opener-Policy (COOP) - Isolation de l'origine
|
||||||
|
add_header Cross-Origin-Opener-Policy "same-origin" always;
|
||||||
|
|
||||||
|
# Cross-Origin-Resource-Policy (CORP)
|
||||||
|
add_header Cross-Origin-Resource-Policy "same-origin" always;
|
||||||
|
|
||||||
|
# Cross-Origin-Embedder-Policy (COEP)
|
||||||
|
add_header Cross-Origin-Embedder-Policy "require-corp" always;
|
||||||
|
|
||||||
|
# Protection contre le clickjacking
|
||||||
|
add_header X-Frame-Options "DENY" always;
|
||||||
|
|
||||||
|
# Protection contre le MIME type sniffing
|
||||||
add_header X-Content-Type-Options "nosniff" always;
|
add_header X-Content-Type-Options "nosniff" always;
|
||||||
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
|
||||||
|
# Protection XSS (legacy)
|
||||||
|
add_header X-XSS-Protection "1; mode=block" always;
|
||||||
|
|
||||||
|
# Referrer Policy - Contrôle des informations envoyées
|
||||||
|
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||||
|
|
||||||
|
# Permissions Policy - Contrôle des fonctionnalités du navigateur
|
||||||
|
add_header Permissions-Policy "geolocation=(), microphone=(), camera=(), payment=(), usb=(), magnetometer=(), gyroscope=(), accelerometer=()" always;
|
||||||
|
|
||||||
# Gzip compression
|
# Gzip compression
|
||||||
gzip on;
|
gzip on;
|
||||||
|
|||||||
85
public/.htaccess
Normal file
85
public/.htaccess
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
# Apache Configuration for Security Headers
|
||||||
|
|
||||||
|
<IfModule mod_headers.c>
|
||||||
|
# Content Security Policy (CSP) - Protection contre XSS
|
||||||
|
Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://www.googletagmanager.com https://www.google-analytics.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: https:; connect-src 'self' https://www.google-analytics.com https://www.googletagmanager.com; frame-ancestors 'none'; base-uri 'self'; form-action 'self'"
|
||||||
|
|
||||||
|
# HTTP Strict Transport Security (HSTS)
|
||||||
|
Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
|
||||||
|
|
||||||
|
# Cross-Origin-Opener-Policy (COOP)
|
||||||
|
Header always set Cross-Origin-Opener-Policy "same-origin"
|
||||||
|
|
||||||
|
# Cross-Origin-Resource-Policy (CORP)
|
||||||
|
Header always set Cross-Origin-Resource-Policy "same-origin"
|
||||||
|
|
||||||
|
# Cross-Origin-Embedder-Policy (COEP)
|
||||||
|
Header always set Cross-Origin-Embedder-Policy "require-corp"
|
||||||
|
|
||||||
|
# Protection contre le clickjacking
|
||||||
|
Header always set X-Frame-Options "DENY"
|
||||||
|
|
||||||
|
# Protection contre le MIME type sniffing
|
||||||
|
Header always set X-Content-Type-Options "nosniff"
|
||||||
|
|
||||||
|
# Protection XSS (legacy)
|
||||||
|
Header always set X-XSS-Protection "1; mode=block"
|
||||||
|
|
||||||
|
# Referrer Policy
|
||||||
|
Header always set Referrer-Policy "strict-origin-when-cross-origin"
|
||||||
|
|
||||||
|
# Permissions Policy
|
||||||
|
Header always set Permissions-Policy "geolocation=(), microphone=(), camera=(), payment=(), usb=(), magnetometer=(), gyroscope=(), accelerometer=()"
|
||||||
|
</IfModule>
|
||||||
|
|
||||||
|
# Redirection HTTPS
|
||||||
|
<IfModule mod_rewrite.c>
|
||||||
|
RewriteEngine On
|
||||||
|
RewriteCond %{HTTPS} off
|
||||||
|
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
|
||||||
|
</IfModule>
|
||||||
|
|
||||||
|
# SPA Fallback - Toutes les routes vers index.html
|
||||||
|
<IfModule mod_rewrite.c>
|
||||||
|
RewriteEngine On
|
||||||
|
RewriteBase /
|
||||||
|
RewriteRule ^index\.html$ - [L]
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-f
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-d
|
||||||
|
RewriteRule . /index.html [L]
|
||||||
|
</IfModule>
|
||||||
|
|
||||||
|
# Cache Control pour les assets statiques
|
||||||
|
<IfModule mod_expires.c>
|
||||||
|
ExpiresActive On
|
||||||
|
|
||||||
|
# Images
|
||||||
|
ExpiresByType image/jpeg "access plus 1 year"
|
||||||
|
ExpiresByType image/jpg "access plus 1 year"
|
||||||
|
ExpiresByType image/png "access plus 1 year"
|
||||||
|
ExpiresByType image/gif "access plus 1 year"
|
||||||
|
ExpiresByType image/webp "access plus 1 year"
|
||||||
|
ExpiresByType image/svg+xml "access plus 1 year"
|
||||||
|
ExpiresByType image/x-icon "access plus 1 year"
|
||||||
|
|
||||||
|
# CSS et JavaScript
|
||||||
|
ExpiresByType text/css "access plus 1 year"
|
||||||
|
ExpiresByType application/javascript "access plus 1 year"
|
||||||
|
ExpiresByType text/javascript "access plus 1 year"
|
||||||
|
|
||||||
|
# Fonts
|
||||||
|
ExpiresByType font/woff "access plus 1 year"
|
||||||
|
ExpiresByType font/woff2 "access plus 1 year"
|
||||||
|
ExpiresByType font/ttf "access plus 1 year"
|
||||||
|
ExpiresByType font/otf "access plus 1 year"
|
||||||
|
ExpiresByType application/font-woff "access plus 1 year"
|
||||||
|
ExpiresByType application/font-woff2 "access plus 1 year"
|
||||||
|
|
||||||
|
# HTML (no cache)
|
||||||
|
ExpiresByType text/html "access plus 0 seconds"
|
||||||
|
</IfModule>
|
||||||
|
|
||||||
|
# Compression Gzip
|
||||||
|
<IfModule mod_deflate.c>
|
||||||
|
AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css text/javascript application/javascript application/json application/xml application/rss+xml application/atom+xml font/truetype font/opentype application/vnd.ms-fontobject image/svg+xml
|
||||||
|
</IfModule>
|
||||||
61
public/_headers
Normal file
61
public/_headers
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
# Security Headers
|
||||||
|
|
||||||
|
# Content Security Policy (CSP) - Protection contre XSS
|
||||||
|
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://www.googletagmanager.com https://www.google-analytics.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: https:; connect-src 'self' https://www.google-analytics.com https://www.googletagmanager.com; frame-ancestors 'none'; base-uri 'self'; form-action 'self'
|
||||||
|
|
||||||
|
# HTTP Strict Transport Security (HSTS) - Force HTTPS
|
||||||
|
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
|
||||||
|
|
||||||
|
# Cross-Origin-Opener-Policy (COOP) - Isolation de l'origine
|
||||||
|
Cross-Origin-Opener-Policy: same-origin
|
||||||
|
|
||||||
|
# Cross-Origin-Resource-Policy (CORP)
|
||||||
|
Cross-Origin-Resource-Policy: same-origin
|
||||||
|
|
||||||
|
# Cross-Origin-Embedder-Policy (COEP)
|
||||||
|
Cross-Origin-Embedder-Policy: require-corp
|
||||||
|
|
||||||
|
# Protection contre le clickjacking
|
||||||
|
X-Frame-Options: DENY
|
||||||
|
|
||||||
|
# Protection contre le MIME type sniffing
|
||||||
|
X-Content-Type-Options: nosniff
|
||||||
|
|
||||||
|
# Protection XSS (legacy)
|
||||||
|
X-XSS-Protection: 1; mode=block
|
||||||
|
|
||||||
|
# Referrer Policy - Contrôle des informations envoyées
|
||||||
|
Referrer-Policy: strict-origin-when-cross-origin
|
||||||
|
|
||||||
|
# Permissions Policy - Contrôle des fonctionnalités du navigateur
|
||||||
|
Permissions-Policy: geolocation=(), microphone=(), camera=(), payment=(), usb=(), magnetometer=(), gyroscope=(), accelerometer=()
|
||||||
|
|
||||||
|
# Cache Control
|
||||||
|
Cache-Control: public, max-age=0, must-revalidate
|
||||||
|
|
||||||
|
/assets/*
|
||||||
|
# Cache les assets statiques pendant 1 an
|
||||||
|
Cache-Control: public, max-age=31536000, immutable
|
||||||
|
|
||||||
|
/images/*
|
||||||
|
# Cache les images pendant 1 an
|
||||||
|
Cache-Control: public, max-age=31536000, immutable
|
||||||
|
|
||||||
|
/*.js
|
||||||
|
# Cache les fichiers JS pendant 1 an
|
||||||
|
Cache-Control: public, max-age=31536000, immutable
|
||||||
|
|
||||||
|
/*.css
|
||||||
|
# Cache les fichiers CSS pendant 1 an
|
||||||
|
Cache-Control: public, max-age=31536000, immutable
|
||||||
|
|
||||||
|
/*.woff
|
||||||
|
# Cache les polices pendant 1 an
|
||||||
|
Cache-Control: public, max-age=31536000, immutable
|
||||||
|
Access-Control-Allow-Origin: *
|
||||||
|
|
||||||
|
/*.woff2
|
||||||
|
# Cache les polices pendant 1 an
|
||||||
|
Cache-Control: public, max-age=31536000, immutable
|
||||||
|
Access-Control-Allow-Origin: *
|
||||||
@ -128,7 +128,7 @@ export const Header = () => {
|
|||||||
animate={{ opacity: 1, scale: 1 }}
|
animate={{ opacity: 1, scale: 1 }}
|
||||||
transition={{ delay: 0.4 }}
|
transition={{ delay: 0.4 }}
|
||||||
>
|
>
|
||||||
<Button variant="ghost" size="icon" onClick={toggleTheme}>
|
<Button variant="ghost" size="icon" onClick={toggleTheme} aria-label={theme === "dark" ? "Passer en mode clair" : "Passer en mode sombre"}>
|
||||||
{theme === "dark" ? (
|
{theme === "dark" ? (
|
||||||
<Sun className="h-5 w-5" />
|
<Sun className="h-5 w-5" />
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@ -68,6 +68,7 @@ export const ImageGallery = ({ images, projectTitle }: ImageGalleryProps) => {
|
|||||||
size="icon"
|
size="icon"
|
||||||
className="absolute top-4 right-4 text-foreground"
|
className="absolute top-4 right-4 text-foreground"
|
||||||
onClick={closeLightbox}
|
onClick={closeLightbox}
|
||||||
|
aria-label="Fermer la galerie"
|
||||||
>
|
>
|
||||||
<X className="w-6 h-6" />
|
<X className="w-6 h-6" />
|
||||||
</Button>
|
</Button>
|
||||||
@ -80,6 +81,7 @@ export const ImageGallery = ({ images, projectTitle }: ImageGalleryProps) => {
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
prevImage();
|
prevImage();
|
||||||
}}
|
}}
|
||||||
|
aria-label="Image précédente"
|
||||||
>
|
>
|
||||||
<ChevronLeft className="w-8 h-8" />
|
<ChevronLeft className="w-8 h-8" />
|
||||||
</Button>
|
</Button>
|
||||||
@ -103,6 +105,7 @@ export const ImageGallery = ({ images, projectTitle }: ImageGalleryProps) => {
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
nextImage();
|
nextImage();
|
||||||
}}
|
}}
|
||||||
|
aria-label="Image suivante"
|
||||||
>
|
>
|
||||||
<ChevronRight className="w-8 h-8" />
|
<ChevronRight className="w-8 h-8" />
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
49
vercel.json
Normal file
49
vercel.json
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
{
|
||||||
|
"headers": [
|
||||||
|
{
|
||||||
|
"source": "/(.*)",
|
||||||
|
"headers": [
|
||||||
|
{
|
||||||
|
"key": "Content-Security-Policy",
|
||||||
|
"value": "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://www.googletagmanager.com https://www.google-analytics.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: https:; connect-src 'self' https://www.google-analytics.com https://www.googletagmanager.com; frame-ancestors 'none'; base-uri 'self'; form-action 'self'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Strict-Transport-Security",
|
||||||
|
"value": "max-age=63072000; includeSubDomains; preload"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Cross-Origin-Opener-Policy",
|
||||||
|
"value": "same-origin"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Cross-Origin-Resource-Policy",
|
||||||
|
"value": "same-origin"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Cross-Origin-Embedder-Policy",
|
||||||
|
"value": "require-corp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "X-Frame-Options",
|
||||||
|
"value": "DENY"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "X-Content-Type-Options",
|
||||||
|
"value": "nosniff"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "X-XSS-Protection",
|
||||||
|
"value": "1; mode=block"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Referrer-Policy",
|
||||||
|
"value": "strict-origin-when-cross-origin"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Permissions-Policy",
|
||||||
|
"value": "geolocation=(), microphone=(), camera=(), payment=(), usb=(), magnetometer=(), gyroscope=(), accelerometer=()"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user