UNPKG

@gftdcojp/ksqldb-orm

Version:

ksqldb-orm - Server-Side TypeScript ORM for ksqlDB with enterprise security extensions

216 lines 8.27 kB
"use strict"; /** * CORS設定のセキュリティ強化ユーティリティ */ Object.defineProperty(exports, "__esModule", { value: true }); exports.getSecureCorsOptions = getSecureCorsOptions; exports.createCorsMiddleware = createCorsMiddleware; exports.logCorsConfiguration = logCorsConfiguration; const env_1 = require("./env"); const logger_1 = require("./logger"); /** * セキュアなCORS設定を生成 * @param customOptions カスタムオプション */ function getSecureCorsOptions(customOptions = {}) { // 環境変数からCORS許可オリジンを取得 const corsOriginsEnv = (0, env_1.getEnvVar)('GFTD_CORS_ORIGINS'); let allowedOrigins = []; if (corsOriginsEnv) { allowedOrigins = corsOriginsEnv .split(',') .map(origin => origin.trim()) .filter(origin => origin.length > 0); } else { // デフォルトは開発環境でのみlocalhostを許可 if (!(0, env_1.isProduction)()) { allowedOrigins = [ 'http://localhost:3000', 'http://localhost:3001', 'http://localhost:8080', 'https://localhost:3000', 'https://localhost:3001', 'https://localhost:8080' ]; } else { // 本番環境ではオリジンを明示的に設定する必要がある logger_1.log.error('CRITICAL SECURITY: GFTD_CORS_ORIGINS must be set in production environment'); allowedOrigins = []; // デフォルトで全て拒否 } } // 本番環境でワイルドカード使用をチェック if ((0, env_1.isProduction)() && allowedOrigins.includes('*')) { logger_1.log.error('CRITICAL SECURITY: Wildcard (*) in CORS origins is not allowed in production'); allowedOrigins = allowedOrigins.filter(origin => origin !== '*'); } // HTTPSを推奨 if ((0, env_1.isProduction)()) { const insecureOrigins = allowedOrigins.filter(origin => origin.startsWith('http://') && !origin.includes('localhost')); if (insecureOrigins.length > 0) { logger_1.log.warn('SECURITY WARNING: HTTP origins detected in production CORS settings', { insecureOrigins }); } } const defaultOptions = { origins: allowedOrigins, allowCredentials: true, // JWT認証用 maxAge: 86400, // 24時間 allowedHeaders: [ 'Content-Type', 'Authorization', 'X-Requested-With', 'Accept', 'Origin', 'Cache-Control', 'X-File-Name' ], allowedMethods: [ 'GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'HEAD' ], exposedHeaders: [ 'Content-Length', 'Content-Type', 'X-Total-Count' ], preflightContinue: false, optionsSuccessStatus: 204 }; // カスタムオプションでデフォルトを上書き const finalOptions = { ...defaultOptions, ...customOptions }; // セキュリティ検証 validateCorsOptions(finalOptions); return finalOptions; } /** * CORS設定の検証 * @param options CORS設定オプション */ function validateCorsOptions(options) { const issues = []; // オリジンチェック if (!options.origins || options.origins.length === 0) { issues.push('No allowed origins configured - this will block all cross-origin requests'); } else if (options.origins.includes('*')) { if ((0, env_1.isProduction)()) { issues.push('CRITICAL: Wildcard origin (*) is dangerous in production'); } else { logger_1.log.warn('WARNING: Wildcard origin (*) should only be used in development'); } } // 認証情報許可チェック if (options.allowCredentials && options.origins?.includes('*')) { issues.push('CRITICAL: Cannot use allowCredentials: true with wildcard origin (*)'); } // HTTPSチェック(本番環境) if ((0, env_1.isProduction)() && options.origins) { const httpOrigins = options.origins.filter(origin => origin.startsWith('http://') && !origin.includes('localhost')); if (httpOrigins.length > 0) { issues.push(`WARNING: HTTP origins in production: ${httpOrigins.join(', ')}`); } } // ヘッダーチェック if (options.allowedHeaders?.includes('*')) { issues.push('WARNING: Wildcard allowed headers (*) may be too permissive'); } // メソッドチェック const dangerousMethods = ['TRACE', 'CONNECT']; const allowedDangerous = options.allowedMethods?.filter(method => dangerousMethods.includes(method.toUpperCase())); if (allowedDangerous && allowedDangerous.length > 0) { issues.push(`WARNING: Potentially dangerous HTTP methods allowed: ${allowedDangerous.join(', ')}`); } // ログ出力 if (issues.length > 0) { issues.forEach(issue => { if (issue.startsWith('CRITICAL')) { logger_1.log.error(issue); } else { logger_1.log.warn(issue); } }); } } /** * Express.js用のCORSミドルウェア関数を生成 * @param customOptions カスタムCORSオプション */ function createCorsMiddleware(customOptions = {}) { const corsOptions = getSecureCorsOptions(customOptions); return (req, res, next) => { const origin = req.headers.origin; // Origin検証 if (origin && corsOptions.origins) { if (corsOptions.origins.includes('*') || corsOptions.origins.includes(origin)) { res.header('Access-Control-Allow-Origin', origin); } else { // 許可されていないオリジンからのリクエストをログ記録 logger_1.log.warn('CORS: Blocked request from unauthorized origin', { origin, userAgent: req.headers['user-agent'], ip: req.ip }); return res.status(403).json({ error: 'CORS policy violation', message: 'Origin not allowed' }); } } // 認証情報許可 if (corsOptions.allowCredentials) { res.header('Access-Control-Allow-Credentials', 'true'); } // プリフライトリクエスト処理 if (req.method === 'OPTIONS') { if (corsOptions.allowedMethods) { res.header('Access-Control-Allow-Methods', corsOptions.allowedMethods.join(', ')); } if (corsOptions.allowedHeaders) { res.header('Access-Control-Allow-Headers', corsOptions.allowedHeaders.join(', ')); } if (corsOptions.maxAge) { res.header('Access-Control-Max-Age', corsOptions.maxAge.toString()); } return res.status(corsOptions.optionsSuccessStatus || 204).end(); } // 実際のリクエストでの追加ヘッダー if (corsOptions.exposedHeaders) { res.header('Access-Control-Expose-Headers', corsOptions.exposedHeaders.join(', ')); } next(); }; } /** * CORS設定のログ出力 */ function logCorsConfiguration() { const corsOptions = getSecureCorsOptions(); logger_1.log.info('CORS configuration initialized', { allowedOrigins: corsOptions.origins?.length || 0, allowCredentials: corsOptions.allowCredentials, environment: (0, env_1.isProduction)() ? 'production' : 'development' }); if (corsOptions.origins) { // セキュリティ上の理由で、本番環境ではオリジンの詳細をログに出力しない if (!(0, env_1.isProduction)()) { logger_1.log.debug('CORS allowed origins', { origins: corsOptions.origins }); } else { logger_1.log.info('CORS allowed origins configured', { count: corsOptions.origins.length }); } } } //# sourceMappingURL=cors.js.map