feat(M02-C): 建立RBAC与门店数据范围
This commit is contained in:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user