UNPKG

@gftdcojp/gftd-orm

Version:

Enterprise-grade real-time data platform with ksqlDB, inspired by Supabase architecture

424 lines 14.5 kB
"use strict"; /** * Row Level Security (RLS) システム - ksqlDB用アクセス制御 */ Object.defineProperty(exports, "__esModule", { value: true }); exports.rls = exports.RLSManager = exports.PolicyType = void 0; exports.rlsMiddleware = rlsMiddleware; const logger_1 = require("./utils/logger"); const audit_log_1 = require("./audit-log"); /** * RLSポリシーのタイプ */ var PolicyType; (function (PolicyType) { PolicyType["SELECT"] = "SELECT"; PolicyType["INSERT"] = "INSERT"; PolicyType["UPDATE"] = "UPDATE"; PolicyType["DELETE"] = "DELETE"; PolicyType["ALL"] = "ALL"; })(PolicyType || (exports.PolicyType = PolicyType = {})); /** * RLSマネージャー */ class RLSManager { constructor() { this.policies = new Map(); this.tableRLSEnabled = new Set(); this.initializeDefaultPolicies(); } /** * シングルトンインスタンスを取得 */ static getInstance() { if (!RLSManager.instance) { RLSManager.instance = new RLSManager(); } return RLSManager.instance; } /** * デフォルトポリシーを初期化 */ initializeDefaultPolicies() { // 認証ユーザーの基本ポリシー this.createPolicy({ id: 'authenticated-users-read', name: 'Authenticated Users Read', tableName: '*', policyType: PolicyType.SELECT, roles: ['authenticated'], condition: 'true', // 認証されたユーザーは読み取り可能 description: 'Allow authenticated users to read data', isActive: true, }); // 匿名ユーザーの制限ポリシー this.createPolicy({ id: 'anon-users-read-public', name: 'Anonymous Users Read Public', tableName: '*', policyType: PolicyType.SELECT, roles: ['anon'], condition: 'visibility = \'public\'', description: 'Allow anonymous users to read only public data', isActive: true, }); // 自分のデータのみアクセス可能なポリシー this.createPolicy({ id: 'user-owns-data', name: 'User Owns Data', tableName: 'user_data', policyType: PolicyType.ALL, roles: ['authenticated'], condition: 'user_id = auth.user_id()', description: 'Users can only access their own data', isActive: true, }); // サービスロールの全権限ポリシー this.createPolicy({ id: 'service-role-full-access', name: 'Service Role Full Access', tableName: '*', policyType: PolicyType.ALL, roles: ['service_role'], condition: 'true', description: 'Service role has full access to all data', isActive: true, }); logger_1.log.info('Default RLS policies initialized'); } /** * ポリシーを作成 */ createPolicy(policy) { const newPolicy = { ...policy, createdAt: new Date(), updatedAt: new Date(), }; this.policies.set(policy.id, newPolicy); logger_1.log.info(`RLS policy created: ${policy.name} for table ${policy.tableName}`); audit_log_1.AuditLogManager.log({ level: audit_log_1.AuditLogLevel.INFO, eventType: audit_log_1.AuditEventType.ADMIN_POLICY_CHANGE, result: 'SUCCESS', message: `RLS policy created: ${policy.name}`, details: { policyId: policy.id, tableName: policy.tableName }, }); } /** * ポリシーを更新 */ updatePolicy(id, updates) { const policy = this.policies.get(id); if (!policy) { return false; } Object.assign(policy, updates, { updatedAt: new Date() }); logger_1.log.info(`RLS policy updated: ${policy.name}`); audit_log_1.AuditLogManager.log({ level: audit_log_1.AuditLogLevel.INFO, eventType: audit_log_1.AuditEventType.ADMIN_POLICY_CHANGE, result: 'SUCCESS', message: `RLS policy updated: ${policy.name}`, details: { policyId: id, updates }, }); return true; } /** * ポリシーを削除 */ deletePolicy(id) { const policy = this.policies.get(id); if (!policy) { return false; } this.policies.delete(id); logger_1.log.info(`RLS policy deleted: ${policy.name}`); audit_log_1.AuditLogManager.log({ level: audit_log_1.AuditLogLevel.INFO, eventType: audit_log_1.AuditEventType.ADMIN_POLICY_CHANGE, result: 'SUCCESS', message: `RLS policy deleted: ${policy.name}`, details: { policyId: id }, }); return true; } /** * テーブルのRLSを有効化 */ enableRLS(tableName) { this.tableRLSEnabled.add(tableName.toUpperCase()); logger_1.log.info(`RLS enabled for table: ${tableName}`); } /** * テーブルのRLSを無効化 */ disableRLS(tableName) { this.tableRLSEnabled.delete(tableName.toUpperCase()); logger_1.log.info(`RLS disabled for table: ${tableName}`); } /** * テーブルのRLS状態を確認 */ isRLSEnabled(tableName) { return this.tableRLSEnabled.has(tableName.toUpperCase()); } /** * クエリを分析 */ analyzeQuery(sql) { const upperSQL = sql.toUpperCase(); // 操作タイプを判定 let operation = 'OTHER'; if (upperSQL.includes('SELECT')) operation = 'SELECT'; else if (upperSQL.includes('INSERT')) operation = 'INSERT'; else if (upperSQL.includes('UPDATE')) operation = 'UPDATE'; else if (upperSQL.includes('DELETE')) operation = 'DELETE'; else if (upperSQL.includes('CREATE')) operation = 'CREATE'; else if (upperSQL.includes('DROP')) operation = 'DROP'; // テーブル名を抽出(簡易的な実装) const tableMatches = sql.match(/(?:FROM|INTO|UPDATE|JOIN)\s+([A-Za-z_][A-Za-z0-9_]*)/gi); const tables = tableMatches ? tableMatches.map(match => match.replace(/(?:FROM|INTO|UPDATE|JOIN)\s+/i, '').trim()) : []; // 条件を抽出 const whereMatch = sql.match(/WHERE\s+(.+?)(?:\s+GROUP\s+BY|\s+ORDER\s+BY|\s+LIMIT|$)/i); const conditions = whereMatch ? [whereMatch[1]] : []; return { operation, tables, columns: [], // 詳細な列解析は省略 conditions, isModifying: ['INSERT', 'UPDATE', 'DELETE'].includes(operation), }; } /** * 適用可能なポリシーを取得 */ getApplicablePolicies(tableName, operation, userRole) { const policies = []; for (const policy of this.policies.values()) { if (!policy.isActive) continue; // テーブル名のマッチング if (policy.tableName !== '*' && policy.tableName.toUpperCase() !== tableName.toUpperCase()) { continue; } // 操作タイプのマッチング if (policy.policyType !== PolicyType.ALL && policy.policyType !== operation) { continue; } // ロールのマッチング if (!policy.roles.includes(userRole)) { continue; } policies.push(policy); } return policies; } /** * 条件をクエリに適用 */ applyConditionsToQuery(sql, conditions) { if (conditions.length === 0) { return sql; } const combinedConditions = conditions.join(' AND '); // 既存のWHERE句がある場合 if (sql.toUpperCase().includes('WHERE')) { return sql.replace(/WHERE\s+/i, `WHERE (${combinedConditions}) AND `); } else { // WHERE句がない場合は追加 const insertIndex = sql.search(/\s+(GROUP\s+BY|ORDER\s+BY|LIMIT|$)/i); if (insertIndex !== -1) { return sql.slice(0, insertIndex) + ` WHERE ${combinedConditions}` + sql.slice(insertIndex); } else { return sql + ` WHERE ${combinedConditions}`; } } } /** * ユーザーコンテキストを条件に適用 */ applyUserContext(condition, user) { return condition .replace(/auth\.user_id\(\)/g, `'${user.sub}'`) .replace(/auth\.role\(\)/g, `'${user.role}'`) .replace(/auth\.tenant_id\(\)/g, `'${user.tenant_id || 'default'}'`) .replace(/auth\.email\(\)/g, `'${user.email || ''}'`); } /** * クエリにRLSを適用 */ applyRLS(sql, user) { const analysis = this.analyzeQuery(sql); // DDL操作は制限しない(管理者のみ実行可能) if (['CREATE', 'DROP'].includes(analysis.operation)) { if (user.role !== 'service_role') { throw new Error('DDL operations require service role'); } return sql; } // RLS適用条件を収集 const rlsConditions = []; for (const tableName of analysis.tables) { // テーブルのRLSが有効でない場合はスキップ if (!this.isRLSEnabled(tableName)) { continue; } const applicablePolicies = this.getApplicablePolicies(tableName, analysis.operation, user.role); if (applicablePolicies.length === 0) { // 適用可能なポリシーがない場合は拒否 throw new Error(`Access denied to table ${tableName}: No applicable RLS policies`); } // ポリシー条件を適用 for (const policy of applicablePolicies) { const condition = this.applyUserContext(policy.condition, user); if (condition !== 'true') { rlsConditions.push(condition); } } } // 条件をクエリに適用 const modifiedSQL = this.applyConditionsToQuery(sql, rlsConditions); // ログ記録 if (modifiedSQL !== sql) { logger_1.log.info(`RLS applied to query for user ${user.sub}`); audit_log_1.AuditLogManager.log({ level: audit_log_1.AuditLogLevel.INFO, eventType: audit_log_1.AuditEventType.DATA_READ, userId: user.sub, tenantId: user.tenant_id, result: 'SUCCESS', message: 'RLS policies applied to query', details: { originalSQL: sql, modifiedSQL: modifiedSQL, appliedConditions: rlsConditions, }, }); } return modifiedSQL; } /** * ポリシー一覧を取得 */ listPolicies() { return Array.from(this.policies.values()); } /** * テーブル固有のポリシーを取得 */ getTablePolicies(tableName) { return Array.from(this.policies.values()).filter(policy => policy.tableName === tableName || policy.tableName === '*'); } /** * ポリシーを取得 */ getPolicy(id) { return this.policies.get(id); } /** * RLS統計情報を取得 */ getStatistics() { const policies = Array.from(this.policies.values()); const policiesByType = {}; for (const policy of policies) { const type = policy.policyType; policiesByType[type] = (policiesByType[type] || 0) + 1; } return { totalPolicies: policies.length, activePolicies: policies.filter(p => p.isActive).length, enabledTables: this.tableRLSEnabled.size, policiesByType, }; } } exports.RLSManager = RLSManager; /** * Express.js ミドルウェア: RLS適用 */ function rlsMiddleware() { const rlsManager = RLSManager.getInstance(); return (req, res, next) => { const originalSend = res.send; // レスポンスを interceptして RLS を適用 res.send = function (body) { if (req.user && req.body && req.body.sql) { try { const modifiedSQL = rlsManager.applyRLS(req.body.sql, req.user); req.body.sql = modifiedSQL; } catch (error) { logger_1.log.error(`RLS application failed: ${error}`); return res.status(403).json({ error: 'Forbidden', message: 'Access denied by RLS policy', }); } } return originalSend.call(this, body); }; next(); }; } /** * RLSヘルパー関数 */ exports.rls = { /** * マネージャーインスタンスを取得 */ manager: () => RLSManager.getInstance(), /** * ポリシーを作成 */ createPolicy: (policy) => { const manager = RLSManager.getInstance(); manager.createPolicy(policy); }, /** * テーブルのRLSを有効化 */ enableTableRLS: (tableName) => { const manager = RLSManager.getInstance(); manager.enableRLS(tableName); }, /** * テーブルのRLSを無効化 */ disableTableRLS: (tableName) => { const manager = RLSManager.getInstance(); manager.disableRLS(tableName); }, /** * クエリにRLSを適用 */ applyToQuery: (sql, user) => { const manager = RLSManager.getInstance(); return manager.applyRLS(sql, user); }, /** * ポリシー一覧を取得 */ listPolicies: () => { const manager = RLSManager.getInstance(); return manager.listPolicies(); }, /** * 統計情報を取得 */ getStatistics: () => { const manager = RLSManager.getInstance(); return manager.getStatistics(); }, }; //# sourceMappingURL=row-level-security.js.map