Skip to content

Commit

Permalink
chore: IsTeamInOrg guard and decorator apiv2 (#15567)
Browse files Browse the repository at this point in the history
  • Loading branch information
ThyMinimalDev authored Jun 26, 2024
1 parent d431607 commit ec755b1
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -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;
});
33 changes: 33 additions & 0 deletions apps/api/v2/src/modules/auth/guards/teams/is-team-in-org.guard.ts
Original file line number Diff line number Diff line change
@@ -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<boolean> {
const request = context.switchToHttp().getRequest<Request & { team: Team }>();
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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 = "[email protected]";
let user: User;
Expand All @@ -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,
Expand All @@ -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);

Expand All @@ -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<Team> = 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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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";

Expand All @@ -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<ApiResponse<Team>> {
console.log(teamId, orgId, organization, team, orgName);
return {
status: SUCCESS_STATUS,
data: team,
};
}
}
10 changes: 10 additions & 0 deletions apps/api/v2/src/modules/organizations/organizations.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
});
}
}

0 comments on commit ec755b1

Please sign in to comment.