174 lines
6.1 KiB
TypeScript
174 lines
6.1 KiB
TypeScript
import { ForbiddenException, Injectable, NotFoundException } from '@nestjs/common';
|
|
import { PrismaService } from '../infra/prisma.service';
|
|
|
|
export interface UserAccessContext {
|
|
roles: string[];
|
|
permissions: string[];
|
|
canAccessAdmin: boolean;
|
|
canManageRoles: boolean;
|
|
canViewOAuth: boolean;
|
|
canManageOAuth: boolean;
|
|
canManageAllOAuth: boolean;
|
|
canManageUsers: boolean;
|
|
canManageAllUsers: boolean;
|
|
canViewUsers: boolean;
|
|
canManageSettings: boolean;
|
|
canViewUserDocuments: boolean;
|
|
canVerifyUsers: boolean;
|
|
canManageBots: boolean;
|
|
}
|
|
|
|
function hasAnyPermission(permissions: string[], ...slugs: string[]) {
|
|
return slugs.some((slug) => permissions.includes(slug));
|
|
}
|
|
|
|
@Injectable()
|
|
export class AccessService {
|
|
constructor(private readonly prisma: PrismaService) {}
|
|
|
|
async getUserAccess(userId: string, isSuperAdmin: boolean): Promise<UserAccessContext> {
|
|
if (isSuperAdmin) {
|
|
return {
|
|
roles: ['super-admin'],
|
|
permissions: [
|
|
'admin.access',
|
|
'oauth.view',
|
|
'oauth.view.all',
|
|
'oauth.manage',
|
|
'oauth.manage.all',
|
|
'users.view',
|
|
'users.view.all',
|
|
'users.manage',
|
|
'users.manage.all',
|
|
'settings.manage',
|
|
'rbac.manage',
|
|
'documents.view_others',
|
|
'users.verify',
|
|
'bots.manage.all'
|
|
],
|
|
canAccessAdmin: true,
|
|
canManageRoles: true,
|
|
canViewOAuth: true,
|
|
canManageOAuth: true,
|
|
canManageAllOAuth: true,
|
|
canManageUsers: true,
|
|
canManageAllUsers: true,
|
|
canViewUsers: true,
|
|
canManageSettings: true,
|
|
canViewUserDocuments: true,
|
|
canVerifyUsers: true,
|
|
canManageBots: true
|
|
};
|
|
}
|
|
|
|
const [roleLinks, directLinks] = await Promise.all([
|
|
this.prisma.userRole.findMany({
|
|
where: { userId },
|
|
include: {
|
|
role: {
|
|
include: {
|
|
permissions: {
|
|
include: { permission: true }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}),
|
|
this.prisma.userPermission.findMany({
|
|
where: { userId },
|
|
include: { permission: true }
|
|
})
|
|
]);
|
|
|
|
const roles = roleLinks.map((link) => link.role.slug);
|
|
const rolePermissions = roleLinks.flatMap((link) => link.role.permissions.map((item) => item.permission.slug));
|
|
const directPermissions = directLinks.map((link) => link.permission.slug);
|
|
const permissions = [...new Set([...rolePermissions, ...directPermissions])];
|
|
|
|
const canViewOAuth = hasAnyPermission(permissions, 'oauth.view', 'oauth.view.all', 'oauth.manage', 'oauth.manage.all');
|
|
const canManageOAuth = hasAnyPermission(permissions, 'oauth.manage', 'oauth.manage.all');
|
|
const canManageAllOAuth = permissions.includes('oauth.manage.all');
|
|
const canViewUsers = hasAnyPermission(permissions, 'users.view', 'users.view.all', 'users.manage', 'users.manage.all', 'users.read');
|
|
const canManageUsers = hasAnyPermission(permissions, 'users.manage', 'users.manage.all');
|
|
const canManageAllUsers = permissions.includes('users.manage.all');
|
|
|
|
return {
|
|
roles,
|
|
permissions,
|
|
canAccessAdmin: permissions.includes('admin.access'),
|
|
canManageRoles: permissions.includes('rbac.manage'),
|
|
canViewOAuth,
|
|
canManageOAuth,
|
|
canManageAllOAuth,
|
|
canManageUsers,
|
|
canManageAllUsers,
|
|
canViewUsers,
|
|
canManageSettings: permissions.includes('settings.manage'),
|
|
canViewUserDocuments: permissions.includes('documents.view_others'),
|
|
canVerifyUsers: permissions.includes('users.verify'),
|
|
canManageBots: permissions.includes('bots.manage.all')
|
|
};
|
|
}
|
|
|
|
canManageOAuthClient(
|
|
access: UserAccessContext,
|
|
actorUserId: string,
|
|
client: { createdById: string | null }
|
|
) {
|
|
if (access.canManageAllOAuth) return true;
|
|
if (access.canManageOAuth && client.createdById === actorUserId) return true;
|
|
return false;
|
|
}
|
|
|
|
async assertRbacManage(actorUserId: string) {
|
|
const actor = await this.prisma.user.findUnique({ where: { id: actorUserId } });
|
|
if (!actor) {
|
|
throw new NotFoundException('Пользователь не найден');
|
|
}
|
|
if (actor.isSuperAdmin) return;
|
|
const access = await this.getUserAccess(actorUserId, false);
|
|
if (!access.canManageRoles) {
|
|
throw new ForbiddenException('Недостаточно прав для управления ролями и правами');
|
|
}
|
|
}
|
|
|
|
async assertCanVerifyUsers(actorUserId: string) {
|
|
const actor = await this.prisma.user.findUnique({ where: { id: actorUserId } });
|
|
if (!actor) {
|
|
throw new NotFoundException('Пользователь не найден');
|
|
}
|
|
if (actor.isSuperAdmin) return;
|
|
const access = await this.getUserAccess(actorUserId, false);
|
|
if (!access.canVerifyUsers) {
|
|
throw new ForbiddenException('Недостаточно прав для верификации пользователей');
|
|
}
|
|
}
|
|
|
|
async assertSuperAdmin(actorUserId: string) {
|
|
const actor = await this.prisma.user.findUnique({ where: { id: actorUserId } });
|
|
if (!actor?.isSuperAdmin) {
|
|
throw new ForbiddenException('Только супер-администратор может выполнять это действие');
|
|
}
|
|
}
|
|
|
|
async assertPermission(userId: string, isSuperAdmin: boolean, permission: string) {
|
|
const access = await this.getUserAccess(userId, isSuperAdmin);
|
|
if (!access.permissions.includes(permission) && !isSuperAdmin) {
|
|
throw new Error('FORBIDDEN_PERMISSION');
|
|
}
|
|
return access;
|
|
}
|
|
|
|
async assertCanViewUserDocuments(requesterId: string, targetUserId: string) {
|
|
if (requesterId === targetUserId) return;
|
|
const requester = await this.prisma.user.findUnique({ where: { id: requesterId } });
|
|
if (!requester) {
|
|
throw new ForbiddenException('Пользователь не найден');
|
|
}
|
|
const access = await this.getUserAccess(requesterId, requester.isSuperAdmin);
|
|
if (!access.canViewUserDocuments) {
|
|
throw new ForbiddenException('Нет доступа к документам пользователя');
|
|
}
|
|
}
|
|
}
|