mirror of
https://github.com/AeThex-Corporation/AeThex-OS.git
synced 2026-04-18 14:27:20 +00:00
- Add revenue_events table to track org/project revenue with source tracking - Add Drizzle schema for revenue_events with proper org/project references - Create migration 0006_revenue_events.sql with indexes - Fix migration 0004: Remove FK constraints to profiles.id (auth schema incompatibility) - Document auth.users/profiles.id type mismatch (UUID vs VARCHAR) - Harden profile update authorization (self-update or org admin/owner only) - Complete org-scoping security audit implementation (42 gaps closed)
118 lines
3.4 KiB
TypeScript
118 lines
3.4 KiB
TypeScript
import * as fs from 'fs';
|
|
import * as path from 'path';
|
|
import { fileURLToPath } from 'url';
|
|
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
const __dirname = path.dirname(__filename);
|
|
|
|
// Tables with organization_id that require scoping
|
|
const ORG_SCOPED_TABLES = [
|
|
'aethex_sites',
|
|
'aethex_opportunities',
|
|
'aethex_events',
|
|
'projects',
|
|
'files',
|
|
'marketplace_listings',
|
|
'custom_apps',
|
|
'aethex_projects',
|
|
'aethex_alerts',
|
|
];
|
|
|
|
interface Violation {
|
|
file: string;
|
|
line: number;
|
|
table: string;
|
|
snippet: string;
|
|
}
|
|
|
|
function scanFile(filePath: string): Violation[] {
|
|
const violations: Violation[] = [];
|
|
const content = fs.readFileSync(filePath, 'utf-8');
|
|
const lines = content.split('\n');
|
|
|
|
for (let i = 0; i < lines.length; i++) {
|
|
const line = lines[i];
|
|
|
|
// Skip comments
|
|
if (line.trim().startsWith('//') || line.trim().startsWith('*')) {
|
|
continue;
|
|
}
|
|
|
|
// Check for Supabase queries
|
|
if (line.includes('.from(') || line.includes('supabase')) {
|
|
// Extract table name from .from('table_name')
|
|
const fromMatch = line.match(/\.from\(['"](\w+)['"]\)/);
|
|
if (fromMatch) {
|
|
const tableName = fromMatch[1];
|
|
|
|
// Check if table requires org scoping
|
|
if (ORG_SCOPED_TABLES.includes(tableName)) {
|
|
// Look ahead 10 lines to see if .eq('organization_id', ...) is present
|
|
let hasOrgFilter = false;
|
|
const contextLines = lines.slice(i, Math.min(i + 11, lines.length));
|
|
|
|
for (const contextLine of contextLines) {
|
|
if (contextLine.includes("organization_id") ||
|
|
contextLine.includes("orgScoped") ||
|
|
contextLine.includes("orgEq(") ||
|
|
// User-owned queries (fallback for projects)
|
|
(tableName === 'projects' && contextLine.includes('owner_user_id')) ||
|
|
// Optional org filter pattern
|
|
contextLine.includes("req.query.org_id") ||
|
|
// Public endpoints with explicit guard
|
|
contextLine.includes("IS_PUBLIC = true")) {
|
|
hasOrgFilter = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!hasOrgFilter) {
|
|
violations.push({
|
|
file: path.relative(path.join(__dirname, '..'), filePath),
|
|
line: i + 1,
|
|
table: tableName,
|
|
snippet: line.trim(),
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return violations;
|
|
}
|
|
|
|
function main() {
|
|
console.log('🔍 Scanning server/routes.ts for org-scoping violations...\n');
|
|
|
|
const routesPath = path.join(__dirname, '..', 'server', 'routes.ts');
|
|
|
|
if (!fs.existsSync(routesPath)) {
|
|
console.error('❌ Error: server/routes.ts not found');
|
|
console.error('Tried:', routesPath);
|
|
process.exit(1);
|
|
}
|
|
|
|
const violations = scanFile(routesPath);
|
|
|
|
if (violations.length === 0) {
|
|
console.log('✅ No org-scoping violations found!');
|
|
process.exit(0);
|
|
}
|
|
|
|
console.log(`❌ Found ${violations.length} potential org-scoping violations:\n`);
|
|
|
|
violations.forEach((v, idx) => {
|
|
console.log(`${idx + 1}. ${v.file}:${v.line}`);
|
|
console.log(` Table: ${v.table}`);
|
|
console.log(` Code: ${v.snippet}`);
|
|
console.log('');
|
|
});
|
|
|
|
console.log(`\n❌ Audit failed with ${violations.length} violations`);
|
|
console.log('💡 Add .eq("organization_id", orgId) or use orgScoped() helper\n');
|
|
|
|
process.exit(1);
|
|
}
|
|
|
|
main();
|