新增多角色权限控制(赛事/财务/客服管理员),支持员工 CRUD、路由菜单按权限显隐、审计日志范围过滤;登录返回角色与权限列表。玩家端赛事列表增加静默刷新避免图片闪烁。Seed 补充演示员工账号与充值相关权限。附带 RBAC/审计范围单元测试及 UAT 文档更新。 Co-authored-by: Cursor <cursoragent@cursor.com>
101 lines
3.2 KiB
TypeScript
101 lines
3.2 KiB
TypeScript
import { ExecutionContext } from '@nestjs/common';
|
||
import { Reflector } from '@nestjs/core';
|
||
import { PermissionsGuard } from '../../domains/identity/guards';
|
||
import { isAuditListUnrestricted } from '../../domains/operations/audit/audit-list-scope';
|
||
import { PERMISSIONS_KEY } from '../../shared/common/decorators';
|
||
|
||
/** Mirrors run-seed.ts role permission assignments (SEC010–SEC012). */
|
||
const ROLE_PERMISSIONS: Record<string, string[]> = {
|
||
SUPER_ADMIN: ['*'],
|
||
MATCH_ADMIN: [
|
||
'matches.manage',
|
||
'settlement.confirm',
|
||
'content.manage',
|
||
'bets.view',
|
||
'reports.view',
|
||
'audit.view',
|
||
],
|
||
FINANCE_ADMIN: [
|
||
'wallet.deposit',
|
||
'wallet.withdraw',
|
||
'cashback.confirm',
|
||
'agents.view',
|
||
'agents.credit',
|
||
'users.view',
|
||
'users.create',
|
||
'deposit.manage',
|
||
'deposit.review',
|
||
'reports.view',
|
||
'bets.view',
|
||
'audit.view',
|
||
],
|
||
SUPPORT: ['users.view', 'users.reset_password', 'bets.view', 'reports.view', 'audit.view'],
|
||
};
|
||
|
||
function mockContext(user: Record<string, unknown>): ExecutionContext {
|
||
return {
|
||
switchToHttp: () => ({
|
||
getRequest: () => ({ user }),
|
||
}),
|
||
getHandler: () => ({}),
|
||
getClass: () => ({}),
|
||
} as ExecutionContext;
|
||
}
|
||
|
||
function guardAllows(user: Record<string, unknown>, ...required: string[]): boolean {
|
||
const reflector = {
|
||
getAllAndOverride: (key: string) => (key === PERMISSIONS_KEY ? required : undefined),
|
||
} as unknown as Reflector;
|
||
const guard = new PermissionsGuard(reflector);
|
||
try {
|
||
return guard.canActivate(mockContext(user));
|
||
} catch {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
function userWithRole(role: string) {
|
||
return {
|
||
userType: 'ADMIN',
|
||
role,
|
||
permissions: ROLE_PERMISSIONS[role] ?? [],
|
||
};
|
||
}
|
||
|
||
describe('Admin RBAC (SEC010–SEC012)', () => {
|
||
it('SEC010: SUPPORT cannot perform wallet deposit', () => {
|
||
const user = userWithRole('SUPPORT');
|
||
expect(guardAllows(user, 'wallet.deposit')).toBe(false);
|
||
expect(guardAllows(user, 'users.view')).toBe(true);
|
||
expect(guardAllows(user, 'users.reset_password')).toBe(true);
|
||
});
|
||
|
||
it('SEC011: FINANCE_ADMIN cannot manage matches', () => {
|
||
const user = userWithRole('FINANCE_ADMIN');
|
||
expect(guardAllows(user, 'matches.manage')).toBe(false);
|
||
expect(guardAllows(user, 'wallet.deposit')).toBe(true);
|
||
expect(guardAllows(user, 'agents.credit')).toBe(true);
|
||
});
|
||
|
||
it('SEC012: MATCH_ADMIN cannot perform wallet deposit', () => {
|
||
const user = userWithRole('MATCH_ADMIN');
|
||
expect(guardAllows(user, 'wallet.deposit')).toBe(false);
|
||
expect(guardAllows(user, 'settlement.confirm')).toBe(true);
|
||
expect(guardAllows(user, 'content.manage')).toBe(true);
|
||
expect(guardAllows(user, 'settlement.resettle')).toBe(false);
|
||
});
|
||
|
||
it('SUPER_ADMIN bypasses permission checks', () => {
|
||
const user = userWithRole('SUPER_ADMIN');
|
||
expect(guardAllows(user, 'wallet.deposit')).toBe(true);
|
||
expect(guardAllows(user, 'matches.manage')).toBe(true);
|
||
expect(guardAllows(user, 'settings.manage')).toBe(true);
|
||
});
|
||
|
||
it('audit list scope: only SUPER_ADMIN is unrestricted', () => {
|
||
expect(isAuditListUnrestricted('SUPER_ADMIN')).toBe(true);
|
||
expect(isAuditListUnrestricted('FINANCE_ADMIN')).toBe(false);
|
||
expect(isAuditListUnrestricted('MATCH_ADMIN')).toBe(false);
|
||
});
|
||
});
|