Files
gongxue-base/scripts/backend-runtime-role-contract-test.js
2026-07-12 19:26:57 +08:00

181 lines
7.3 KiB
JavaScript

import assert from 'node:assert/strict';
import fs from 'node:fs';
import path from 'node:path';
const repoRoot = process.cwd();
const migration = fs.readFileSync(
path.join(repoRoot, 'supabase', 'migrations', '202607120013_backend_runtime_roles.sql'),
'utf8',
);
const authBoundaryMigration = fs.readFileSync(
path.join(repoRoot, 'supabase', 'migrations', '202607120018_auth_user_reference_boundary.sql'),
'utf8',
);
const migrationHistoryBoundaryMigration = fs.readFileSync(
path.join(repoRoot, 'supabase', 'migrations', '202607120019_production_migration_history_boundary.sql'),
'utf8',
);
const safetyMigration = fs.readFileSync(
path.join(repoRoot, 'supabase', 'migrations', '202607120001_destructive_test_environment_safety.sql'),
'utf8',
);
const readiness = fs.readFileSync(
path.join(repoRoot, 'scripts', 'production-readiness-check.js'),
'utf8',
);
const apiEnv = fs.readFileSync(
path.join(repoRoot, 'scripts', 'deploy', 'env', 'api.env.example'),
'utf8',
);
const workerEnv = fs.readFileSync(
path.join(repoRoot, 'scripts', 'deploy', 'env', 'worker.env.example'),
'utf8',
);
const destructiveGuard = fs.readFileSync(
path.join(repoRoot, 'scripts', 'lib', 'destructive-test-database-guard.js'),
'utf8',
);
const platformAdminRoutes = fs.readFileSync(
path.join(repoRoot, 'apps', 'api', 'src', 'features', 'platform-admin', 'routes.ts'),
'utf8',
);
const platformAdminBootstrap = fs.readFileSync(
path.join(repoRoot, 'scripts', 'bootstrap-platform-admin.js'),
'utf8',
);
const packageJson = JSON.parse(fs.readFileSync(path.join(repoRoot, 'package.json'), 'utf8'));
for (const role of ['tiku_api', 'tiku_worker']) {
assert.match(destructiveGuard, new RegExp(`['"]${role}['"]`));
}
assert.doesNotMatch(
migration,
/create role tiku_(?:api|worker)|alter role tiku_(?:api|worker)/i,
'normal Supabase migrations must not require superuser-only cluster role changes',
);
assert.match(migration, /not role_state\.rolbypassrls/i);
assert.match(migration, /role_state\.has_parent_roles/i);
assert.match(migration, /search_path=pg_catalog, public, extensions/i);
assert.match(migration, /revoke create on schema public, app, app_private, extensions from public/i);
assert.match(migration, /revoke all privileges on schema public, app, app_private, extensions from tiku_api, tiku_worker/i);
assert.match(migration, /grant usage on schema public, app_private, extensions to tiku_api, tiku_worker/i);
assert.match(migration, /grant usage on schema app to tiku_api/i);
assert.doesNotMatch(migration, /grant usage on schema app to tiku_worker/i);
assert.match(
migration,
/grant select, insert, update, delete[\s\S]*on all tables in schema public[\s\S]*to tiku_api, tiku_worker/i,
);
assert.match(
migration,
/grant select[\s\S]*on all tables in schema app_private[\s\S]*to tiku_api, tiku_worker/i,
);
assert.match(
migration,
/grant insert, update[\s\S]*on app_private\.auth_sessions,[\s\S]*app_private\.tenant_secrets,[\s\S]*app_private\.platform_secrets[\s\S]*to tiku_api/i,
);
assert.match(
migration,
/grant insert, update, delete[\s\S]*on app_private\.sms_send_rate_limits[\s\S]*to tiku_api/i,
);
assert.doesNotMatch(migration, /grant[^;]*truncate[^;]*to tiku_api|grant[^;]*truncate[^;]*to tiku_worker/i);
assert.match(migration, /revoke execute on all functions in schema app from public/i);
assert.match(
migration,
/alter table %I\.%I alter column %I set default pg_catalog\.gen_random_uuid\(\)/i,
'UUID defaults must be independent of the pgcrypto extension schema',
);
assert.doesNotMatch(
migration,
/grant execute on function (?:public|extensions)\.gen_random_uuid\(\)/i,
'runtime roles must rely on the PostgreSQL core UUID function instead of an extension wrapper',
);
assert.match(migration, /grant execute on function app\.public_question_bank_grant_allows[\s\S]*to tiku_api/i);
assert.doesNotMatch(
migration,
/grant execute[\s\S]*on all functions in schema (?:public|app|app_private)[\s\S]*to tiku_api/i,
);
assert.match(
migration,
/pg_has_role\(current_user, owner_role\.oid, 'MEMBER'\)/i,
'default ACL loops must skip Supabase-owned roles the migration user cannot SET ROLE into',
);
assert.match(migration, /from pg_auth_members membership/i);
assert.match(migration, /tiku_api\/tiku_worker must not own database objects/i);
assert.doesNotMatch(migration, /password\s+['"]/i, 'role passwords must be provisioned outside migrations');
assert.match(authBoundaryMigration, /create or replace function app\.auth_user_exists\(target_user_id uuid\)/i);
assert.match(authBoundaryMigration, /security definer[\s\S]*set search_path = ''/i);
assert.match(authBoundaryMigration, /from auth\.users auth_user[\s\S]*auth_user\.id = target_user_id/i);
assert.match(
authBoundaryMigration,
/revoke all on function app\.auth_user_exists\(uuid\)[\s\S]*from public, anon, authenticated, service_role, tiku_api, tiku_worker/i,
);
assert.match(authBoundaryMigration, /grant execute on function app\.auth_user_exists\(uuid\) to tiku_api/i);
assert.doesNotMatch(authBoundaryMigration, /grant[^;]*to tiku_worker/i);
assert.match(
migrationHistoryBoundaryMigration,
/create or replace function app\.production_migration_history\(expected_version text\)/i,
);
assert.match(
migrationHistoryBoundaryMigration,
/security definer[\s\S]*set search_path = ''/i,
);
assert.match(
migrationHistoryBoundaryMigration,
/from supabase_migrations\.schema_migrations/i,
);
assert.match(
migrationHistoryBoundaryMigration,
/revoke all on function app\.production_migration_history\(text\)[\s\S]*from public, anon, authenticated, service_role, tiku_api, tiku_worker/i,
);
assert.match(
migrationHistoryBoundaryMigration,
/grant execute on function app\.production_migration_history\(text\) to tiku_api/i,
);
assert.doesNotMatch(migrationHistoryBoundaryMigration, /grant[^;]*to tiku_worker/i);
for (const [label, source] of [
['platform staff API', platformAdminRoutes],
['platform admin bootstrap CLI', platformAdminBootstrap],
]) {
assert.match(source, /app\.auth_user_exists\(\$1::uuid\)/, `${label} must use the boolean Auth boundary`);
assert.doesNotMatch(source, /\bfrom\s+auth\.users\b/i, `${label} must not read auth.users directly`);
assert.doesNotMatch(source, /\bjoin\s+auth\.users\b/i, `${label} must not join auth.users directly`);
}
const retiredSharedRole = ['tiku', 'app'].join('_');
assert.equal(
safetyMigration.includes(retiredSharedRole),
false,
'safety migration must not retain the retired shared runtime role',
);
assert.match(apiEnv, /DATABASE_URL=postgresql:\/\/tiku_api:/);
assert.match(apiEnv, /^DB_EXPECTED_RUNTIME_ROLE=tiku_api$/m);
assert.match(workerEnv, /DATABASE_URL=postgresql:\/\/tiku_worker:/);
assert.match(workerEnv, /^DB_EXPECTED_RUNTIME_ROLE=tiku_worker$/m);
for (const gateId of [
'db.runtime_role.identity',
'db.runtime_role.attributes',
'db.runtime_role.schema_acl',
'db.runtime_role.table_acl',
'db.runtime_role.function_acl',
'db.runtime_role.auth_acl',
'db.extensions.isolation',
'db.runtime_role.ownership',
'db.runtime_role.ddl_denied',
'db.migrations.current',
]) {
assert.ok(readiness.includes(gateId), `readiness must enforce ${gateId}`);
}
assert.ok(
packageJson.scripts?.['test:readiness']?.includes('backend-runtime-role-contract-test.js'),
'the production readiness contract suite must run the backend runtime role test',
);
console.log('[PASS] backend runtime role least-privilege contract');