@gftdcojp/ksqldb-orm
Version:
ksqldb-orm - Server-Side TypeScript ORM for ksqlDB with enterprise security extensions
303 lines (300 loc) • 12.7 kB
JavaScript
;
/**
* セキュリティ設定とデータベーススキーマの初期化
*/
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