diff --git a/apps/api/v2/src/modules/auth/decorators/get-team/get-team.decorator.ts b/apps/api/v2/src/modules/auth/decorators/get-team/get-team.decorator.ts new file mode 100644 index 00000000000000..80560f8aa7a95b --- /dev/null +++ b/apps/api/v2/src/modules/auth/decorators/get-team/get-team.decorator.ts @@ -0,0 +1,33 @@ +import { ExecutionContext } from "@nestjs/common"; +import { createParamDecorator } from "@nestjs/common"; + +import { Team } from "@calcom/prisma/client"; + +export type GetTeamReturnType = Team; + +export const GetTeam = createParamDecorator< + keyof GetTeamReturnType | (keyof GetTeamReturnType)[], + ExecutionContext +>((data, ctx) => { + const request = ctx.switchToHttp().getRequest(); + const team = request.team as GetTeamReturnType; + + if (!team) { + throw new Error("GetTeam decorator : Team not found"); + } + + if (Array.isArray(data)) { + return data.reduce((prev, curr) => { + return { + ...prev, + [curr]: team[curr], + }; + }, {}); + } + + if (data) { + return team[data]; + } + + return team; +}); diff --git a/apps/api/v2/src/modules/auth/guards/teams/is-team-in-org.guard.ts b/apps/api/v2/src/modules/auth/guards/teams/is-team-in-org.guard.ts new file mode 100644 index 00000000000000..d29fe486ea1a36 --- /dev/null +++ b/apps/api/v2/src/modules/auth/guards/teams/is-team-in-org.guard.ts @@ -0,0 +1,33 @@ +import { OrganizationsRepository } from "@/modules/organizations/organizations.repository"; +import { Injectable, CanActivate, ExecutionContext, ForbiddenException } from "@nestjs/common"; +import { Request } from "express"; + +import { Team } from "@calcom/prisma/client"; + +@Injectable() +export class IsTeamInOrg implements CanActivate { + constructor(private organizationsRepository: OrganizationsRepository) {} + + async canActivate(context: ExecutionContext): Promise { + const request = context.switchToHttp().getRequest(); + const teamId: string = request.params.teamId; + const orgId: string = request.params.orgId; + + if (!orgId) { + throw new ForbiddenException("No org id found in request params."); + } + + if (!teamId) { + throw new ForbiddenException("No team id found in request params."); + } + + const team = await this.organizationsRepository.findOrgTeam(Number(orgId), Number(teamId)); + + if (team && !team.isOrganization && team.parentId === Number(orgId)) { + request.team = team; + return true; + } + + return false; + } +} diff --git a/apps/api/v2/src/modules/organizations/controllers/organizations-teams.controller.e2e-spec.ts b/apps/api/v2/src/modules/organizations/controllers/organizations-teams.controller.e2e-spec.ts index 9da3105a003013..9a1535055a7ee0 100644 --- a/apps/api/v2/src/modules/organizations/controllers/organizations-teams.controller.e2e-spec.ts +++ b/apps/api/v2/src/modules/organizations/controllers/organizations-teams.controller.e2e-spec.ts @@ -22,7 +22,10 @@ describe("Organizations Team Endpoints", () => { let userRepositoryFixture: UserRepositoryFixture; let organizationsRepositoryFixture: TeamRepositoryFixture; + let teamsRepositoryFixture: TeamRepositoryFixture; + let org: Team; + let team: Team; const userEmail = "org-teams-controller-e2e@api.com"; let user: User; @@ -37,6 +40,7 @@ describe("Organizations Team Endpoints", () => { userRepositoryFixture = new UserRepositoryFixture(moduleRef); organizationsRepositoryFixture = new TeamRepositoryFixture(moduleRef); + teamsRepositoryFixture = new TeamRepositoryFixture(moduleRef); user = await userRepositoryFixture.create({ email: userEmail, @@ -48,6 +52,12 @@ describe("Organizations Team Endpoints", () => { isOrganization: true, }); + team = await teamsRepositoryFixture.create({ + name: "Test org team", + isOrganization: false, + parent: { connect: { id: org.id } }, + }); + app = moduleRef.createNestApplication(); bootstrap(app as NestExpressApplication); @@ -72,6 +82,26 @@ describe("Organizations Team Endpoints", () => { }); }); + it("should fail if org does not exist", async () => { + return request(app.getHttpServer()).get(`/v2/organizations/120494059/teams`).expect(403); + }); + + it("should get the team of the org", async () => { + return request(app.getHttpServer()) + .get(`/v2/organizations/${org.id}/teams/${team.id}`) + .expect(200) + .then((response) => { + const responseBody: ApiSuccessResponse = response.body; + expect(responseBody.status).toEqual(SUCCESS_STATUS); + expect(responseBody.data.id).toEqual(team.id); + expect(responseBody.data.parentId).toEqual(team.parentId); + }); + }); + + it("should fail if the team does not exist", async () => { + return request(app.getHttpServer()).get(`/v2/organizations/${org.id}/teams/123132145`).expect(403); + }); + afterAll(async () => { await userRepositoryFixture.deleteByEmail(user.email); await organizationsRepositoryFixture.delete(org.id); diff --git a/apps/api/v2/src/modules/organizations/controllers/organizations-teams.controller.ts b/apps/api/v2/src/modules/organizations/controllers/organizations-teams.controller.ts index 92f31d3fbdf252..9742b4c1762a95 100644 --- a/apps/api/v2/src/modules/organizations/controllers/organizations-teams.controller.ts +++ b/apps/api/v2/src/modules/organizations/controllers/organizations-teams.controller.ts @@ -1,7 +1,9 @@ import { API_VERSIONS_VALUES } from "@/lib/api-versions"; import { GetOrg } from "@/modules/auth/decorators/get-org/get-org.decorator"; +import { GetTeam } from "@/modules/auth/decorators/get-team/get-team.decorator"; import { ApiAuthGuard } from "@/modules/auth/guards/api-auth/api-auth.guard"; import { IsOrgGuard } from "@/modules/auth/guards/organizations/is-org.guard"; +import { IsTeamInOrg } from "@/modules/auth/guards/teams/is-team-in-org.guard"; import { Controller, UseGuards, Get, Param, ParseIntPipe } from "@nestjs/common"; import { ApiTags as DocsTags } from "@nestjs/swagger"; @@ -28,4 +30,20 @@ export class OrganizationsTeamsController { data: [], }; } + + @UseGuards(IsTeamInOrg) + @Get("/:teamId") + async getTeam( + @Param("orgId", ParseIntPipe) orgId: number, + @Param("teamId", ParseIntPipe) teamId: number, + @GetOrg() organization: Team, + @GetTeam() team: Team, + @GetOrg("name") orgName: string + ): Promise> { + console.log(teamId, orgId, organization, team, orgName); + return { + status: SUCCESS_STATUS, + data: team, + }; + } } diff --git a/apps/api/v2/src/modules/organizations/organizations.repository.ts b/apps/api/v2/src/modules/organizations/organizations.repository.ts index 53d21d1782bd44..82dffd0a4f8974 100644 --- a/apps/api/v2/src/modules/organizations/organizations.repository.ts +++ b/apps/api/v2/src/modules/organizations/organizations.repository.ts @@ -70,4 +70,14 @@ export class OrganizationsRepository { }, }); } + + async findOrgTeam(organizationId: number, teamId: number) { + return this.dbRead.prisma.team.findUnique({ + where: { + id: teamId, + isOrganization: false, + parentId: organizationId, + }, + }); + } }