feat(M02-C): 建立RBAC与门店数据范围

This commit is contained in:
Codex
2026-06-18 10:50:43 +08:00
parent a8c2a3b3e1
commit caacc78545
16 changed files with 334 additions and 16 deletions
@@ -11,6 +11,7 @@ import {
PlatformConfigRepository
} from '../dist/tenancy/platform-config-repository.js';
import { AuthRepository } from '../dist/auth/auth-repository.js';
import { RbacRepository } from '../dist/auth/rbac-repository.js';
import {
executeMigrationPlan,
loadMigrationPlan,
@@ -27,7 +28,10 @@ const expectedTables = [
'qipai_orders',
'qipai_outbox_events',
'qipai_payments',
'qipai_permissions',
'qipai_platform_apps',
'qipai_role_permissions',
'qipai_roles',
'qipai_rooms',
'qipai_schema_migrations',
'qipai_stores',
@@ -35,6 +39,8 @@ const expectedTables = [
'qipai_tenant_configs',
'qipai_tenants',
'qipai_user_identities',
'qipai_user_roles',
'qipai_user_store_scopes',
'qipai_users'
];
const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), '../..');
@@ -56,9 +62,9 @@ async function readMigrationVersions(pool) {
const [rows] = await pool.query(
`SELECT version, name
FROM qipai_schema_migrations
WHERE version IN (?, ?, ?, ?)
WHERE version IN (?, ?, ?, ?, ?)
ORDER BY version`,
['2026061601', '2026061802', '2026061803', '2026061804']
['2026061601', '2026061802', '2026061803', '2026061804', '2026061805']
);
return rows;
}
@@ -193,6 +199,28 @@ async function assertRevocableAuthSession(pool, context) {
userAgent: 'M02-B test'
});
assert.equal(session.user.userType, 'CUSTOMER');
const rbac = new RbacRepository(pool);
assert.deepEqual(await rbac.getAccessProfile(context.tenantId, session.user.id), {
roles: ['CUSTOMER'],
capabilities: ['order.self.read', 'profile.read'],
storeIds: []
});
const [storeResult] = await pool.query(
`INSERT INTO qipai_stores (tenant_id, name) VALUES (?, 'M02C Store')`,
[context.tenantId]
);
assert.equal(await rbac.grantStore({
tenantId: context.tenantId,
userId: session.user.id,
storeId: String(storeResult.insertId),
scopeType: 'STAFF'
}), true);
assert.equal(await rbac.grantStore({
tenantId: String(Number(context.tenantId) + 1),
userId: session.user.id,
storeId: String(storeResult.insertId),
scopeType: 'STAFF'
}), false);
assert.equal((await repository.loginWithWechat({
context,
openid: 'm02b-openid-a',
@@ -249,7 +277,8 @@ try {
{ version: '2026061601', name: 'm01b_core_schema' },
{ version: '2026061802', name: 'm01c_async_tasks' },
{ version: '2026061803', name: 'm02a_tenant_apps' },
{ version: '2026061804', name: 'm02b_wechat_auth' }
{ version: '2026061804', name: 'm02b_wechat_auth' },
{ version: '2026061805', name: 'm02c_rbac' }
]);
await assertTaskDurability(pool);
const loginContext = await assertPlatformTenantIsolation(pool);
@@ -269,7 +298,8 @@ try {
{ version: '2026061601', name: 'm01b_core_schema' },
{ version: '2026061802', name: 'm01c_async_tasks' },
{ version: '2026061803', name: 'm02a_tenant_apps' },
{ version: '2026061804', name: 'm02b_wechat_auth' }
{ version: '2026061804', name: 'm02b_wechat_auth' },
{ version: '2026061805', name: 'm02c_rbac' }
]);
await assertLegacyCompatibility(pool);
console.log('PASS: second up and verify restored the schema.');
@@ -300,7 +330,9 @@ try {
'cross-tenant bootstrap rejection',
'openid identity reuse',
'session revocation',
'role-version invalidation'
'role-version invalidation',
'customer capabilities',
'cross-tenant store grant rejection'
]
}, null, 2));
} finally {