feat(v2.7): 建立协作治理基础合同
This commit is contained in:
63
apps/server/src/common/rbac/rbac.service.ts
Normal file
63
apps/server/src/common/rbac/rbac.service.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { ForbiddenException, Injectable } from '@nestjs/common';
|
||||
import { PrismaService } from '../../prisma/prisma.service';
|
||||
|
||||
export type ProjectGovernanceRole = 'owner' | 'admin' | 'member' | 'viewer';
|
||||
|
||||
export interface ProjectRoleAssertion {
|
||||
actorId?: string;
|
||||
projectId?: string | null;
|
||||
allowedRoles: ProjectGovernanceRole[];
|
||||
permissions?: string[];
|
||||
}
|
||||
|
||||
export interface ProjectRoleDecision {
|
||||
actorId: string;
|
||||
projectId: string;
|
||||
role: ProjectGovernanceRole;
|
||||
via: 'system' | 'project_member';
|
||||
}
|
||||
|
||||
const ROLE_RANK: Record<ProjectGovernanceRole, number> = {
|
||||
owner: 4,
|
||||
admin: 3,
|
||||
member: 2,
|
||||
viewer: 1,
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class RbacService {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
async assertProjectRole(input: ProjectRoleAssertion): Promise<ProjectRoleDecision> {
|
||||
const actorId = input.actorId?.trim();
|
||||
const projectId = input.projectId?.trim();
|
||||
if (!actorId || !projectId) {
|
||||
throw new ForbiddenException('Missing actor or project scope');
|
||||
}
|
||||
|
||||
if (input.permissions?.includes('*')) {
|
||||
return { actorId, projectId, role: 'owner', via: 'system' };
|
||||
}
|
||||
|
||||
const membership = await this.prisma.projectMember.findUnique({
|
||||
where: { projectId_userId: { projectId, userId: actorId } },
|
||||
});
|
||||
const role = normalizeProjectRole(membership?.role);
|
||||
if (!role || !hasRequiredRole(role, input.allowedRoles)) {
|
||||
throw new ForbiddenException('Insufficient project role');
|
||||
}
|
||||
|
||||
return { actorId, projectId, role, via: 'project_member' };
|
||||
}
|
||||
}
|
||||
|
||||
export function normalizeProjectRole(role: string | null | undefined): ProjectGovernanceRole | null {
|
||||
if (role === 'owner' || role === 'admin' || role === 'member' || role === 'viewer') return role;
|
||||
return null;
|
||||
}
|
||||
|
||||
export function hasRequiredRole(role: ProjectGovernanceRole, allowedRoles: ProjectGovernanceRole[]): boolean {
|
||||
if (allowedRoles.length === 0) return false;
|
||||
const minimumRank = Math.min(...allowedRoles.map((allowedRole) => ROLE_RANK[allowedRole]));
|
||||
return ROLE_RANK[role] >= minimumRank;
|
||||
}
|
||||
Reference in New Issue
Block a user