feat(auth): 增加服务端权限上下文
This commit is contained in:
83
apps/server/src/common/auth/permission.service.spec.ts
Normal file
83
apps/server/src/common/auth/permission.service.spec.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import { ForbiddenException, UnauthorizedException } from '@nestjs/common';
|
||||
import { PermissionService } from './permission.service';
|
||||
import type { CurrentUser } from './auth-context.service';
|
||||
|
||||
describe('PermissionService', () => {
|
||||
const versionFindUnique = jest.fn();
|
||||
const projectMemberFindUnique = jest.fn();
|
||||
|
||||
const prisma = {
|
||||
version: { findUnique: versionFindUnique },
|
||||
projectMember: { findUnique: projectMemberFindUnique },
|
||||
} as any;
|
||||
|
||||
const service = new PermissionService(prisma);
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('allows the built-in super admin wildcard for any permission without a scope lookup', async () => {
|
||||
const user = currentUser({ id: 'm-8', roleId: 'role-admin' });
|
||||
|
||||
await expect(service.can(user, 'audit:view', { versionId: 'version-1' })).resolves.toBe(true);
|
||||
|
||||
expect(versionFindUnique).not.toHaveBeenCalled();
|
||||
expect(projectMemberFindUnique).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('requires scoped users to belong to the project resolved from a version', async () => {
|
||||
versionFindUnique.mockResolvedValue({ id: 'version-1', projectId: 'project-1', members: [] });
|
||||
projectMemberFindUnique.mockResolvedValue({ projectId: 'project-1', userId: 'dev-1', role: 'member' });
|
||||
|
||||
await expect(service.can(currentUser({ id: 'dev-1', roleId: 'role-dev' }), 'version.devtask:manage', {
|
||||
versionId: 'version-1',
|
||||
})).resolves.toBe(true);
|
||||
|
||||
expect(versionFindUnique).toHaveBeenCalledWith({
|
||||
where: { id: 'version-1' },
|
||||
select: { id: true, projectId: true, members: true },
|
||||
});
|
||||
expect(projectMemberFindUnique).toHaveBeenCalledWith({
|
||||
where: { projectId_userId: { projectId: 'project-1', userId: 'dev-1' } },
|
||||
});
|
||||
});
|
||||
|
||||
it('allows a version member when no project membership exists', async () => {
|
||||
versionFindUnique.mockResolvedValue({
|
||||
id: 'version-1',
|
||||
projectId: 'project-1',
|
||||
members: [{ id: 'tester-1', name: 'Tester One' }],
|
||||
});
|
||||
projectMemberFindUnique.mockResolvedValue(null);
|
||||
|
||||
await expect(service.can(currentUser({ id: 'tester-1', name: 'Tester One', roleId: 'role-test' }), 'version.testcase:manage', {
|
||||
versionId: 'version-1',
|
||||
})).resolves.toBe(true);
|
||||
});
|
||||
|
||||
it('denies users who have a global role permission but are outside the resource scope', async () => {
|
||||
versionFindUnique.mockResolvedValue({ id: 'version-1', projectId: 'project-1', members: [] });
|
||||
projectMemberFindUnique.mockResolvedValue(null);
|
||||
|
||||
await expect(service.can(currentUser({ id: 'dev-1', roleId: 'role-dev' }), 'version.devtask:manage', {
|
||||
versionId: 'version-1',
|
||||
})).resolves.toBe(false);
|
||||
});
|
||||
|
||||
it('throws explicit auth exceptions for assertion callers', async () => {
|
||||
await expect(service.assertCan(null, 'product:create')).rejects.toBeInstanceOf(UnauthorizedException);
|
||||
await expect(service.assertCan(currentUser({ roleId: 'role-dev' }), 'product:delete')).rejects.toBeInstanceOf(ForbiddenException);
|
||||
});
|
||||
});
|
||||
|
||||
function currentUser(overrides: Partial<CurrentUser>): CurrentUser {
|
||||
return {
|
||||
id: 'user-1',
|
||||
name: 'User One',
|
||||
username: 'user.one',
|
||||
roleId: 'role-dev',
|
||||
email: '',
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user