forked from wangziqi/gongxue-base
feat: add platform admin permissions
This commit is contained in:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user