forked from wangziqi/gongxue-base
181 lines
7.3 KiB
JavaScript
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');
|