feat: add platform admin permissions

This commit is contained in:
Codex
2026-06-30 07:50:02 +08:00
parent 51f8cbdec8
commit 780feee4f8
20 changed files with 392 additions and 63 deletions

View File

@@ -21,6 +21,7 @@ const PARTNER_TENANT_ADMIN_USER_ID = '00000000-0000-0000-0000-000000000907';
const AUTH_USER_ID = '00000000-0000-0000-0000-00000000a101';
const AUTH_TENANT_ADMIN_USER_ID = '00000000-0000-0000-0000-00000000a102';
const AUTH_PLATFORM_ADMIN_USER_ID = '00000000-0000-0000-0000-00000000a999';
const AUTH_RESTRICTED_PLATFORM_ADMIN_USER_ID = '00000000-0000-0000-0000-00000000a998';
const AUTH_JWT_SECRET = 'development-jwt-secret-change-me';
const START_SERVER = process.argv.includes('--start-server');
const ENABLE_REAL_STORAGE_SIGN_TESTS = process.env.ENABLE_REAL_STORAGE_SIGN_TESTS === 'true';
@@ -1063,6 +1064,121 @@ async function testSupabaseJwtIdentity() {
assert.equal(studentPlatformDenied.code, 'PLATFORM_ADMIN_REQUIRED', 'student Supabase JWT must not access platform APIs');
}
async function testPlatformAdminPermissions() {
const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL || DEFAULT_DATABASE_URL });
const restrictedUserId = crypto.randomUUID();
try {
await pool.query(
`
insert into auth.users (
id, aud, role, phone, phone_confirmed_at, raw_app_meta_data, raw_user_meta_data,
created_at, updated_at
)
values (
$1, 'authenticated', 'authenticated', '13999999998', now(),
'{"provider":"phone","providers":["phone"],"app_role":"platform_admin"}'::jsonb,
'{}'::jsonb, now(), now()
)
on conflict (id)
do update set phone = excluded.phone,
raw_app_meta_data = excluded.raw_app_meta_data,
updated_at = now()
`,
[AUTH_RESTRICTED_PLATFORM_ADMIN_USER_ID],
);
await pool.query(
`
insert into public.platform_users (
id, auth_user_id, username, phone, name, primary_role, platform_permissions, raw_profile
)
values (
$1, $2, 'restricted_platform_reader', '13999999998', 'Restricted Platform Reader',
'platform_admin',
'{"platform:overview:read":true,"platform:tenant:read":true,"platform:plan:read":true}'::jsonb,
'{"source":"api-integration-test"}'::jsonb
)
on conflict (auth_user_id)
do update set username = excluded.username,
phone = excluded.phone,
name = excluded.name,
primary_role = excluded.primary_role,
platform_permissions = excluded.platform_permissions,
updated_at = now()
`,
[restrictedUserId, AUTH_RESTRICTED_PLATFORM_ADMIN_USER_ID],
);
} finally {
await pool.end();
}
const restrictedJwt = await createSupabaseJwt(AUTH_RESTRICTED_PLATFORM_ADMIN_USER_ID, {
phone: '13999999998',
appRole: 'platform_admin',
tenantId: false,
});
const restrictedHeaders = { authorization: `Bearer ${restrictedJwt}` };
const permissionSummary = await request('/api/platform-admin/permissions', {
tenantId: false,
userId: false,
headers: restrictedHeaders,
});
assert.equal(permissionSummary.item?.effective?.['platform:tenant:read'], true, 'restricted platform admin should expose granted tenant read permission');
assert.equal(permissionSummary.item?.effective?.['platform:tenant:write'], false, 'restricted platform admin should not expose tenant write permission');
assert.equal(permissionSummary.item?.effective?.['platform:audit:export'], false, 'restricted platform admin should not expose audit export permission');
assert.ok(!JSON.stringify(permissionSummary).includes('local-platform-admin-key'), 'platform permission summary must not leak platform admin key');
const overview = await request('/api/platform-admin/overview', {
tenantId: false,
userId: false,
headers: restrictedHeaders,
});
assert.ok(overview.item?.tenants?.total >= 1, 'restricted platform admin should access explicitly granted overview');
const tenants = await request('/api/platform-admin/tenants', {
tenantId: false,
userId: false,
headers: restrictedHeaders,
});
assert.ok(Array.isArray(tenants.items), 'restricted platform admin should list tenants with tenant read permission');
const createDenied = await request('/api/platform-admin/tenants', {
tenantId: false,
userId: false,
headers: restrictedHeaders,
method: 'POST',
body: {
slug: `restricted-denied-${Date.now().toString(36)}`,
name: 'Restricted Denied Tenant',
},
expectStatus: 403,
});
assert.equal(createDenied.code, 'PLATFORM_PERMISSION_REQUIRED', 'tenant write must require platform tenant write permission');
const auditExportDenied = await request('/api/platform-admin/audit-logs/export', {
tenantId: false,
userId: false,
headers: restrictedHeaders,
query: { format: 'csv' },
expectStatus: 403,
});
assert.equal(auditExportDenied.code, 'PLATFORM_PERMISSION_REQUIRED', 'audit export must require explicit platform audit export permission');
const paymentDenied = await request('/api/platform-admin/invoices/payments/manual-confirm', {
tenantId: false,
userId: false,
headers: restrictedHeaders,
method: 'POST',
body: {
tenantId: PARTNER_TENANT_ID,
invoiceId: ids.platformOverdueInvoice,
amountCents: 1,
},
expectStatus: 403,
});
assert.equal(paymentDenied.code, 'PLATFORM_PERMISSION_REQUIRED', 'manual service-fee payment must require billing payment permission');
}
async function testSupabaseJwksIdentity() {
const { publicKey, privateKey } = crypto.generateKeyPairSync('rsa', { modulusLength: 2048 });
const publicJwk = await exportJWK(publicKey);
@@ -8897,6 +9013,7 @@ async function main() {
await check('trusted session identity', testTrustedSessionIdentity);
await check('phone binding', testPhoneBinding);
await check('Supabase JWT identity', testSupabaseJwtIdentity);
await check('platform admin permissions', testPlatformAdminPermissions);
await check('Supabase JWKS JWT identity', testSupabaseJwksIdentity);
await check('legacy auth headers disabled', testLegacyAuthHeadersDisabled);
await check('platform tenant operations and audit', testPlatformTenantOperationsAndAudit);