supa-seed
Version:
A constraint-aware, framework-agnostic database seeding framework with deep PostgreSQL business logic discovery and MakerKit integration support
374 lines (372 loc) • 14.1 kB
JavaScript
;
/**
* RLS Compliance Validator
* Validates and generates detailed RLS compliance reports
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.RLSComplianceValidator = void 0;
const logger_1 = require("../../core/utils/logger");
class RLSComplianceValidator {
constructor(client) {
this.validationHistory = [];
this.client = client;
}
/**
* Generate detailed compliance report
*/
async generateDetailedReport(engineResult, options = {}) {
logger_1.Logger.info('📊 Generating detailed RLS compliance report...');
const defaultOptions = {
includeHistoricalTrends: false,
generateRecommendations: true,
detailedPolicyAnalysis: true,
exportFormat: 'json',
...options
};
try {
const tableReports = await this.generateTableReports(engineResult);
const summary = this.generateSummary(engineResult, tableReports);
const recommendations = defaultOptions.generateRecommendations
? await this.generateRecommendations(engineResult, tableReports)
: [];
const trends = defaultOptions.includeHistoricalTrends
? await this.getHistoricalTrends()
: undefined;
const report = {
timestamp: new Date().toISOString(),
overallScore: this.getComplianceScore(engineResult.overallCompliance),
tableReports,
summary,
recommendations,
trends
};
// Store this result for historical tracking
this.recordComplianceTrend(engineResult);
logger_1.Logger.success('✅ Detailed compliance report generated');
return report;
}
catch (error) {
logger_1.Logger.error(`Failed to generate detailed report: ${error.message}`);
throw error;
}
}
/**
* Generate individual table compliance reports
*/
async generateTableReports(engineResult) {
const tableReports = [];
const tables = await this.getUniqueTables(engineResult.violations);
for (const table of tables) {
const tableViolations = engineResult.violations.filter(v => v.table === table);
const policies = await this.getTablePolicyReports(table);
const score = this.calculateTableScore(tableViolations, policies);
const status = this.determineComplianceStatus(score);
tableReports.push({
table,
score,
status,
policies,
violations: tableViolations,
lastChecked: new Date().toISOString()
});
}
return tableReports;
}
/**
* Get detailed policy reports for a table
*/
async getTablePolicyReports(table) {
try {
const { data: policies } = await this.client
.from('pg_policies')
.select('*')
.eq('tablename', table);
if (!policies)
return [];
return policies.map((policy) => ({
name: policy.policyname,
command: policy.cmd,
roles: policy.roles || [],
expression: policy.qual || '',
isSecure: this.isPolicySecure(policy),
issues: this.identifyPolicyIssues(policy)
}));
}
catch (error) {
logger_1.Logger.warn(`Could not fetch policies for table ${table}: ${error.message}`);
return [];
}
}
/**
* Check if a policy is considered secure
*/
isPolicySecure(policy) {
const expression = policy.qual || '';
// Basic security checks
if (expression.includes('true') || expression === '') {
return false; // Overly permissive
}
if (!expression.includes('auth.')) {
return false; // No authentication check
}
// Could add more sophisticated analysis here
return true;
}
/**
* Identify specific issues with a policy
*/
identifyPolicyIssues(policy) {
const issues = [];
const expression = policy.qual || '';
if (expression.includes('true') || expression === '') {
issues.push('Policy allows unrestricted access');
}
if (!expression.includes('auth.')) {
issues.push('Policy does not enforce authentication');
}
if (expression.includes('OR') && expression.split('OR').length > 3) {
issues.push('Policy has complex OR conditions that may be hard to maintain');
}
if (!expression.includes('user_id') && !expression.includes('owner_id')) {
issues.push('Policy may not enforce proper ownership checks');
}
return issues;
}
/**
* Calculate compliance score for a table
*/
calculateTableScore(violations, policies) {
let score = 100;
// Deduct points for violations
for (const violation of violations) {
switch (violation.severity) {
case 'high':
score -= 30;
break;
case 'medium':
score -= 15;
break;
case 'low':
score -= 5;
break;
}
}
// Deduct points for insecure policies
const insecurePolicies = policies.filter(p => !p.isSecure).length;
score -= insecurePolicies * 10;
// Bonus for having policies
if (policies.length > 0) {
score += 10;
}
return Math.max(0, Math.min(100, score));
}
/**
* Determine compliance status from score
*/
determineComplianceStatus(score) {
if (score >= 80)
return 'compliant';
if (score >= 50)
return 'partial';
return 'non_compliant';
}
/**
* Generate summary from engine result and table reports
*/
generateSummary(engineResult, tableReports) {
const compliantTables = tableReports.filter(t => t.status === 'compliant').length;
const partiallyCompliantTables = tableReports.filter(t => t.status === 'partial').length;
const nonCompliantTables = tableReports.filter(t => t.status === 'non_compliant').length;
const totalPolicies = tableReports.reduce((sum, table) => sum + table.policies.length, 0);
const averageScore = tableReports.reduce((sum, table) => sum + table.score, 0) / tableReports.length;
return {
totalTables: tableReports.length,
compliantTables,
partiallyCompliantTables,
nonCompliantTables,
totalPolicies,
securityScore: averageScore
};
}
/**
* Generate categorized recommendations
*/
async generateRecommendations(engineResult, tableReports) {
const categories = [];
// Security recommendations
const securityRecs = [];
const highViolations = engineResult.violations.filter(v => v.severity === 'high');
if (highViolations.length > 0) {
securityRecs.push(`Address ${highViolations.length} critical security violations immediately`);
securityRecs.push('Enable RLS on all tables handling sensitive data');
}
const nonCompliantTables = tableReports.filter(t => t.status === 'non_compliant');
if (nonCompliantTables.length > 0) {
securityRecs.push(`Create RLS policies for ${nonCompliantTables.length} non-compliant tables`);
}
if (securityRecs.length > 0) {
categories.push({
category: 'security',
priority: 'high',
recommendations: securityRecs,
estimatedEffort: 'medium'
});
}
// Performance recommendations
const performanceRecs = [];
if (engineResult.policiesFound > 20) {
performanceRecs.push('Consider consolidating similar policies to improve query performance');
}
if (performanceRecs.length > 0) {
categories.push({
category: 'performance',
priority: 'medium',
recommendations: performanceRecs,
estimatedEffort: 'low'
});
}
// Maintenance recommendations
const maintenanceRecs = [];
maintenanceRecs.push('Schedule regular RLS compliance reviews');
maintenanceRecs.push('Document policy intentions for future maintenance');
categories.push({
category: 'maintenance',
priority: 'low',
recommendations: maintenanceRecs,
estimatedEffort: 'low'
});
return categories;
}
/**
* Get unique table names from violations
*/
async getUniqueTables(violations) {
const tableSet = new Set(violations.map(v => v.table));
// Also include tables that might not have violations but should be checked
try {
const { data: tables } = await this.client
.from('information_schema.tables')
.select('table_name')
.eq('table_schema', 'public');
if (tables) {
tables.forEach((t) => tableSet.add(t.table_name));
}
}
catch {
// Ignore error, use tables from violations only
}
return Array.from(tableSet);
}
/**
* Record compliance trend for historical tracking
*/
recordComplianceTrend(engineResult) {
const trend = {
date: new Date().toISOString(),
score: this.getComplianceScore(engineResult.overallCompliance),
tablesAnalyzed: engineResult.tablesAnalyzed,
violations: engineResult.violations.length
};
this.validationHistory.push(trend);
// Keep only last 30 entries
if (this.validationHistory.length > 30) {
this.validationHistory = this.validationHistory.slice(-30);
}
}
/**
* Get numeric score from compliance union type
*/
getComplianceScore(compliance) {
return typeof compliance === 'number' ? compliance : compliance.score;
}
/**
* Get historical compliance trends
*/
async getHistoricalTrends() {
return this.validationHistory;
}
/**
* Export report in different formats
*/
async exportReport(report, format = 'json') {
switch (format) {
case 'markdown':
return this.generateMarkdownReport(report);
case 'html':
return this.generateHtmlReport(report);
default:
return JSON.stringify(report, null, 2);
}
}
/**
* Generate markdown format report
*/
generateMarkdownReport(report) {
let markdown = `# RLS Compliance Report\n\n`;
markdown += `**Generated:** ${new Date(report.timestamp).toLocaleString()}\n`;
markdown += `**Overall Score:** ${report.overallScore.toFixed(1)}%\n\n`;
markdown += `## Summary\n\n`;
markdown += `- **Total Tables:** ${report.summary.totalTables}\n`;
markdown += `- **Compliant:** ${report.summary.compliantTables}\n`;
markdown += `- **Partially Compliant:** ${report.summary.partiallyCompliantTables}\n`;
markdown += `- **Non-Compliant:** ${report.summary.nonCompliantTables}\n`;
markdown += `- **Total Policies:** ${report.summary.totalPolicies}\n\n`;
if (report.recommendations.length > 0) {
markdown += `## Recommendations\n\n`;
for (const category of report.recommendations) {
markdown += `### ${category.category.toUpperCase()} (${category.priority} priority)\n\n`;
for (const rec of category.recommendations) {
markdown += `- ${rec}\n`;
}
markdown += `\n`;
}
}
return markdown;
}
/**
* Generate HTML format report
*/
generateHtmlReport(report) {
return `
<!DOCTYPE html>
<html>
<head>
<title>RLS Compliance Report</title>
<style>
body { font-family: Arial, sans-serif; margin: 40px; }
.score { font-size: 24px; font-weight: bold; color: ${report.overallScore >= 80 ? 'green' : report.overallScore >= 50 ? 'orange' : 'red'}; }
.summary { background: #f5f5f5; padding: 20px; border-radius: 5px; }
.recommendation { margin: 10px 0; padding: 10px; border-left: 4px solid #007cba; }
</style>
</head>
<body>
<h1>RLS Compliance Report</h1>
<p><strong>Generated:</strong> ${new Date(report.timestamp).toLocaleString()}</p>
<p class="score">Overall Score: ${report.overallScore.toFixed(1)}%</p>
<div class="summary">
<h2>Summary</h2>
<ul>
<li>Total Tables: ${report.summary.totalTables}</li>
<li>Compliant: ${report.summary.compliantTables}</li>
<li>Partially Compliant: ${report.summary.partiallyCompliantTables}</li>
<li>Non-Compliant: ${report.summary.nonCompliantTables}</li>
<li>Total Policies: ${report.summary.totalPolicies}</li>
</ul>
</div>
${report.recommendations.length > 0 ? `
<h2>Recommendations</h2>
${report.recommendations.map(category => `
<div class="recommendation">
<h3>${category.category.toUpperCase()} (${category.priority} priority)</h3>
<ul>
${category.recommendations.map(rec => `<li>${rec}</li>`).join('')}
</ul>
</div>
`).join('')}
` : ''}
</body>
</html>`;
}
}
exports.RLSComplianceValidator = RLSComplianceValidator;
//# sourceMappingURL=rls-compliance-validator.js.map