Files
RepoDructor/server/middleware/proxy-headers.ts
josedario87 d966fab4ca
All checks were successful
build-and-deploy / build (push) Successful in 26s
build-and-deploy / deploy (push) Successful in 3s
mejorando configuracion de seguridad
2025-10-11 18:06:11 -06:00

104 lines
4.1 KiB
TypeScript

export default defineEventHandler(async (event) => {
const url = getRequestURL(event)
// ==========================================
// PWA Critical Resources: Service Worker & Manifest
// ==========================================
if (url.pathname === '/sw.js' || url.pathname.startsWith('/workbox-')) {
// Service Worker must NEVER be cached by browser - always check for updates
setHeader(event, 'Content-Type', 'application/javascript; charset=utf-8')
setHeader(event, 'Cache-Control', 'no-cache, no-store, must-revalidate')
setHeader(event, 'Service-Worker-Allowed', '/')
return
}
if (url.pathname === '/manifest.webmanifest' || url.pathname === '/manifest.json') {
setHeader(event, 'Content-Type', 'application/manifest+json; charset=utf-8')
setHeader(event, 'Cache-Control', 'public, max-age=0, must-revalidate')
return
}
// ==========================================
// Nuxt Build Assets: Let Service Worker control cache
// ==========================================
if (url.pathname.startsWith('/_nuxt/')) {
const ext = url.pathname.split('.').pop()?.toLowerCase()
// Set proper MIME types
switch (ext) {
case 'js':
setHeader(event, 'Content-Type', 'application/javascript; charset=utf-8')
break
case 'mjs':
setHeader(event, 'Content-Type', 'application/javascript; charset=utf-8')
break
case 'css':
setHeader(event, 'Content-Type', 'text/css; charset=utf-8')
break
case 'json':
setHeader(event, 'Content-Type', 'application/json; charset=utf-8')
break
case 'svg':
setHeader(event, 'Content-Type', 'image/svg+xml; charset=utf-8')
break
}
// Immutable assets (with hash in filename) can be cached aggressively
if (url.pathname.match(/\.[a-f0-9]{8,}\.(js|css|json|svg|png|jpg|webp|woff2?)$/)) {
setHeader(event, 'Cache-Control', 'public, max-age=31536000, immutable')
} else {
// Non-hashed assets: let Service Worker decide
setHeader(event, 'Cache-Control', 'public, max-age=0, must-revalidate')
}
}
// ==========================================
// Static Assets (logos, icons, images)
// ==========================================
if (url.pathname.match(/\.(png|jpg|jpeg|svg|gif|webp|ico)$/)) {
// Let Service Worker control static image caching
setHeader(event, 'Cache-Control', 'public, max-age=0, must-revalidate')
}
// ==========================================
// API Endpoints
// ==========================================
if (url.pathname.startsWith('/api/')) {
// Trust proxy headers
const realIP = getHeader(event, 'x-real-ip') || getHeader(event, 'x-forwarded-for')
const proto = getHeader(event, 'x-forwarded-proto') || 'http'
const host = getHeader(event, 'host')
// Set CORS headers for PWA offline support
setHeader(event, 'Access-Control-Allow-Origin', '*')
setHeader(event, 'Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
setHeader(event, 'Access-Control-Allow-Headers', 'Content-Type, Authorization, Range')
// Handle music file requests
if (url.pathname.startsWith('/api/music/')) {
// Music files: cache by Service Worker, not browser
setHeader(event, 'Cache-Control', 'public, max-age=0, must-revalidate')
setHeader(event, 'Accept-Ranges', 'bytes')
// Security headers
setHeader(event, 'X-Content-Type-Options', 'nosniff')
if (process.env.NODE_ENV === 'production') {
setHeader(event, 'X-Frame-Options', 'DENY')
}
} else {
// Other API endpoints: no cache
setHeader(event, 'Cache-Control', 'no-store, must-revalidate')
}
}
// ==========================================
// Handle OPTIONS preflight requests
// ==========================================
if (event.node.req.method === 'OPTIONS') {
setHeader(event, 'Access-Control-Allow-Origin', '*')
setHeader(event, 'Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
setHeader(event, 'Access-Control-Allow-Headers', 'Content-Type, Authorization, Range')
setResponseStatus(event, 200)
return ''
}
})