@@ -3,7 +3,7 @@ const cors = require('cors');
33const bcrypt = require ( 'bcrypt' ) ;
44const https = require ( 'https' ) ;
55const fs = require ( 'fs' ) ;
6- const rateLimit = require ( 'express-rate-limit' ) ; // 🔒 NUEVO: Rate limiting
6+ const rateLimit = require ( 'express-rate-limit' ) ;
77const config = require ( './config' ) ;
88const db = require ( './db' ) ;
99const path = require ( 'path' ) ;
@@ -154,7 +154,6 @@ const Logger = {
154154 }
155155 } ,
156156
157- // 🔒 NUEVO: Logger para rate limiting
158157 rateLimitExceeded : ( req , limitType ) => {
159158 const clientIP = req . headers [ 'x-forwarded-for' ] ||
160159 req . headers [ 'x-real-ip' ] ||
@@ -351,21 +350,99 @@ app.use(cors({
351350// 🔒 Rate limiter general ANTES de otros middlewares
352351app . use ( generalLimiter ) ;
353352
354- // ===== MIDDLEWARE ADICIONAL =====
353+ // ===== MIDDLEWARE ADICIONAL SEGURO =====
355354app . use ( ( req , res , next ) => {
356- res . header ( 'Access-Control-Allow-Origin' , req . headers . origin || '*' ) ;
357- res . header ( 'Access-Control-Allow-Credentials' , 'true' ) ;
355+ // 🔒 CORS SEGURO: Solo orígenes whitelisteados con credenciales
356+ const requestOrigin = req . headers . origin ;
357+
358+ // Verificar si el origen está en la whitelist
359+ const isOriginAllowed = requestOrigin && allowedOrigins . some ( allowedOrigin => {
360+ if ( typeof allowedOrigin === 'string' ) {
361+ return requestOrigin === allowedOrigin ;
362+ } else if ( allowedOrigin instanceof RegExp ) {
363+ return allowedOrigin . test ( requestOrigin ) ;
364+ }
365+ return false ;
366+ } ) ;
367+
368+ // Solo establecer headers CORS para orígenes permitidos
369+ if ( isOriginAllowed ) {
370+ // ✅ SEGURO: Origin específico validado
371+ res . header ( 'Access-Control-Allow-Origin' , requestOrigin ) ;
372+ res . header ( 'Access-Control-Allow-Credentials' , 'true' ) ;
373+ } else if ( isDevelopment && ! requestOrigin ) {
374+ // Solo en desarrollo: permitir requests sin origin (Postman, curl, etc.)
375+ res . header ( 'Access-Control-Allow-Origin' , '*' ) ;
376+ // ✅ NO permitir credenciales con origen wildcard
377+ res . header ( 'Access-Control-Allow-Credentials' , 'false' ) ;
378+ } else {
379+ // ❌ Origen no permitido: no establecer headers CORS
380+ if ( requestOrigin ) {
381+ Logger . security ( 'CORS: Origen no permitido intentando acceso con credenciales' , {
382+ origin : requestOrigin ,
383+ ip : req . headers [ 'x-forwarded-for' ] || req . connection . remoteAddress ,
384+ userAgent : req . headers [ 'user-agent' ]
385+ } ) ;
386+ }
387+
388+ // No establecer headers CORS para orígenes no permitidos
389+ // El navegador bloqueará la request
390+ }
391+
392+ // Headers comunes que son seguros
358393 res . header ( 'Access-Control-Allow-Methods' , 'GET,PUT,POST,DELETE,OPTIONS,PATCH' ) ;
359394 res . header ( 'Access-Control-Allow-Headers' , 'Content-Type, Authorization, Content-Length, X-Requested-With, Accept, Origin, ngrok-skip-browser-warning' ) ;
395+ res . header ( 'Access-Control-Max-Age' , '3600' ) ; // Cache preflight por 1 hora
360396
397+ // Manejar preflight requests
361398 if ( req . method === 'OPTIONS' ) {
362- res . status ( 204 ) . send ( ) ;
399+ if ( isOriginAllowed || ( isDevelopment && ! requestOrigin ) ) {
400+ res . status ( 204 ) . send ( ) ;
401+ } else {
402+ Logger . security ( 'CORS: Preflight bloqueado para origen no permitido' , requestOrigin ) ;
403+ res . status ( 403 ) . json ( {
404+ success : false ,
405+ message : 'Origen no permitido' ,
406+ error : 'CORS_ORIGIN_NOT_ALLOWED'
407+ } ) ;
408+ }
363409 return ;
364410 }
365411
366412 next ( ) ;
367413} ) ;
368414
415+ // 🔒 MIDDLEWARE ADICIONAL DE SEGURIDAD CORS
416+ app . use ( ( req , res , next ) => {
417+ // Verificar que no se esté intentando un ataque CORS
418+ const origin = req . headers . origin ;
419+ const referer = req . headers . referer ;
420+
421+ // Detectar intentos de ataque CORS
422+ if ( origin === 'null' && req . headers [ 'access-control-request-method' ] ) {
423+ Logger . security ( 'CORS: Intento de ataque con origin null detectado' , {
424+ ip : req . headers [ 'x-forwarded-for' ] || req . connection . remoteAddress ,
425+ userAgent : req . headers [ 'user-agent' ] ,
426+ referer : referer
427+ } ) ;
428+ }
429+
430+ // Logging adicional en producción para monitoreo
431+ if ( isProduction && origin && ! allowedOrigins . some ( allowed => {
432+ if ( typeof allowed === 'string' ) return origin === allowed ;
433+ if ( allowed instanceof RegExp ) return allowed . test ( origin ) ;
434+ return false ;
435+ } ) ) {
436+ Logger . security ( 'CORS: Intento de acceso desde origen no autorizado en producción' , {
437+ origin : origin ,
438+ path : req . path ,
439+ method : req . method
440+ } ) ;
441+ }
442+
443+ next ( ) ;
444+ } ) ;
445+
369446app . use ( express . json ( { limit : '50mb' } ) ) ;
370447app . use ( express . urlencoded ( { extended : true , limit : '50mb' } ) ) ;
371448
@@ -379,7 +456,7 @@ app.use((req, res, next) => {
379456
380457 Logger . api ( req . method , req . path , req . headers . origin ) ;
381458
382- // 🔒 NUEVO: Log rate limit info si está disponible
459+ // 🔒 Log rate limit info si está disponible
383460 if ( req . rateLimit && ! isProduction ) {
384461 console . debug ( `Rate Limit Info: ${ req . rateLimit . remaining } /${ req . rateLimit . limit } remaining for ${ clientIP } ` ) ;
385462 }
@@ -506,7 +583,6 @@ app.get('/', (req, res) => {
506583 allowedOrigins : allowedOrigins . length ,
507584 currentOrigin : req . headers . origin || 'No origin'
508585 } ,
509- // 🔒 NUEVO: Rate limit info
510586 rateLimitInfo : req . rateLimit ? {
511587 remaining : req . rateLimit . remaining ,
512588 total : req . rateLimit . limit ,
@@ -746,7 +822,7 @@ function formatUptime(seconds) {
746822
747823// ===== MANEJO DE ERRORES =====
748824app . use ( ( err , req , res , next ) => {
749- // 🔒 NUEVO: Log específico si es error de rate limiting
825+ // 🔒 Log específico si es error de rate limiting
750826 if ( err . status === 429 || err . type === 'rate_limit' ) {
751827 Logger . rateLimitExceeded ( req , 'MIDDLEWARE_ERROR' ) ;
752828 } else {
@@ -796,18 +872,18 @@ const server = https.createServer(sslOptions, app).listen(config.port, '0.0.0.0'
796872 Logger . startup ( `Entorno: ${ config . environment } ` ) ;
797873 Logger . startup ( `Versión: ${ config . version } ` ) ;
798874
799- // 🔒 NUEVO: Log de configuración de rate limiting
875+ // 🔒 Log de configuración de seguridad
800876 Logger . startup ( `Rate Limiting: ${ isProduction ? 'STRICT' : 'PERMISSIVE' } mode` ) ;
801877 Logger . startup ( `Auth Rate Limit: ${ isProduction ? '5' : '20' } attempts per 15min` ) ;
802878 Logger . startup ( `General Rate Limit: ${ isProduction ? '100' : '200' } requests per 15min` ) ;
803879 Logger . startup ( `DB API Rate Limit: ${ isProduction ? '50' : '100' } requests per 10min` ) ;
804880 Logger . startup ( `Config Rate Limit: ${ isProduction ? '20' : '50' } requests per 5min` ) ;
881+ Logger . startup ( `CORS Security: Whitelist-only mode with ${ allowedOrigins . length } allowed origins` ) ;
805882
806883 if ( ! isProduction ) {
807884 Logger . startup ( `URL Local HTTPS: https://localhost:${ config . port } ` ) ;
808885 Logger . startup ( `URL Red HTTPS: https://10.0.0.19:${ config . port } ` ) ;
809886 Logger . startup ( `Sistema: ${ config . system . platform } - ${ config . system . hostname } ` ) ;
810- Logger . startup ( `CORS: Configurado para ${ allowedOrigins . length } orígenes` ) ;
811887 }
812888
813889 // Probar conexión a la base de datos
0 commit comments