UNPKG

@gftdcojp/ksqldb-orm

Version:

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

303 lines (300 loc) 12.7 kB
"use strict"; /** * セキュリティ設定とデータベーススキーマの初期化 */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.REDIS_REFRESH_TOKEN_CONFIG = exports.REFRESH_TOKEN_TABLE_SQL = void 0; exports.initializeSecurity = initializeSecurity; exports.createRefreshTokenTable = createRefreshTokenTable; exports.validateSecurityConfiguration = validateSecurityConfiguration; exports.securityHealthCheck = securityHealthCheck; exports.displaySecurityStatus = displaySecurityStatus; const jwt_auth_1 = require("./jwt-auth"); const env_1 = require("./utils/env"); const cors_1 = require("./utils/cors"); const logger_1 = require("./utils/logger"); /** * PostgreSQL用リフレッシュトークンテーブル作成SQL */ exports.REFRESH_TOKEN_TABLE_SQL = ` -- リフレッシュトークンテーブル CREATE TABLE IF NOT EXISTS refresh_tokens ( token_id VARCHAR(255) PRIMARY KEY, user_id VARCHAR(255) NOT NULL, expires_at TIMESTAMP WITH TIME ZONE NOT NULL, created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, last_used_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, -- インデックス INDEX idx_refresh_tokens_user_id (user_id), INDEX idx_refresh_tokens_expires_at (expires_at), INDEX idx_refresh_tokens_created_at (created_at) ); -- 期限切れトークンの自動削除用関数(オプション) CREATE OR REPLACE FUNCTION cleanup_expired_refresh_tokens() RETURNS INTEGER AS $$ DECLARE deleted_count INTEGER; BEGIN DELETE FROM refresh_tokens WHERE expires_at <= NOW(); GET DIAGNOSTICS deleted_count = ROW_COUNT; RETURN deleted_count; END; $$ LANGUAGE plpgsql; -- 期限切れトークンクリーンアップを毎日実行するcronジョブ設定例 -- SELECT cron.schedule('cleanup-refresh-tokens', '0 2 * * *', 'SELECT cleanup_expired_refresh_tokens();'); `; /** * Redis用リフレッシュトークンスキーマ設定 */ exports.REDIS_REFRESH_TOKEN_CONFIG = { keyPrefix: 'gftd:refresh_token:', defaultTTL: 7 * 24 * 60 * 60, // 7日間(秒) cleanupInterval: 60 * 60 * 1000, // 1時間(ミリ秒) }; /** * セキュリティ設定の初期化 */ async function initializeSecurity() { logger_1.log.info('🔒 Initializing security configuration...'); try { // 1. 環境変数の検証 logger_1.log.info('📋 Validating environment variables...'); const envValid = (0, env_1.validateAndLogEnvironment)(); if (!envValid) { throw new Error('Environment validation failed - security initialization aborted'); } // 2. CORS設定の初期化 logger_1.log.info('🌐 Initializing CORS configuration...'); (0, cors_1.logCorsConfiguration)(); // 3. JWT認証マネージャーの初期化 logger_1.log.info('🔑 Initializing JWT authentication...'); const authManager = jwt_auth_1.JwtAuthManager.getInstance(); // 4. リフレッシュトークンストレージの設定 await initializeRefreshTokenStorage(authManager); // 5. セキュリティヘッダーの設定確認 logger_1.log.info('🛡️ Security headers configuration ready'); logger_1.log.success('✅ Security initialization completed successfully'); } catch (error) { logger_1.log.failure('❌ Security initialization failed', error); throw error; } } /** * リフレッシュトークンストレージの初期化 */ async function initializeRefreshTokenStorage(authManager) { const redisUrl = (0, env_1.getEnvVar)('GFTD_REDIS_URL'); const postgresUrl = (0, env_1.getEnvVar)('GFTD_POSTGRES_URL') || (0, env_1.getEnvVar)('DATABASE_URL'); // テスト環境ではRedisを無効化 if (redisUrl && (0, env_1.getEnvVar)('NODE_ENV') !== 'test') { logger_1.log.info('🗄️ Configuring Redis refresh token storage...'); try { // Redis クライアントの動的インポート(オプショナル依存関係) const redisModule = await Promise.resolve(`${'ioredis'}`).then(s => __importStar(require(s))).catch(() => { throw new Error('ioredis module not found. Install with: pnpm add ioredis'); }); const Redis = redisModule.default || redisModule; const redis = new Redis(redisUrl); // 接続テスト await redis.ping(); authManager.setRedisStorage(redis, exports.REDIS_REFRESH_TOKEN_CONFIG.keyPrefix); logger_1.log.success('✅ Redis refresh token storage configured'); } catch (error) { logger_1.log.error('❌ Failed to configure Redis storage, falling back to memory storage', error); } } else if (postgresUrl && (0, env_1.getEnvVar)('NODE_ENV') !== 'test') { logger_1.log.info('🐘 Configuring PostgreSQL refresh token storage...'); try { // PostgreSQL クライアントの動的インポート(オプショナル依存関係) const pgModule = await Promise.resolve(`${'pg'}`).then(s => __importStar(require(s))).catch(() => { throw new Error('pg module not found. Install with: pnpm add pg @types/pg'); }); const { Pool } = pgModule; const pool = new Pool({ connectionString: postgresUrl, ssl: postgresUrl.includes('sslmode=require') || (0, env_1.getEnvVar)('NODE_ENV') === 'production' ? { rejectUnauthorized: false } : false }); // 接続テスト const client = await pool.connect(); client.release(); authManager.setPostgreSQLStorage(pool); logger_1.log.success('✅ PostgreSQL refresh token storage configured'); // テーブル作成の案内 logger_1.log.info('📄 To create the refresh_tokens table, run the SQL provided in REFRESH_TOKEN_TABLE_SQL'); } catch (error) { logger_1.log.error('❌ Failed to configure PostgreSQL storage, falling back to memory storage', error); } } else { logger_1.log.warn('⚠️ No persistent storage configured for refresh tokens'); logger_1.log.warn('💡 Set GFTD_REDIS_URL or GFTD_POSTGRES_URL for production use'); } } /** * PostgreSQLにリフレッシュトークンテーブルを作成 * @param pool PostgreSQL接続プール */ async function createRefreshTokenTable(pool) { try { logger_1.log.info('🏗️ Creating refresh_tokens table...'); const client = await pool.connect(); try { await client.query(exports.REFRESH_TOKEN_TABLE_SQL); logger_1.log.success('✅ refresh_tokens table created successfully'); } finally { client.release(); } } catch (error) { logger_1.log.error('❌ Failed to create refresh_tokens table', error); throw error; } } /** * セキュリティ設定の検証 */ function validateSecurityConfiguration() { const issues = []; const recommendations = []; // JWT秘密鍵チェック const jwtSecret = (0, env_1.getEnvVar)('GFTD_JWT_SECRET'); if (!jwtSecret) { issues.push('CRITICAL: GFTD_JWT_SECRET environment variable is not set'); } else if (jwtSecret.length < 32) { issues.push('CRITICAL: GFTD_JWT_SECRET is too short (minimum 32 characters)'); } else if (jwtSecret.length < 64) { recommendations.push('RECOMMENDATION: Use a longer JWT secret (64+ characters) for better security'); } // 本番環境での追加チェック if ((0, env_1.getEnvVar)('NODE_ENV') === 'production') { // データベースURLのHTTPSチェック const dbUrl = (0, env_1.getEnvVar)('GFTD_KSQLDB_URL'); if (!dbUrl) { issues.push('HIGH: GFTD_KSQLDB_URL environment variable is not set'); } else if (!dbUrl.startsWith('https://')) { issues.push('HIGH: GFTD_KSQLDB_URL should use HTTPS in production'); } // CORS設定チェック const corsOrigins = (0, env_1.getEnvVar)('GFTD_CORS_ORIGINS'); if (!corsOrigins) { issues.push('HIGH: GFTD_CORS_ORIGINS should be configured in production'); } } // リフレッシュトークンストレージチェック const hasRedis = !!(0, env_1.getEnvVar)('GFTD_REDIS_URL'); const hasPostgres = !!((0, env_1.getEnvVar)('GFTD_POSTGRES_URL') || (0, env_1.getEnvVar)('DATABASE_URL')); if ((0, env_1.getEnvVar)('NODE_ENV') === 'production' && !hasRedis && !hasPostgres) { issues.push('CRITICAL: No persistent storage configured for refresh tokens in production'); recommendations.push('RECOMMENDATION: Configure Redis (GFTD_REDIS_URL) or PostgreSQL for refresh token storage'); } return { valid: issues.length === 0, issues, recommendations }; } /** * セキュリティ設定のヘルスチェック */ async function securityHealthCheck() { const details = {}; let status = 'healthy'; try { // JWT認証マネージャーの状態チェック const authManager = jwt_auth_1.JwtAuthManager.getInstance(); details.jwtAuth = 'configured'; // 環境変数検証 const envValidation = validateSecurityConfiguration(); details.environment = { valid: envValidation.valid, issueCount: envValidation.issues.length, recommendationCount: envValidation.recommendations.length }; if (envValidation.issues.length > 0) { status = envValidation.issues.some(issue => issue.startsWith('CRITICAL')) ? 'critical' : 'warning'; } // リフレッシュトークンストレージの状態チェック try { await authManager.cleanupExpiredTokens(); details.refreshTokenStorage = 'operational'; } catch (error) { details.refreshTokenStorage = 'error'; details.refreshTokenError = error instanceof Error ? error.message : 'Unknown error'; status = 'warning'; } details.timestamp = new Date().toISOString(); } catch (error) { status = 'critical'; details.error = error instanceof Error ? error.message : 'Unknown error'; } return { status, details }; } /** * セキュリティ設定の表示 */ function displaySecurityStatus() { const validation = validateSecurityConfiguration(); console.log('\n🔒 Security Configuration Status\n'); console.log('='.repeat(50)); if (validation.valid) { console.log('✅ Status: SECURE'); } else { console.log('❌ Status: ISSUES DETECTED'); } if (validation.issues.length > 0) { console.log('\n⚠️ Security Issues:'); validation.issues.forEach(issue => console.log(` • ${issue}`)); } if (validation.recommendations.length > 0) { console.log('\n💡 Recommendations:'); validation.recommendations.forEach(rec => console.log(` • ${rec}`)); } console.log('\n' + '='.repeat(50) + '\n'); } //# sourceMappingURL=security.js.map