diff --git a/apps/api/src/approval/controller/approval.controller.spec.ts b/apps/api/src/approval/controller/approval.controller.spec.ts index ff475fd8..6097bf85 100644 --- a/apps/api/src/approval/controller/approval.controller.spec.ts +++ b/apps/api/src/approval/controller/approval.controller.spec.ts @@ -14,6 +14,7 @@ import { REDIS_CLIENT } from '../../provider/redis.provider' import { RedisClientType } from 'redis' import { mockDeep } from 'jest-mock-extended' import { ProviderModule } from '../../provider/provider.module' +import { AuthorityCheckerService } from '../../common/authority-checker.service' describe('ApprovalController', () => { let controller: ApprovalController @@ -34,7 +35,8 @@ describe('ApprovalController', () => { { provide: MAIL_SERVICE, useClass: MockMailService - } + }, + AuthorityCheckerService ] }) .overrideProvider(REDIS_CLIENT) diff --git a/apps/api/src/approval/service/approval.service.spec.ts b/apps/api/src/approval/service/approval.service.spec.ts index cb600041..811daf5f 100644 --- a/apps/api/src/approval/service/approval.service.spec.ts +++ b/apps/api/src/approval/service/approval.service.spec.ts @@ -13,6 +13,7 @@ import { REDIS_CLIENT } from '../../provider/redis.provider' import { RedisClientType } from 'redis' import { mockDeep } from 'jest-mock-extended' import { ProviderModule } from '../../provider/provider.module' +import { AuthorityCheckerService } from '../../common/authority-checker.service' describe('ApprovalService', () => { let service: ApprovalService @@ -32,7 +33,8 @@ describe('ApprovalService', () => { { provide: MAIL_SERVICE, useClass: MockMailService - } + }, + AuthorityCheckerService ] }) .overrideProvider(REDIS_CLIENT) diff --git a/apps/api/src/approval/service/approval.service.ts b/apps/api/src/approval/service/approval.service.ts index dfd29d83..f462993f 100644 --- a/apps/api/src/approval/service/approval.service.ts +++ b/apps/api/src/approval/service/approval.service.ts @@ -31,7 +31,7 @@ import { UpdateVariableMetadata, UpdateWorkspaceMetadata } from '../approval.types' -import getWorkspaceWithAuthority from '../../common/get-workspace-with-authority' +import { AuthorityCheckerService } from '../../common/authority-checker.service' @Injectable() export class ApprovalService { @@ -43,7 +43,8 @@ export class ApprovalService { private readonly projectService: ProjectService, private readonly environmentService: EnvironmentService, private readonly secretService: SecretService, - private readonly variableService: VariableService + private readonly variableService: VariableService, + public authorityCheckerService: AuthorityCheckerService ) {} async updateApproval(user: User, reason: string, approvalId: Approval['id']) { @@ -453,12 +454,12 @@ export class ApprovalService { actions: ApprovalAction[], statuses: ApprovalStatus[] ) { - await getWorkspaceWithAuthority( - user.id, - workspaceId, - Authority.MANAGE_APPROVALS, - this.prisma - ) + await this.authorityCheckerService.checkAuthorityOverWorkspace({ + userId: user.id, + entity: { id: workspaceId }, + authority: Authority.MANAGE_APPROVALS, + prisma: this.prisma + }) return await this.prisma.approval.findMany({ where: { @@ -493,12 +494,13 @@ export class ApprovalService { actions: ApprovalAction[], statuses: ApprovalStatus[] ) { - await getWorkspaceWithAuthority( - user.id, - workspaceId, - Authority.READ_WORKSPACE, - this.prisma - ) + await this.authorityCheckerService.checkAuthorityOverWorkspace({ + userId: user.id, + entity: { id: workspaceId }, + authority: Authority.READ_WORKSPACE, + + prisma: this.prisma + }) return this.prisma.approval.findMany({ where: { diff --git a/apps/api/src/common/authority-checker.service.ts b/apps/api/src/common/authority-checker.service.ts new file mode 100644 index 00000000..ef695159 --- /dev/null +++ b/apps/api/src/common/authority-checker.service.ts @@ -0,0 +1,380 @@ +import { PrismaClient, Authority, Workspace } from '@prisma/client' +import { VariableWithProjectAndVersion } from 'src/variable/variable.types' +import { + BadRequestException, + Injectable, + NotFoundException, + UnauthorizedException +} from '@nestjs/common' +import getCollectiveProjectAuthorities from './get-collective-project-authorities' +import getCollectiveWorkspaceAuthorities from './get-collective-workspace-authorities' +import { EnvironmentWithProject } from 'src/environment/environment.types' +import { ProjectWithSecrets } from 'src/project/project.types' +import { SecretWithProjectAndVersion } from 'src/secret/secret.types' + +export interface AuthorityInput { + userId: string + entity: { + id?: string + name?: string + } + authority: Authority + prisma: PrismaClient +} + +@Injectable() +export class AuthorityCheckerService { + public async checkAuthorityOverWorkspace( + input: AuthorityInput + ): Promise { + const { userId, entity, authority, prisma } = input + + let workspace: Workspace + + try { + if (entity?.id) { + workspace = await prisma.workspace.findUnique({ + where: { + id: entity.id + } + }) + } else { + workspace = await prisma.workspace.findFirst({ + where: { + name: entity?.name + } + }) + } + } catch (error) { + /* empty */ + } + + // Check if the workspace exists or not + if (!workspace) { + throw new NotFoundException(`Workspace with id ${entity.id} not found`) + } + + const permittedAuthorities = await getCollectiveWorkspaceAuthorities( + entity.id, + userId, + prisma + ) + + // Check if the user has the authority to perform the action + if ( + !permittedAuthorities.has(authority) && + !permittedAuthorities.has(Authority.WORKSPACE_ADMIN) + ) { + throw new UnauthorizedException( + `User ${userId} does not have the required authorities to perform the action` + ) + } + + return workspace + } + + public async checkAuthorityOverProject( + input: AuthorityInput + ): Promise { + const { userId, entity, authority, prisma } = input + + // Fetch the project + let project: ProjectWithSecrets + + // Fetch the project + try { + if (entity?.id) { + project = await prisma.project.findUnique({ + where: { + id: entity.id + }, + include: { + secrets: true + } + }) + } else { + project = await prisma.project.findFirst({ + where: { + name: entity?.name + }, + include: { + secrets: true + } + }) + } + } catch (error) { + /* empty */ + } + + // If the project is not found, throw an error + if (!project) { + throw new NotFoundException(`Project with id ${entity?.id} not found`) + } + + // Get the authorities of the user in the workspace with the project + const permittedAuthorities = await getCollectiveProjectAuthorities( + userId, + project, + prisma + ) + + // If the user does not have the required authority, or is not a workspace admin, throw an error + if ( + !permittedAuthorities.has(authority) && + !permittedAuthorities.has(Authority.WORKSPACE_ADMIN) + ) { + throw new UnauthorizedException( + `User with id ${userId} does not have the authority in the project with id ${entity?.id}` + ) + } + + // If the project is pending creation, only the user who created the project, a workspace admin or + // a user with the MANAGE_APPROVALS authority can fetch the project + if ( + project.pendingCreation && + !permittedAuthorities.has(Authority.WORKSPACE_ADMIN) && + !permittedAuthorities.has(Authority.MANAGE_APPROVALS) && + project.lastUpdatedById !== userId + ) { + throw new BadRequestException( + `The project with id ${entity?.id} is pending creation and cannot be fetched by the user with id ${userId}` + ) + } + + return project + } + + public async checkAuthorityOverEnvironment( + input: AuthorityInput + ): Promise { + const { userId, entity, authority, prisma } = input + + // Fetch the environment + let environment: EnvironmentWithProject + + try { + if (entity?.id) { + environment = await prisma.environment.findUnique({ + where: { + id: entity.id + }, + include: { + project: true + } + }) + } else { + environment = await prisma.environment.findFirst({ + where: { + name: entity?.name + }, + include: { + project: true + } + }) + } + } catch (e) { + /* empty */ + } + + if (!environment) { + throw new NotFoundException(`Environment with id ${entity.id} not found`) + } + + const permittedAuthorities = await getCollectiveProjectAuthorities( + userId, + environment.project, + prisma + ) + + // Check if the user has the required authorities + if ( + !permittedAuthorities.has(authority) && + !permittedAuthorities.has(Authority.WORKSPACE_ADMIN) + ) { + throw new UnauthorizedException( + `User ${userId} does not have the required authorities` + ) + } + + // If the environment is pending creation, only the user who created the environment, a workspace admin or + // a user with the MANAGE_APPROVALS authority can fetch the environment + if ( + environment.pendingCreation && + !permittedAuthorities.has(Authority.WORKSPACE_ADMIN) && + !permittedAuthorities.has(Authority.MANAGE_APPROVALS) && + environment.lastUpdatedById !== userId + ) { + throw new BadRequestException( + `The environment with id ${entity.id} is pending creation and cannot be fetched by the user with id ${userId}` + ) + } + + return environment + } + + public async checkAuthorityOverVariable( + input: AuthorityInput + ): Promise { + const { userId, entity, authority, prisma } = input + + // Fetch the variable + let variable: VariableWithProjectAndVersion + + try { + if (entity?.id) { + variable = await prisma.variable.findUnique({ + where: { + id: entity.id + }, + include: { + versions: true, + project: true, + environment: { + select: { + id: true, + name: true + } + } + } + }) + } else { + variable = await prisma.variable.findFirst({ + where: { + name: entity?.name + }, + include: { + versions: true, + project: true, + environment: { + select: { + id: true, + name: true + } + } + } + }) + } + } catch (error) { + /* empty */ + } + + if (!variable) { + throw new NotFoundException(`Variable with id ${entity.id} not found`) + } + + // Check if the user has the project in their workspace role list + const permittedAuthorities = await getCollectiveProjectAuthorities( + userId, + variable.project, + prisma + ) + + // Check if the user has the required authorities + if ( + !permittedAuthorities.has(authority) && + !permittedAuthorities.has(Authority.WORKSPACE_ADMIN) + ) { + throw new UnauthorizedException( + `User ${userId} does not have the required authorities` + ) + } + + // If the variable is pending creation, only the user who created the variable, a workspace admin or + // a user with the MANAGE_APPROVALS authority can fetch the variable + if ( + variable.pendingCreation && + !permittedAuthorities.has(Authority.WORKSPACE_ADMIN) && + !permittedAuthorities.has(Authority.MANAGE_APPROVALS) && + variable.lastUpdatedById !== userId + ) { + throw new BadRequestException( + `The variable with id ${entity.id} is pending creation and cannot be fetched by the user with id ${userId}` + ) + } + + return variable + } + + public async checkAuthorityOverSecret( + input: AuthorityInput + ): Promise { + const { userId, entity, authority, prisma } = input + + // Fetch the secret + let secret: SecretWithProjectAndVersion + + try { + if (entity?.id) { + secret = await prisma.secret.findUnique({ + where: { + id: entity.id + }, + include: { + versions: true, + project: true, + environment: { + select: { + id: true, + name: true + } + } + } + }) + } else { + secret = await prisma.secret.findFirst({ + where: { + name: entity?.name + }, + include: { + versions: true, + project: true, + environment: { + select: { + id: true, + name: true + } + } + } + }) + } + } catch (error) { + /* empty */ + } + + if (!secret) { + throw new NotFoundException(`Secret with id ${entity.id} not found`) + } + + // Check if the user has the project in their workspace role list + const permittedAuthorities = await getCollectiveProjectAuthorities( + userId, + secret.project, + prisma + ) + + // Check if the user has the required authorities + if ( + !permittedAuthorities.has(authority) && + !permittedAuthorities.has(Authority.WORKSPACE_ADMIN) + ) { + throw new UnauthorizedException( + `User ${userId} does not have the required authorities` + ) + } + + // If the secret is pending creation, only the user who created the secret, a workspace admin or + // a user with the MANAGE_APPROVALS authority can fetch the secret + if ( + secret.pendingCreation && + !permittedAuthorities.has(Authority.WORKSPACE_ADMIN) && + !permittedAuthorities.has(Authority.MANAGE_APPROVALS) && + secret.lastUpdatedById !== userId + ) { + throw new BadRequestException( + `The secret with id ${entity.id} is pending creation and cannot be fetched by the user with id ${userId}` + ) + } + + return secret + } +} diff --git a/apps/api/src/common/common.module.ts b/apps/api/src/common/common.module.ts index 01be450f..bacfc034 100644 --- a/apps/api/src/common/common.module.ts +++ b/apps/api/src/common/common.module.ts @@ -1,8 +1,9 @@ import { Global, Module } from '@nestjs/common' +import { AuthorityCheckerService } from './authority-checker.service' @Global() @Module({ - providers: [], - exports: [] + providers: [AuthorityCheckerService], + exports: [AuthorityCheckerService] }) export class CommonModule {} diff --git a/apps/api/src/common/get-environment-with-authority.ts b/apps/api/src/common/get-environment-with-authority.ts deleted file mode 100644 index 93cdb3ac..00000000 --- a/apps/api/src/common/get-environment-with-authority.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { - BadRequestException, - NotFoundException, - UnauthorizedException -} from '@nestjs/common' -import { Authority, Environment, PrismaClient, User } from '@prisma/client' -import getCollectiveProjectAuthorities from './get-collective-project-authorities' -import { EnvironmentWithProject } from 'src/environment/environment.types' - -export default async function getEnvironmentWithAuthority( - userId: User['id'], - environmentId: Environment['id'], - authority: Authority, - prisma: PrismaClient -): Promise { - // Fetch the environment - let environment: EnvironmentWithProject - - try { - environment = await prisma.environment.findUnique({ - where: { - id: environmentId - }, - include: { - project: true - } - }) - } catch (e) { - /* empty */ - } - - if (!environment) { - throw new NotFoundException( - `Environment with id ${environmentId} not found` - ) - } - - const permittedAuthorities = await getCollectiveProjectAuthorities( - userId, - environment.project, - prisma - ) - - // Check if the user has the required authorities - if ( - !permittedAuthorities.has(authority) && - !permittedAuthorities.has(Authority.WORKSPACE_ADMIN) - ) { - throw new UnauthorizedException( - `User ${userId} does not have the required authorities` - ) - } - - // If the environment is pending creation, only the user who created the environment, a workspace admin or - // a user with the MANAGE_APPROVALS authority can fetch the environment - if ( - environment.pendingCreation && - !permittedAuthorities.has(Authority.WORKSPACE_ADMIN) && - !permittedAuthorities.has(Authority.MANAGE_APPROVALS) && - environment.lastUpdatedById !== userId - ) { - throw new BadRequestException( - `The environment with id ${environmentId} is pending creation and cannot be fetched by the user with id ${userId}` - ) - } - - return environment -} diff --git a/apps/api/src/common/get-project-with-authority.ts b/apps/api/src/common/get-project-with-authority.ts deleted file mode 100644 index 9b291def..00000000 --- a/apps/api/src/common/get-project-with-authority.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { - BadRequestException, - NotFoundException, - UnauthorizedException -} from '@nestjs/common' -import { Authority, PrismaClient, Project, User } from '@prisma/client' -import getCollectiveProjectAuthorities from './get-collective-project-authorities' -import { ProjectWithSecrets } from '../project/project.types' - -export default async function getProjectWithAuthority( - userId: User['id'], - projectId: Project['id'], - authority: Authority, - prisma: PrismaClient -): Promise { - // Fetch the project - let project: ProjectWithSecrets - - // Fetch the project - try { - project = await prisma.project.findUnique({ - where: { - id: projectId - }, - include: { - secrets: true - } - }) - } catch (error) { - /* empty */ - } - - // If the project is not found, throw an error - if (!project) { - throw new NotFoundException(`Project with id ${projectId} not found`) - } - - // Get the authorities of the user in the workspace with the project - const permittedAuthorities = await getCollectiveProjectAuthorities( - userId, - project, - prisma - ) - - // If the user does not have the required authority, or is not a workspace admin, throw an error - if ( - !permittedAuthorities.has(authority) && - !permittedAuthorities.has(Authority.WORKSPACE_ADMIN) - ) { - throw new UnauthorizedException( - `User with id ${userId} does not have the authority in the project with id ${projectId}` - ) - } - - // If the project is pending creation, only the user who created the project, a workspace admin or - // a user with the MANAGE_APPROVALS authority can fetch the project - if ( - project.pendingCreation && - !permittedAuthorities.has(Authority.WORKSPACE_ADMIN) && - !permittedAuthorities.has(Authority.MANAGE_APPROVALS) && - project.lastUpdatedById !== userId - ) { - throw new BadRequestException( - `The project with id ${projectId} is pending creation and cannot be fetched by the user with id ${userId}` - ) - } - - return project -} diff --git a/apps/api/src/common/get-secret-with-authority.ts b/apps/api/src/common/get-secret-with-authority.ts deleted file mode 100644 index b964ad0d..00000000 --- a/apps/api/src/common/get-secret-with-authority.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { Authority, PrismaClient, Secret, User } from '@prisma/client' -import { SecretWithProjectAndVersion } from '../secret/secret.types' -import getCollectiveProjectAuthorities from './get-collective-project-authorities' -import { - BadRequestException, - NotFoundException, - UnauthorizedException -} from '@nestjs/common' - -export default async function getSecretWithAuthority( - userId: User['id'], - secretId: Secret['id'], - authority: Authority, - prisma: PrismaClient -): Promise { - // Fetch the secret - let secret: SecretWithProjectAndVersion - - try { - secret = await prisma.secret.findUnique({ - where: { - id: secretId - }, - include: { - versions: true, - project: true, - environment: { - select: { - id: true, - name: true - } - } - } - }) - } catch (error) { - /* empty */ - } - - if (!secret) { - throw new NotFoundException(`Secret with id ${secretId} not found`) - } - - // Check if the user has the project in their workspace role list - const permittedAuthorities = await getCollectiveProjectAuthorities( - userId, - secret.project, - prisma - ) - - // Check if the user has the required authorities - if ( - !permittedAuthorities.has(authority) && - !permittedAuthorities.has(Authority.WORKSPACE_ADMIN) - ) { - throw new UnauthorizedException( - `User ${userId} does not have the required authorities` - ) - } - - // If the secret is pending creation, only the user who created the secret, a workspace admin or - // a user with the MANAGE_APPROVALS authority can fetch the secret - if ( - secret.pendingCreation && - !permittedAuthorities.has(Authority.WORKSPACE_ADMIN) && - !permittedAuthorities.has(Authority.MANAGE_APPROVALS) && - secret.lastUpdatedById !== userId - ) { - throw new BadRequestException( - `The secret with id ${secretId} is pending creation and cannot be fetched by the user with id ${userId}` - ) - } - - return secret -} diff --git a/apps/api/src/common/get-variable-with-authority.ts b/apps/api/src/common/get-variable-with-authority.ts deleted file mode 100644 index 90dac1b1..00000000 --- a/apps/api/src/common/get-variable-with-authority.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { Authority, PrismaClient, User, Variable } from '@prisma/client' -import getCollectiveProjectAuthorities from './get-collective-project-authorities' -import { - BadRequestException, - NotFoundException, - UnauthorizedException -} from '@nestjs/common' -import { VariableWithProjectAndVersion } from '../variable/variable.types' - -export default async function getVariableWithAuthority( - userId: User['id'], - variableId: Variable['id'], - authority: Authority, - prisma: PrismaClient -): Promise { - // Fetch the variable - let variable: VariableWithProjectAndVersion - - try { - variable = await prisma.variable.findUnique({ - where: { - id: variableId - }, - include: { - versions: true, - project: true, - environment: { - select: { - id: true, - name: true - } - } - } - }) - } catch (error) { - /* empty */ - } - - if (!variable) { - throw new NotFoundException(`Variable with id ${variableId} not found`) - } - - // Check if the user has the project in their workspace role list - const permittedAuthorities = await getCollectiveProjectAuthorities( - userId, - variable.project, - prisma - ) - - // Check if the user has the required authorities - if ( - !permittedAuthorities.has(authority) && - !permittedAuthorities.has(Authority.WORKSPACE_ADMIN) - ) { - throw new UnauthorizedException( - `User ${userId} does not have the required authorities` - ) - } - - // If the variable is pending creation, only the user who created the variable, a workspace admin or - // a user with the MANAGE_APPROVALS authority can fetch the variable - if ( - variable.pendingCreation && - !permittedAuthorities.has(Authority.WORKSPACE_ADMIN) && - !permittedAuthorities.has(Authority.MANAGE_APPROVALS) && - variable.lastUpdatedById !== userId - ) { - throw new BadRequestException( - `The variable with id ${variableId} is pending creation and cannot be fetched by the user with id ${userId}` - ) - } - - return variable -} diff --git a/apps/api/src/common/get-workspace-with-authority.ts b/apps/api/src/common/get-workspace-with-authority.ts deleted file mode 100644 index e83203c5..00000000 --- a/apps/api/src/common/get-workspace-with-authority.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { Authority, PrismaClient, User, Workspace } from '@prisma/client' -import getCollectiveWorkspaceAuthorities from './get-collective-workspace-authorities' -import { NotFoundException, UnauthorizedException } from '@nestjs/common' - -export default async function getWorkspaceWithAuthority( - userId: User['id'], - workspaceId: Workspace['id'], - authority: Authority, - prisma: PrismaClient -): Promise { - let workspace: Workspace - - try { - workspace = await prisma.workspace.findUnique({ - where: { - id: workspaceId - } - }) - } catch (error) { - /* empty */ - } - - // Check if the workspace exists or not - if (!workspace) { - throw new NotFoundException(`Workspace with id ${workspaceId} not found`) - } - - const permittedAuthorities = await getCollectiveWorkspaceAuthorities( - workspaceId, - userId, - prisma - ) - - // Check if the user has the authority to perform the action - if ( - !permittedAuthorities.has(authority) && - !permittedAuthorities.has(Authority.WORKSPACE_ADMIN) - ) { - throw new UnauthorizedException( - `User ${userId} does not have the required authorities to perform the action` - ) - } - - return workspace -} diff --git a/apps/api/src/environment/controller/environment.controller.spec.ts b/apps/api/src/environment/controller/environment.controller.spec.ts index 0b5d37c6..329e260b 100644 --- a/apps/api/src/environment/controller/environment.controller.spec.ts +++ b/apps/api/src/environment/controller/environment.controller.spec.ts @@ -3,6 +3,7 @@ import { EnvironmentController } from './environment.controller' import { EnvironmentService } from '../service/environment.service' import { PrismaService } from '../../prisma/prisma.service' import { mockDeep } from 'jest-mock-extended' +import { AuthorityCheckerService } from '../../common/authority-checker.service' describe('EnvironmentController', () => { let controller: EnvironmentController @@ -10,7 +11,7 @@ describe('EnvironmentController', () => { beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [EnvironmentController], - providers: [EnvironmentService, PrismaService] + providers: [EnvironmentService, PrismaService, AuthorityCheckerService] }) .overrideProvider(PrismaService) .useValue(mockDeep()) diff --git a/apps/api/src/environment/service/environment.service.spec.ts b/apps/api/src/environment/service/environment.service.spec.ts index 8e3c2731..81bd3fff 100644 --- a/apps/api/src/environment/service/environment.service.spec.ts +++ b/apps/api/src/environment/service/environment.service.spec.ts @@ -2,13 +2,14 @@ import { Test, TestingModule } from '@nestjs/testing' import { EnvironmentService } from './environment.service' import { PrismaService } from '../../prisma/prisma.service' import { mockDeep } from 'jest-mock-extended' +import { AuthorityCheckerService } from '../../common/authority-checker.service' describe('EnvironmentService', () => { let service: EnvironmentService beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ - providers: [EnvironmentService, PrismaService] + providers: [EnvironmentService, PrismaService, AuthorityCheckerService] }) .overrideProvider(PrismaService) .useValue(mockDeep()) diff --git a/apps/api/src/environment/service/environment.service.ts b/apps/api/src/environment/service/environment.service.ts index dcb92c08..947c3786 100644 --- a/apps/api/src/environment/service/environment.service.ts +++ b/apps/api/src/environment/service/environment.service.ts @@ -17,17 +17,19 @@ import { import { CreateEnvironment } from '../dto/create.environment/create.environment' import { UpdateEnvironment } from '../dto/update.environment/update.environment' import { PrismaService } from '../../prisma/prisma.service' -import getProjectWithAuthority from '../../common/get-project-with-authority' -import getEnvironmentWithAuthority from '../../common/get-environment-with-authority' import createEvent from '../../common/create-event' import workspaceApprovalEnabled from '../../common/workspace-approval-enabled' import { EnvironmentWithProject } from '../environment.types' import createApproval from '../../common/create-approval' import { UpdateEnvironmentMetadata } from '../../approval/approval.types' +import { AuthorityCheckerService } from '../../common/authority-checker.service' @Injectable() export class EnvironmentService { - constructor(private readonly prisma: PrismaService) {} + constructor( + private readonly prisma: PrismaService, + public authorityCheckerService: AuthorityCheckerService + ) {} async createEnvironment( user: User, @@ -36,12 +38,13 @@ export class EnvironmentService { reason?: string ) { // Check if the user has the required role to create an environment - const project = await getProjectWithAuthority( - user.id, - projectId, - Authority.CREATE_ENVIRONMENT, - this.prisma - ) + const project = + await this.authorityCheckerService.checkAuthorityOverProject({ + userId: user.id, + entity: { id: projectId }, + authority: Authority.CREATE_ENVIRONMENT, + prisma: this.prisma + }) // Check if an environment with the same name already exists if (await this.environmentExists(dto.name, projectId)) { @@ -136,12 +139,13 @@ export class EnvironmentService { environmentId: Environment['id'], reason?: string ) { - const environment = await getEnvironmentWithAuthority( - user.id, - environmentId, - Authority.UPDATE_ENVIRONMENT, - this.prisma - ) + const environment = + await this.authorityCheckerService.checkAuthorityOverEnvironment({ + userId: user.id, + entity: { id: environmentId }, + authority: Authority.UPDATE_ENVIRONMENT, + prisma: this.prisma + }) // Check if an environment with the same name already exists if ( @@ -179,12 +183,13 @@ export class EnvironmentService { } async getEnvironment(user: User, environmentId: Environment['id']) { - const environment = await getEnvironmentWithAuthority( - user.id, - environmentId, - Authority.READ_ENVIRONMENT, - this.prisma - ) + const environment = + await this.authorityCheckerService.checkAuthorityOverEnvironment({ + userId: user.id, + entity: { id: environmentId }, + authority: Authority.READ_ENVIRONMENT, + prisma: this.prisma + }) return environment } @@ -198,12 +203,12 @@ export class EnvironmentService { order: string, search: string ) { - await getProjectWithAuthority( - user.id, - projectId, - Authority.READ_ENVIRONMENT, - this.prisma - ) + await this.authorityCheckerService.checkAuthorityOverProject({ + userId: user.id, + entity: { id: projectId }, + authority: Authority.READ_ENVIRONMENT, + prisma: this.prisma + }) // Get the environments return await this.prisma.environment.findMany({ @@ -230,12 +235,13 @@ export class EnvironmentService { environmentId: Environment['id'], reason?: string ) { - const environment = await getEnvironmentWithAuthority( - user.id, - environmentId, - Authority.DELETE_ENVIRONMENT, - this.prisma - ) + const environment = + await this.authorityCheckerService.checkAuthorityOverEnvironment({ + userId: user.id, + entity: { id: environmentId }, + authority: Authority.DELETE_ENVIRONMENT, + prisma: this.prisma + }) // Check if the environment is the default one if (environment.isDefault) { diff --git a/apps/api/src/event/controller/event.controller.spec.ts b/apps/api/src/event/controller/event.controller.spec.ts index 94c1b6ef..83a78515 100644 --- a/apps/api/src/event/controller/event.controller.spec.ts +++ b/apps/api/src/event/controller/event.controller.spec.ts @@ -2,6 +2,7 @@ import { Test, TestingModule } from '@nestjs/testing' import { EventController } from './event.controller' import { EventService } from '../service/event.service' import { PrismaService } from '../../prisma/prisma.service' +import { AuthorityCheckerService } from '../../common/authority-checker.service' describe('EventController', () => { let controller: EventController @@ -9,7 +10,7 @@ describe('EventController', () => { beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [EventController], - providers: [EventService, PrismaService] + providers: [EventService, PrismaService, AuthorityCheckerService] }).compile() controller = module.get(EventController) diff --git a/apps/api/src/event/event.module.ts b/apps/api/src/event/event.module.ts index c8d9823f..f5500f69 100644 --- a/apps/api/src/event/event.module.ts +++ b/apps/api/src/event/event.module.ts @@ -1,8 +1,10 @@ import { Module } from '@nestjs/common' import { EventService } from './service/event.service' import { EventController } from './controller/event.controller' +import { CommonModule } from 'src/common/common.module' @Module({ + imports: [CommonModule], providers: [EventService], controllers: [EventController] }) diff --git a/apps/api/src/event/service/event.service.spec.ts b/apps/api/src/event/service/event.service.spec.ts index 4032d3c1..81aedc74 100644 --- a/apps/api/src/event/service/event.service.spec.ts +++ b/apps/api/src/event/service/event.service.spec.ts @@ -1,13 +1,14 @@ import { Test, TestingModule } from '@nestjs/testing' import { EventService } from './event.service' import { PrismaService } from '../../prisma/prisma.service' +import { AuthorityCheckerService } from '../../common/authority-checker.service' describe('EventService', () => { let service: EventService beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ - providers: [EventService, PrismaService] + providers: [EventService, PrismaService, AuthorityCheckerService] }).compile() service = module.get(EventService) diff --git a/apps/api/src/event/service/event.service.ts b/apps/api/src/event/service/event.service.ts index 0336cceb..f25c5e6a 100644 --- a/apps/api/src/event/service/event.service.ts +++ b/apps/api/src/event/service/event.service.ts @@ -1,11 +1,14 @@ import { BadRequestException, Injectable } from '@nestjs/common' import { Authority, EventSeverity, EventSource, User } from '@prisma/client' -import getWorkspaceWithAuthority from '../../common/get-workspace-with-authority' import { PrismaService } from '../../prisma/prisma.service' +import { AuthorityCheckerService } from '../../common/authority-checker.service' @Injectable() export class EventService { - constructor(private readonly prisma: PrismaService) {} + constructor( + private readonly prisma: PrismaService, + public authorityCheckerService: AuthorityCheckerService + ) {} async getEvents( user: User, @@ -25,12 +28,12 @@ export class EventService { } // Check for workspace authority - await getWorkspaceWithAuthority( - user.id, - workspaceId, - Authority.READ_EVENT, - this.prisma - ) + await this.authorityCheckerService.checkAuthorityOverWorkspace({ + userId: user.id, + entity: { id: workspaceId }, + authority: Authority.READ_EVENT, + prisma: this.prisma + }) const query = { where: { diff --git a/apps/api/src/project/controller/project.controller.spec.ts b/apps/api/src/project/controller/project.controller.spec.ts index 85ee649f..fb4b7c84 100644 --- a/apps/api/src/project/controller/project.controller.spec.ts +++ b/apps/api/src/project/controller/project.controller.spec.ts @@ -5,6 +5,7 @@ import { MAIL_SERVICE } from '../../mail/services/interface.service' import { MockMailService } from '../../mail/services/mock.service' import { PrismaService } from '../../prisma/prisma.service' import { mockDeep } from 'jest-mock-extended' +import { AuthorityCheckerService } from '../../common/authority-checker.service' describe('ProjectController', () => { let controller: ProjectController @@ -15,7 +16,8 @@ describe('ProjectController', () => { providers: [ ProjectService, PrismaService, - { provide: MAIL_SERVICE, useClass: MockMailService } + { provide: MAIL_SERVICE, useClass: MockMailService }, + AuthorityCheckerService ] }) .overrideProvider(PrismaService) diff --git a/apps/api/src/project/project.module.ts b/apps/api/src/project/project.module.ts index 4737691a..6ebc2879 100644 --- a/apps/api/src/project/project.module.ts +++ b/apps/api/src/project/project.module.ts @@ -4,10 +4,11 @@ import { ProjectController } from './controller/project.controller' import { EnvironmentModule } from '../environment/environment.module' import { UserModule } from '../user/user.module' import { SecretModule } from '../secret/secret.module' +import { CommonModule } from 'src/common/common.module' @Module({ providers: [ProjectService], controllers: [ProjectController], - imports: [UserModule, EnvironmentModule, SecretModule] + imports: [UserModule, EnvironmentModule, SecretModule, CommonModule] }) export class ProjectModule {} diff --git a/apps/api/src/project/service/project.service.spec.ts b/apps/api/src/project/service/project.service.spec.ts index fd3d08b0..b32af0c1 100644 --- a/apps/api/src/project/service/project.service.spec.ts +++ b/apps/api/src/project/service/project.service.spec.ts @@ -4,6 +4,7 @@ import { MockMailService } from '../../mail/services/mock.service' import { MAIL_SERVICE } from '../../mail/services/interface.service' import { PrismaService } from '../../prisma/prisma.service' import { mockDeep } from 'jest-mock-extended' +import { AuthorityCheckerService } from '../../common/authority-checker.service' describe('ProjectService', () => { let service: ProjectService @@ -13,7 +14,8 @@ describe('ProjectService', () => { providers: [ ProjectService, PrismaService, - { provide: MAIL_SERVICE, useClass: MockMailService } + { provide: MAIL_SERVICE, useClass: MockMailService }, + AuthorityCheckerService ] }) .overrideProvider(PrismaService) diff --git a/apps/api/src/project/service/project.service.ts b/apps/api/src/project/service/project.service.ts index 337381b4..ef7ee18c 100644 --- a/apps/api/src/project/service/project.service.ts +++ b/apps/api/src/project/service/project.service.ts @@ -18,20 +18,22 @@ import { excludeFields } from '../../common/exclude-fields' import { PrismaService } from '../../prisma/prisma.service' import { decrypt } from '../../common/decrypt' import { encrypt } from '../../common/encrypt' -import getWorkspaceWithAuthority from '../../common/get-workspace-with-authority' -import getProjectWithAuthority from '../../common/get-project-with-authority' import { v4 } from 'uuid' import createEvent from '../../common/create-event' import workspaceApprovalEnabled from '../../common/workspace-approval-enabled' import createApproval from '../../common/create-approval' import { UpdateProjectMetadata } from '../../approval/approval.types' import { ProjectWithSecrets } from '../project.types' +import { AuthorityCheckerService } from '../../common/authority-checker.service' @Injectable() export class ProjectService { private readonly log: Logger = new Logger(ProjectService.name) - constructor(private readonly prisma: PrismaService) {} + constructor( + private readonly prisma: PrismaService, + public authorityCheckerService: AuthorityCheckerService + ) {} async createProject( user: User, @@ -40,12 +42,13 @@ export class ProjectService { reason?: string ) { // Check if the workspace exists or not - const workspace = await getWorkspaceWithAuthority( - user.id, - workspaceId, - Authority.CREATE_PROJECT, - this.prisma - ) + const workspace = + await this.authorityCheckerService.checkAuthorityOverWorkspace({ + userId: user.id, + entity: { id: workspaceId }, + authority: Authority.CREATE_PROJECT, + prisma: this.prisma + }) // Check if project with this name already exists for the user if (await this.projectExists(dto.name, workspaceId)) @@ -219,12 +222,13 @@ export class ProjectService { dto: UpdateProject, reason?: string ) { - const project = await getProjectWithAuthority( - user.id, - projectId, - Authority.UPDATE_PROJECT, - this.prisma - ) + const project = + await this.authorityCheckerService.checkAuthorityOverProject({ + userId: user.id, + entity: { id: projectId }, + authority: Authority.UPDATE_PROJECT, + prisma: this.prisma + }) // Check if project with this name already exists for the user if ( @@ -257,12 +261,13 @@ export class ProjectService { } async deleteProject(user: User, projectId: Project['id'], reason?: string) { - const project = await getProjectWithAuthority( - user.id, - projectId, - Authority.DELETE_PROJECT, - this.prisma - ) + const project = + await this.authorityCheckerService.checkAuthorityOverProject({ + userId: user.id, + entity: { id: projectId }, + authority: Authority.DELETE_PROJECT, + prisma: this.prisma + }) if (await workspaceApprovalEnabled(project.workspaceId, this.prisma)) { return await createApproval( @@ -282,12 +287,13 @@ export class ProjectService { } async getProjectByUserAndId(user: User, projectId: Project['id']) { - const project = await getProjectWithAuthority( - user.id, - projectId, - Authority.READ_PROJECT, - this.prisma - ) + const project = + await this.authorityCheckerService.checkAuthorityOverProject({ + userId: user.id, + entity: { id: projectId }, + authority: Authority.READ_PROJECT, + prisma: this.prisma + }) return project } @@ -301,12 +307,12 @@ export class ProjectService { order: string, search: string ) { - await getWorkspaceWithAuthority( - user.id, - workspaceId, - Authority.READ_PROJECT, - this.prisma - ) + await this.authorityCheckerService.checkAuthorityOverWorkspace({ + userId: user.id, + entity: { id: workspaceId }, + authority: Authority.READ_PROJECT, + prisma: this.prisma + }) return ( await this.prisma.project.findMany({ diff --git a/apps/api/src/secret/controller/secret.controller.spec.ts b/apps/api/src/secret/controller/secret.controller.spec.ts index 69c1de26..3d6fc2b0 100644 --- a/apps/api/src/secret/controller/secret.controller.spec.ts +++ b/apps/api/src/secret/controller/secret.controller.spec.ts @@ -8,6 +8,7 @@ import { mockDeep } from 'jest-mock-extended' import { REDIS_CLIENT } from '../../provider/redis.provider' import { RedisClientType } from 'redis' import { ProviderModule } from '../../provider/provider.module' +import { AuthorityCheckerService } from '../../common/authority-checker.service' describe('SecretController', () => { let controller: SecretController @@ -22,7 +23,8 @@ describe('SecretController', () => { provide: MAIL_SERVICE, useClass: MockMailService }, - SecretService + SecretService, + AuthorityCheckerService ] }) .overrideProvider(REDIS_CLIENT) diff --git a/apps/api/src/secret/service/secret.service.spec.ts b/apps/api/src/secret/service/secret.service.spec.ts index 408b4ae6..8daef440 100644 --- a/apps/api/src/secret/service/secret.service.spec.ts +++ b/apps/api/src/secret/service/secret.service.spec.ts @@ -7,6 +7,7 @@ import { mockDeep } from 'jest-mock-extended' import { REDIS_CLIENT } from '../../provider/redis.provider' import { RedisClientType } from 'redis' import { ProviderModule } from '../../provider/provider.module' +import { AuthorityCheckerService } from '../../common/authority-checker.service' describe('SecretService', () => { let service: SecretService @@ -20,7 +21,8 @@ describe('SecretService', () => { provide: MAIL_SERVICE, useClass: MockMailService }, - SecretService + SecretService, + AuthorityCheckerService ] }) .overrideProvider(REDIS_CLIENT) diff --git a/apps/api/src/secret/service/secret.service.ts b/apps/api/src/secret/service/secret.service.ts index ffc342c3..22696bb2 100644 --- a/apps/api/src/secret/service/secret.service.ts +++ b/apps/api/src/secret/service/secret.service.ts @@ -25,9 +25,6 @@ import { decrypt } from '../../common/decrypt' import { PrismaService } from '../../prisma/prisma.service' import { addHoursToDate } from '../../common/add-hours-to-date' import { encrypt } from '../../common/encrypt' -import getProjectWithAuthority from '../../common/get-project-with-authority' -import getEnvironmentWithAuthority from '../../common/get-environment-with-authority' -import getSecretWithAuthority from '../../common/get-secret-with-authority' import { SecretWithProject, SecretWithProjectAndVersion, @@ -41,6 +38,7 @@ import { UpdateSecretMetadata } from '../../approval/approval.types' import { RedisClientType } from 'redis' import { REDIS_CLIENT } from '../../provider/redis.provider' import { CHANGE_NOTIFIER_RSC } from '../../socket/change-notifier.socket' +import { AuthorityCheckerService } from '../../common/authority-checker.service' @Injectable() export class SecretService { @@ -52,7 +50,8 @@ export class SecretService { @Inject(REDIS_CLIENT) readonly redisClient: { publisher: RedisClientType - } + }, + public authorityCheckerService: AuthorityCheckerService ) { this.redis = redisClient.publisher } @@ -65,22 +64,24 @@ export class SecretService { ) { const environmentId = dto.environmentId // Fetch the project - const project = await getProjectWithAuthority( - user.id, - projectId, - Authority.CREATE_SECRET, - this.prisma - ) + const project = + await this.authorityCheckerService.checkAuthorityOverProject({ + userId: user.id, + entity: { id: projectId }, + authority: Authority.CREATE_SECRET, + prisma: this.prisma + }) // Check if the environment exists let environment: Environment | null = null if (environmentId) { - environment = await getEnvironmentWithAuthority( - user.id, - environmentId, - Authority.READ_ENVIRONMENT, - this.prisma - ) + environment = + await this.authorityCheckerService.checkAuthorityOverEnvironment({ + userId: user.id, + entity: { id: environmentId }, + authority: Authority.READ_ENVIRONMENT, + prisma: this.prisma + }) } if (!environment) { environment = await getDefaultEnvironmentOfProject(projectId, this.prisma) @@ -193,12 +194,12 @@ export class SecretService { dto: UpdateSecret, reason?: string ) { - const secret = await getSecretWithAuthority( - user.id, - secretId, - Authority.UPDATE_SECRET, - this.prisma - ) + const secret = await this.authorityCheckerService.checkAuthorityOverSecret({ + userId: user.id, + entity: { id: secretId }, + authority: Authority.UPDATE_SECRET, + prisma: this.prisma + }) // Check if the secret already exists in the environment if ( @@ -242,12 +243,12 @@ export class SecretService { environmentId: Environment['id'], reason?: string ) { - const secret = await getSecretWithAuthority( - user.id, - secretId, - Authority.UPDATE_SECRET, - this.prisma - ) + const secret = await this.authorityCheckerService.checkAuthorityOverSecret({ + userId: user.id, + entity: { id: secretId }, + authority: Authority.UPDATE_SECRET, + prisma: this.prisma + }) if (secret.environmentId === environmentId) { throw new BadRequestException( @@ -256,12 +257,13 @@ export class SecretService { } // Check if the environment exists - const environment = await getEnvironmentWithAuthority( - user.id, - environmentId, - Authority.READ_ENVIRONMENT, - this.prisma - ) + const environment = + await this.authorityCheckerService.checkAuthorityOverEnvironment({ + userId: user.id, + entity: { id: environmentId }, + authority: Authority.READ_ENVIRONMENT, + prisma: this.prisma + }) if (environment.projectId !== secret.projectId) { throw new BadRequestException( @@ -306,12 +308,12 @@ export class SecretService { reason?: string ) { // Fetch the secret - const secret = await getSecretWithAuthority( - user.id, - secretId, - Authority.UPDATE_SECRET, - this.prisma - ) + const secret = await this.authorityCheckerService.checkAuthorityOverSecret({ + userId: user.id, + entity: { id: secretId }, + authority: Authority.UPDATE_SECRET, + prisma: this.prisma + }) const maxVersion = secret.versions[secret.versions.length - 1].version @@ -347,12 +349,12 @@ export class SecretService { async deleteSecret(user: User, secretId: Secret['id'], reason?: string) { // Check if the user has the required role - const secret = await getSecretWithAuthority( - user.id, - secretId, - Authority.DELETE_SECRET, - this.prisma - ) + const secret = await this.authorityCheckerService.checkAuthorityOverSecret({ + userId: user.id, + entity: { id: secretId }, + authority: Authority.DELETE_SECRET, + prisma: this.prisma + }) if ( !secret.pendingCreation && @@ -380,12 +382,12 @@ export class SecretService { decryptValue: boolean ) { // Fetch the secret - const secret = await getSecretWithAuthority( - user.id, - secretId, - Authority.READ_SECRET, - this.prisma - ) + const secret = await this.authorityCheckerService.checkAuthorityOverSecret({ + userId: user.id, + entity: { id: secretId }, + authority: Authority.READ_SECRET, + prisma: this.prisma + }) const project = secret.project @@ -430,12 +432,13 @@ export class SecretService { search: string ) { // Fetch the project - const project = await getProjectWithAuthority( - user.id, - projectId, - Authority.READ_SECRET, - this.prisma - ) + const project = + await this.authorityCheckerService.checkAuthorityOverProject({ + userId: user.id, + entity: { id: projectId }, + authority: Authority.READ_SECRET, + prisma: this.prisma + }) // Check if the project is allowed to store the private key if (decryptValue && !project.storePrivateKey) { diff --git a/apps/api/src/socket/change-notifier.socket.ts b/apps/api/src/socket/change-notifier.socket.ts index 8b817667..c4cf10e9 100644 --- a/apps/api/src/socket/change-notifier.socket.ts +++ b/apps/api/src/socket/change-notifier.socket.ts @@ -15,10 +15,7 @@ import { ChangeNotifierRegistration } from './socket.types' import { Authority, User } from '@prisma/client' -import getWorkspaceWithAuthority from '../common/get-workspace-with-authority' import { CurrentUser } from '../decorators/user.decorator' -import getProjectWithAuthority from '../common/get-project-with-authority' -import getEnvironmentWithAuthority from '../common/get-environment-with-authority' import { PrismaService } from '../prisma/prisma.service' import { REDIS_CLIENT } from '../provider/redis.provider' import { RedisClientType } from 'redis' @@ -26,6 +23,7 @@ import { ApiKeyGuard } from '../auth/guard/api-key/api-key.guard' import { AuthGuard } from '../auth/guard/auth/auth.guard' import { RequiredApiKeyAuthorities } from '../decorators/required-api-key-authorities.decorator' import { Cron, CronExpression } from '@nestjs/schedule' +import { AuthorityCheckerService } from '../common/authority-checker.service' // The redis subscription channel for configuration updates export const CHANGE_NOTIFIER_RSC = 'configuration-updates' @@ -52,7 +50,8 @@ export default class ChangeNotifier subscriber: RedisClientType publisher: RedisClientType }, - private readonly prisma: PrismaService + private readonly prisma: PrismaService, + public authorityCheckerService: AuthorityCheckerService ) { this.redis = redisClient.publisher this.redisSubscriber = redisClient.subscriber @@ -106,12 +105,12 @@ export default class ChangeNotifier name: data.workspaceName } }) - await getWorkspaceWithAuthority( - user.id, - workspace.id, - Authority.READ_WORKSPACE, - this.prisma - ) + await this.authorityCheckerService.checkAuthorityOverWorkspace({ + userId: user.id, + entity: { id: workspace.id }, + authority: Authority.READ_WORKSPACE, + prisma: this.prisma + }) // Check if the user has access to the project const project = await this.prisma.project.findFirst({ @@ -119,12 +118,12 @@ export default class ChangeNotifier name: data.projectName } }) - await getProjectWithAuthority( - user.id, - project.id, - Authority.READ_PROJECT, - this.prisma - ) + await this.authorityCheckerService.checkAuthorityOverProject({ + userId: user.id, + entity: { id: project.id }, + authority: Authority.READ_PROJECT, + prisma: this.prisma + }) // Check if the user has access to the environment const environment = await this.prisma.environment.findFirst({ @@ -132,12 +131,12 @@ export default class ChangeNotifier name: data.environmentName } }) - await getEnvironmentWithAuthority( - user.id, - environment.id, - Authority.READ_ENVIRONMENT, - this.prisma - ) + await this.authorityCheckerService.checkAuthorityOverEnvironment({ + userId: user.id, + entity: { id: environment.id }, + authority: Authority.READ_ENVIRONMENT, + prisma: this.prisma + }) // Add the client to the environment await this.addClientToEnvironment(client, environment.id) diff --git a/apps/api/src/socket/socket.module.ts b/apps/api/src/socket/socket.module.ts index 0171a914..48a63510 100644 --- a/apps/api/src/socket/socket.module.ts +++ b/apps/api/src/socket/socket.module.ts @@ -1,7 +1,9 @@ import { Module } from '@nestjs/common' import ChangeNotifier from './change-notifier.socket' +import { CommonModule } from 'src/common/common.module' @Module({ + imports: [CommonModule], providers: [ChangeNotifier] }) export class SocketModule {} diff --git a/apps/api/src/variable/controller/variable.controller.spec.ts b/apps/api/src/variable/controller/variable.controller.spec.ts index 233655ec..710a547e 100644 --- a/apps/api/src/variable/controller/variable.controller.spec.ts +++ b/apps/api/src/variable/controller/variable.controller.spec.ts @@ -8,6 +8,7 @@ import { REDIS_CLIENT } from '../../provider/redis.provider' import { RedisClientType } from 'redis' import { mockDeep } from 'jest-mock-extended' import { ProviderModule } from '../../provider/provider.module' +import { AuthorityCheckerService } from '../../common/authority-checker.service' describe('VariableController', () => { let controller: VariableController @@ -21,7 +22,8 @@ describe('VariableController', () => { provide: MAIL_SERVICE, useClass: MockMailService }, - VariableService + VariableService, + AuthorityCheckerService ], controllers: [VariableController] }) diff --git a/apps/api/src/variable/service/variable.service.spec.ts b/apps/api/src/variable/service/variable.service.spec.ts index da77e3de..8fb6ca74 100644 --- a/apps/api/src/variable/service/variable.service.spec.ts +++ b/apps/api/src/variable/service/variable.service.spec.ts @@ -7,6 +7,7 @@ import { REDIS_CLIENT } from '../../provider/redis.provider' import { RedisClientType } from 'redis' import { mockDeep } from 'jest-mock-extended' import { ProviderModule } from '../../provider/provider.module' +import { AuthorityCheckerService } from '../../common/authority-checker.service' describe('VariableService', () => { let service: VariableService @@ -20,7 +21,8 @@ describe('VariableService', () => { provide: MAIL_SERVICE, useClass: MockMailService }, - VariableService + VariableService, + AuthorityCheckerService ] }) .overrideProvider(REDIS_CLIENT) diff --git a/apps/api/src/variable/service/variable.service.ts b/apps/api/src/variable/service/variable.service.ts index 53534251..06e526f4 100644 --- a/apps/api/src/variable/service/variable.service.ts +++ b/apps/api/src/variable/service/variable.service.ts @@ -21,12 +21,9 @@ import { VariableVersion } from '@prisma/client' import { CreateVariable } from '../dto/create.variable/create.variable' -import getProjectWithAuthority from '../../common/get-project-with-authority' -import getEnvironmentWithAuthority from '../../common/get-environment-with-authority' import getDefaultEnvironmentOfProject from '../../common/get-default-project-environment' import createEvent from '../../common/create-event' import { UpdateVariable } from '../dto/update.variable/update.variable' -import getVariableWithAuthority from '../../common/get-variable-with-authority' import workspaceApprovalEnabled from '../../common/workspace-approval-enabled' import createApproval from '../../common/create-approval' import { UpdateVariableMetadata } from '../../approval/approval.types' @@ -37,6 +34,7 @@ import { import { RedisClientType } from 'redis' import { REDIS_CLIENT } from '../../provider/redis.provider' import { CHANGE_NOTIFIER_RSC } from '../../socket/change-notifier.socket' +import { AuthorityCheckerService } from '../../common/authority-checker.service' @Injectable() export class VariableService { @@ -48,7 +46,8 @@ export class VariableService { @Inject(REDIS_CLIENT) readonly redisClient: { publisher: RedisClientType - } + }, + public authorityCheckerService: AuthorityCheckerService ) { this.redis = redisClient.publisher } @@ -61,22 +60,24 @@ export class VariableService { ) { const environmentId = dto.environmentId // Fetch the project - const project = await getProjectWithAuthority( - user.id, - projectId, - Authority.CREATE_VARIABLE, - this.prisma - ) + const project = + await this.authorityCheckerService.checkAuthorityOverProject({ + userId: user.id, + entity: { id: projectId }, + authority: Authority.CREATE_VARIABLE, + prisma: this.prisma + }) // Check i the environment exists let environment: Environment | null = null if (environmentId) { - environment = await getEnvironmentWithAuthority( - user.id, - environmentId, - Authority.READ_ENVIRONMENT, - this.prisma - ) + environment = + await this.authorityCheckerService.checkAuthorityOverEnvironment({ + userId: user.id, + entity: { id: environmentId }, + authority: Authority.READ_ENVIRONMENT, + prisma: this.prisma + }) } if (!environment) { environment = await getDefaultEnvironmentOfProject(projectId, this.prisma) @@ -199,12 +200,13 @@ export class VariableService { dto: UpdateVariable, reason?: string ) { - const variable = await getVariableWithAuthority( - user.id, - variableId, - Authority.UPDATE_VARIABLE, - this.prisma - ) + const variable = + await this.authorityCheckerService.checkAuthorityOverVariable({ + userId: user.id, + entity: { id: variableId }, + authority: Authority.UPDATE_VARIABLE, + prisma: this.prisma + }) // Check if the variable already exists in the environment if ( @@ -247,12 +249,13 @@ export class VariableService { environmentId: Environment['id'], reason?: string ) { - const variable = await getVariableWithAuthority( - user.id, - variableId, - Authority.UPDATE_VARIABLE, - this.prisma - ) + const variable = + await this.authorityCheckerService.checkAuthorityOverVariable({ + userId: user.id, + entity: { id: variableId }, + authority: Authority.UPDATE_VARIABLE, + prisma: this.prisma + }) if (variable.environmentId === environmentId) { throw new BadRequestException( @@ -261,12 +264,13 @@ export class VariableService { } // Check if the environment exists - const environment = await getEnvironmentWithAuthority( - user.id, - environmentId, - Authority.READ_ENVIRONMENT, - this.prisma - ) + const environment = + await this.authorityCheckerService.checkAuthorityOverEnvironment({ + userId: user.id, + entity: { id: environmentId }, + authority: Authority.READ_ENVIRONMENT, + prisma: this.prisma + }) // Check if the environment belongs to the same project if (environment.projectId !== variable.projectId) { @@ -313,12 +317,13 @@ export class VariableService { rollbackVersion: VariableVersion['version'], reason?: string ) { - const variable = await getVariableWithAuthority( - user.id, - variableId, - Authority.UPDATE_VARIABLE, - this.prisma - ) + const variable = + await this.authorityCheckerService.checkAuthorityOverVariable({ + userId: user.id, + entity: { id: variableId }, + authority: Authority.UPDATE_VARIABLE, + prisma: this.prisma + }) const maxVersion = variable.versions[variable.versions.length - 1].version @@ -360,12 +365,13 @@ export class VariableService { variableId: Variable['id'], reason?: string ) { - const variable = await getVariableWithAuthority( - user.id, - variableId, - Authority.DELETE_VARIABLE, - this.prisma - ) + const variable = + await this.authorityCheckerService.checkAuthorityOverVariable({ + userId: user.id, + entity: { id: variableId }, + authority: Authority.DELETE_VARIABLE, + prisma: this.prisma + }) if ( !variable.pendingCreation && @@ -391,12 +397,12 @@ export class VariableService { } async getVariableById(user: User, variableId: Variable['id']) { - return getVariableWithAuthority( - user.id, - variableId, - Authority.READ_VARIABLE, - this.prisma - ) + return this.authorityCheckerService.checkAuthorityOverVariable({ + userId: user.id, + entity: { id: variableId }, + authority: Authority.READ_VARIABLE, + prisma: this.prisma + }) } async getAllVariablesOfProject( @@ -409,12 +415,12 @@ export class VariableService { search: string ) { // Check if the user has the required authorities in the project - await getProjectWithAuthority( - user.id, - projectId, - Authority.READ_VARIABLE, - this.prisma - ) + await this.authorityCheckerService.checkAuthorityOverProject({ + userId: user.id, + entity: { id: projectId }, + authority: Authority.READ_VARIABLE, + prisma: this.prisma + }) return await this.prisma.variable.findMany({ where: { diff --git a/apps/api/src/workspace-role/controller/workspace-role.controller.spec.ts b/apps/api/src/workspace-role/controller/workspace-role.controller.spec.ts index e879a6cd..c48dd4e5 100644 --- a/apps/api/src/workspace-role/controller/workspace-role.controller.spec.ts +++ b/apps/api/src/workspace-role/controller/workspace-role.controller.spec.ts @@ -4,6 +4,7 @@ import { MockMailService } from '../../mail/services/mock.service' import { MAIL_SERVICE } from '../../mail/services/interface.service' import { PrismaService } from '../../prisma/prisma.service' import { WorkspaceRoleService } from '../service/workspace-role.service' +import { AuthorityCheckerService } from '../../common/authority-checker.service' describe('WorkspaceRoleController', () => { let controller: WorkspaceRoleController @@ -13,7 +14,8 @@ describe('WorkspaceRoleController', () => { providers: [ WorkspaceRoleService, PrismaService, - { provide: MAIL_SERVICE, useClass: MockMailService } + { provide: MAIL_SERVICE, useClass: MockMailService }, + AuthorityCheckerService ], controllers: [WorkspaceRoleController] }).compile() diff --git a/apps/api/src/workspace-role/service/workspace-role.service.spec.ts b/apps/api/src/workspace-role/service/workspace-role.service.spec.ts index 518c24d4..9b36a923 100644 --- a/apps/api/src/workspace-role/service/workspace-role.service.spec.ts +++ b/apps/api/src/workspace-role/service/workspace-role.service.spec.ts @@ -3,6 +3,7 @@ import { WorkspaceRoleService } from './workspace-role.service' import { PrismaService } from '../../prisma/prisma.service' import { MAIL_SERVICE } from '../../mail/services/interface.service' import { MockMailService } from '../../mail/services/mock.service' +import { AuthorityCheckerService } from '../../common/authority-checker.service' describe('WorkspaceRoleService', () => { let service: WorkspaceRoleService @@ -12,7 +13,8 @@ describe('WorkspaceRoleService', () => { providers: [ WorkspaceRoleService, PrismaService, - { provide: MAIL_SERVICE, useClass: MockMailService } + { provide: MAIL_SERVICE, useClass: MockMailService }, + AuthorityCheckerService ] }).compile() diff --git a/apps/api/src/workspace-role/service/workspace-role.service.ts b/apps/api/src/workspace-role/service/workspace-role.service.ts index 2ee4de70..a978f128 100644 --- a/apps/api/src/workspace-role/service/workspace-role.service.ts +++ b/apps/api/src/workspace-role/service/workspace-role.service.ts @@ -15,19 +15,22 @@ import { WorkspaceRole } from '@prisma/client' import { CreateWorkspaceRole } from '../dto/create-workspace-role/create-workspace-role' -import getWorkspaceWithAuthority from '../../common/get-workspace-with-authority' import getCollectiveWorkspaceAuthorities from '../../common/get-collective-workspace-authorities' import { UpdateWorkspaceRole } from '../dto/update-workspace-role/update-workspace-role' import { PrismaService } from '../../prisma/prisma.service' import createEvent from '../../common/create-event' import { WorkspaceRoleWithProjects } from '../workspace-role.types' import { v4 } from 'uuid' +import { AuthorityCheckerService } from '../../common/authority-checker.service' @Injectable() export class WorkspaceRoleService { private readonly logger: Logger = new Logger(WorkspaceRoleService.name) - constructor(private readonly prisma: PrismaService) {} + constructor( + private readonly prisma: PrismaService, + public authorityCheckerService: AuthorityCheckerService + ) {} async createWorkspaceRole( user: User, @@ -43,12 +46,13 @@ export class WorkspaceRoleService { ) } - const workspace = await getWorkspaceWithAuthority( - user.id, - workspaceId, - Authority.CREATE_WORKSPACE_ROLE, - this.prisma - ) + const workspace = + await this.authorityCheckerService.checkAuthorityOverWorkspace({ + userId: user.id, + entity: { id: workspaceId }, + authority: Authority.CREATE_WORKSPACE_ROLE, + prisma: this.prisma + }) if (await this.checkWorkspaceRoleExists(user, workspaceId, dto.name)) { throw new ConflictException( @@ -263,12 +267,12 @@ export class WorkspaceRoleService { workspaceId: Workspace['id'], name: string ) { - await getWorkspaceWithAuthority( - user.id, - workspaceId, - Authority.READ_WORKSPACE_ROLE, - this.prisma - ) + await this.authorityCheckerService.checkAuthorityOverWorkspace({ + userId: user.id, + entity: { id: workspaceId }, + authority: Authority.READ_WORKSPACE_ROLE, + prisma: this.prisma + }) return ( (await this.prisma.workspaceRole.count({ @@ -300,12 +304,12 @@ export class WorkspaceRoleService { order: string, search: string ): Promise { - await getWorkspaceWithAuthority( - user.id, - workspaceId, - Authority.READ_WORKSPACE_ROLE, - this.prisma - ) + await this.authorityCheckerService.checkAuthorityOverWorkspace({ + userId: user.id, + entity: { id: workspaceId }, + authority: Authority.READ_WORKSPACE_ROLE, + prisma: this.prisma + }) return await this.prisma.workspaceRole.findMany({ where: { diff --git a/apps/api/src/workspace-role/workspace-role.module.ts b/apps/api/src/workspace-role/workspace-role.module.ts index 09abe1e3..80eb7b57 100644 --- a/apps/api/src/workspace-role/workspace-role.module.ts +++ b/apps/api/src/workspace-role/workspace-role.module.ts @@ -1,8 +1,10 @@ import { Module } from '@nestjs/common' import { WorkspaceRoleService } from './service/workspace-role.service' import { WorkspaceRoleController } from './controller/workspace-role.controller' +import { CommonModule } from 'src/common/common.module' @Module({ + imports: [CommonModule], providers: [WorkspaceRoleService], controllers: [WorkspaceRoleController] }) diff --git a/apps/api/src/workspace/controller/workspace.controller.spec.ts b/apps/api/src/workspace/controller/workspace.controller.spec.ts index 38ea6d9a..346a0b64 100644 --- a/apps/api/src/workspace/controller/workspace.controller.spec.ts +++ b/apps/api/src/workspace/controller/workspace.controller.spec.ts @@ -5,6 +5,7 @@ import { PrismaService } from '../../prisma/prisma.service' import { MAIL_SERVICE } from '../../mail/services/interface.service' import { MockMailService } from '../../mail/services/mock.service' import { JwtService } from '@nestjs/jwt' +import { AuthorityCheckerService } from '../../common/authority-checker.service' describe('WorkspaceController', () => { let controller: WorkspaceController @@ -18,7 +19,8 @@ describe('WorkspaceController', () => { provide: MAIL_SERVICE, useClass: MockMailService }, - JwtService + JwtService, + AuthorityCheckerService ], controllers: [WorkspaceController] }).compile() diff --git a/apps/api/src/workspace/service/workspace.service.spec.ts b/apps/api/src/workspace/service/workspace.service.spec.ts index acfa1362..399f0b31 100644 --- a/apps/api/src/workspace/service/workspace.service.spec.ts +++ b/apps/api/src/workspace/service/workspace.service.spec.ts @@ -4,6 +4,7 @@ import { PrismaService } from '../../prisma/prisma.service' import { MAIL_SERVICE } from '../../mail/services/interface.service' import { MockMailService } from '../../mail/services/mock.service' import { JwtService } from '@nestjs/jwt' +import { AuthorityCheckerService } from '../../common/authority-checker.service' describe('WorkspaceService', () => { let service: WorkspaceService @@ -17,7 +18,8 @@ describe('WorkspaceService', () => { provide: MAIL_SERVICE, useClass: MockMailService }, - JwtService + JwtService, + AuthorityCheckerService ] }).compile() diff --git a/apps/api/src/workspace/service/workspace.service.ts b/apps/api/src/workspace/service/workspace.service.ts index 6dab9958..9dffa70e 100644 --- a/apps/api/src/workspace/service/workspace.service.ts +++ b/apps/api/src/workspace/service/workspace.service.ts @@ -29,13 +29,13 @@ import { } from '../../mail/services/interface.service' import { JwtService } from '@nestjs/jwt' import { UpdateWorkspace } from '../dto/update.workspace/update.workspace' -import getWorkspaceWithAuthority from '../../common/get-workspace-with-authority' import { v4 } from 'uuid' import createEvent from '../../common/create-event' import { UpdateWorkspaceMetadata } from '../../approval/approval.types' import workspaceApprovalEnabled from '../../common/workspace-approval-enabled' import createApproval from '../../common/create-approval' import createWorkspace from '../../common/create-workspace' +import { AuthorityCheckerService } from '../../common/authority-checker.service' @Injectable() export class WorkspaceService { @@ -44,7 +44,8 @@ export class WorkspaceService { constructor( private readonly prisma: PrismaService, private readonly jwt: JwtService, - @Inject(MAIL_SERVICE) private readonly mailService: IMailService + @Inject(MAIL_SERVICE) private readonly mailService: IMailService, + public authorityCheckerService: AuthorityCheckerService ) {} async createWorkspace(user: User, dto: CreateWorkspace) { @@ -62,12 +63,14 @@ export class WorkspaceService { reason?: string ) { // Fetch the workspace - const workspace = await getWorkspaceWithAuthority( - user.id, - workspaceId, - Authority.UPDATE_WORKSPACE, - this.prisma - ) + const workspace = + await this.authorityCheckerService.checkAuthorityOverWorkspace({ + userId: user.id, + entity: { id: workspaceId }, + authority: Authority.UPDATE_WORKSPACE, + + prisma: this.prisma + }) // Check if a same named workspace already exists if ( @@ -102,12 +105,14 @@ export class WorkspaceService { workspaceId: Workspace['id'], userId: User['id'] ): Promise { - const workspace = await getWorkspaceWithAuthority( - user.id, - workspaceId, - Authority.WORKSPACE_ADMIN, - this.prisma - ) + const workspace = + await this.authorityCheckerService.checkAuthorityOverWorkspace({ + userId: user.id, + entity: { id: workspaceId }, + authority: Authority.WORKSPACE_ADMIN, + + prisma: this.prisma + }) if (userId === user.id) { throw new BadRequestException( @@ -222,12 +227,13 @@ export class WorkspaceService { user: User, workspaceId: Workspace['id'] ): Promise { - const workspace = await getWorkspaceWithAuthority( - user.id, - workspaceId, - Authority.DELETE_WORKSPACE, - this.prisma - ) + const workspace = + await this.authorityCheckerService.checkAuthorityOverWorkspace({ + userId: user.id, + entity: { id: workspaceId }, + authority: Authority.DELETE_WORKSPACE, + prisma: this.prisma + }) // We don't want the users to delete their default workspace if (workspace.isDefault) { @@ -251,12 +257,13 @@ export class WorkspaceService { workspaceId: Workspace['id'], members: WorkspaceMemberDTO[] ): Promise { - const workspace = await getWorkspaceWithAuthority( - user.id, - workspaceId, - Authority.ADD_USER, - this.prisma - ) + const workspace = + await this.authorityCheckerService.checkAuthorityOverWorkspace({ + userId: user.id, + entity: { id: workspaceId }, + authority: Authority.ADD_USER, + prisma: this.prisma + }) // Add users to the workspace if any if (members && members.length > 0) { @@ -296,12 +303,13 @@ export class WorkspaceService { workspaceId: Workspace['id'], userIds: User['id'][] ): Promise { - const workspace = await getWorkspaceWithAuthority( - user.id, - workspaceId, - Authority.REMOVE_USER, - this.prisma - ) + const workspace = + await this.authorityCheckerService.checkAuthorityOverWorkspace({ + userId: user.id, + entity: { id: workspaceId }, + authority: Authority.REMOVE_USER, + prisma: this.prisma + }) // Remove users from the workspace if any if (userIds && userIds.length > 0) { @@ -350,12 +358,13 @@ export class WorkspaceService { userId: User['id'], roleIds: WorkspaceRole['id'][] ): Promise { - const workspace = await getWorkspaceWithAuthority( - user.id, - workspaceId, - Authority.UPDATE_USER_ROLE, - this.prisma - ) + const workspace = + await this.authorityCheckerService.checkAuthorityOverWorkspace({ + userId: user.id, + entity: { id: workspaceId }, + authority: Authority.UPDATE_USER_ROLE, + prisma: this.prisma + }) if (!roleIds || roleIds.length === 0) { this.log.warn( @@ -432,12 +441,12 @@ export class WorkspaceService { order: string, search: string ) { - await getWorkspaceWithAuthority( - user.id, - workspaceId, - Authority.READ_USERS, - this.prisma - ) + await this.authorityCheckerService.checkAuthorityOverWorkspace({ + userId: user.id, + entity: { id: workspaceId }, + authority: Authority.READ_USERS, + prisma: this.prisma + }) return this.prisma.workspaceMember.findMany({ skip: page * limit, @@ -541,12 +550,13 @@ export class WorkspaceService { workspaceId: Workspace['id'], inviteeId: User['id'] ): Promise { - const workspace = await getWorkspaceWithAuthority( - user.id, - workspaceId, - Authority.REMOVE_USER, - this.prisma - ) + const workspace = + await this.authorityCheckerService.checkAuthorityOverWorkspace({ + userId: user.id, + entity: { id: workspaceId }, + authority: Authority.REMOVE_USER, + prisma: this.prisma + }) // Check if the user has a pending invitation to the workspace if (!(await this.invitationPending(workspaceId, inviteeId))) @@ -618,12 +628,13 @@ export class WorkspaceService { user: User, workspaceId: Workspace['id'] ): Promise { - const workspace = await getWorkspaceWithAuthority( - user.id, - workspaceId, - Authority.READ_WORKSPACE, - this.prisma - ) + const workspace = + await this.authorityCheckerService.checkAuthorityOverWorkspace({ + userId: user.id, + entity: { id: workspaceId }, + authority: Authority.READ_WORKSPACE, + prisma: this.prisma + }) const workspaceOwnerId = await this.prisma.workspace .findUnique({ @@ -670,12 +681,12 @@ export class WorkspaceService { workspaceId: Workspace['id'], otherUserId: User['id'] ): Promise { - await getWorkspaceWithAuthority( - user.id, - workspaceId, - Authority.READ_USERS, - this.prisma - ) + await this.authorityCheckerService.checkAuthorityOverWorkspace({ + userId: user.id, + entity: { id: workspaceId }, + authority: Authority.READ_USERS, + prisma: this.prisma + }) return await this.memberExistsInWorkspace(workspaceId, otherUserId) } @@ -684,12 +695,12 @@ export class WorkspaceService { user: User, workspaceId: Workspace['id'] ): Promise { - return await getWorkspaceWithAuthority( - user.id, - workspaceId, - Authority.READ_USERS, - this.prisma - ) + return await this.authorityCheckerService.checkAuthorityOverWorkspace({ + userId: user.id, + entity: { id: workspaceId }, + authority: Authority.READ_USERS, + prisma: this.prisma + }) } async getWorkspacesOfUser( @@ -729,12 +740,13 @@ export class WorkspaceService { } async exportData(user: User, workspaceId: Workspace['id']) { - const workspace = await getWorkspaceWithAuthority( - user.id, - workspaceId, - Authority.WORKSPACE_ADMIN, - this.prisma - ) + const workspace = + await this.authorityCheckerService.checkAuthorityOverWorkspace({ + userId: user.id, + entity: { id: workspaceId }, + authority: Authority.WORKSPACE_ADMIN, + prisma: this.prisma + }) // eslint-disable-next-line @typescript-eslint/no-explicit-any const data: any = {} diff --git a/git b/git new file mode 100644 index 00000000..e69de29b