chore: v2 dont allow mixing platform teams/users with non-platform (#20296)

* chore: v2 dont allow mixing platform teams/users with non-platform

* chore: finish main merge

* test: e2e

* refactor: simplify can add user to team logic

* refactor: fetch only needed oauth data

* refactor: cleanup
This commit is contained in:
Lauris Skraucis
2025-09-09 21:24:18 +02:00
committed by GitHub
parent d65c0753ef
commit a2d0cbfe5b
8 changed files with 562 additions and 7 deletions
@@ -119,6 +119,15 @@ export class OAuthClientRepository {
});
}
async getByOrgId(organizationId: number) {
return this.dbRead.prisma.platformOAuthClient.findMany({
where: {
organizationId,
},
select: { id: true },
});
}
async getByEventTypeHosts(eventTypeId: number) {
const hostWithUserPlatformClient = await this.dbRead.prisma.host.findFirst({
select: {
@@ -1,17 +1,23 @@
import { OAuthClientRepository } from "@/modules/oauth-clients/oauth-client.repository";
import { CreateOrgMembershipDto } from "@/modules/organizations/memberships/inputs/create-organization-membership.input";
import { OrganizationsMembershipRepository } from "@/modules/organizations/memberships/organizations-membership.repository";
import { Injectable, NotFoundException } from "@nestjs/common";
import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common";
import { TeamService } from "@calcom/platform-libraries";
import { UpdateOrgMembershipDto } from "../inputs/update-organization-membership.input";
import { OrganizationsMembershipOutputService } from "./organizations-membership-output.service";
export const PLATFORM_USER_BEING_ADDED_TO_REGULAR_ORG_ERROR = `Can't add user to organization - the user is platform managed user but organization is not because organization probably was not created using OAuth credentials.`;
export const REGULAR_USER_BEING_ADDED_TO_PLATFORM_ORG_ERROR = `Can't add user to organization - the user is not platform managed user but organization is platform managed. Both have to be created using OAuth credentials.`;
export const MANAGED_USER_AND_MANAGED_ORG_CREATED_WITH_DIFFERENT_OAUTH_CLIENTS_ERROR = `Can't add user to organization - managed user and organization were created using different OAuth clients.`;
@Injectable()
export class OrganizationsMembershipService {
constructor(
private readonly organizationsMembershipRepository: OrganizationsMembershipRepository,
private readonly organizationsMembershipOutputService: OrganizationsMembershipOutputService
private readonly organizationsMembershipOutputService: OrganizationsMembershipOutputService,
private readonly oAuthClientsRepository: OAuthClientRepository
) {}
async getOrgMembership(organizationId: number, membershipId: number) {
@@ -95,7 +101,33 @@ export class OrganizationsMembershipService {
}
async createOrgMembership(organizationId: number, data: CreateOrgMembershipDto) {
await this.canUserBeAddedToOrg(data.userId, organizationId);
const membership = await this.organizationsMembershipRepository.createOrgMembership(organizationId, data);
return this.organizationsMembershipOutputService.getOrgMembershipOutput(membership);
}
async canUserBeAddedToOrg(userId: number, orgId: number) {
const [userOAuthClient, orgOAuthClients] = await Promise.all([
this.oAuthClientsRepository.getByUserId(userId),
this.oAuthClientsRepository.getByOrgId(orgId),
]);
if (!userOAuthClient && orgOAuthClients.length === 0) {
return true;
}
if (userOAuthClient && orgOAuthClients.some((orgClient) => orgClient.id === userOAuthClient.id)) {
return true;
}
if (!userOAuthClient) {
throw new BadRequestException(REGULAR_USER_BEING_ADDED_TO_PLATFORM_ORG_ERROR);
}
if (orgOAuthClients.length === 0) {
throw new BadRequestException(PLATFORM_USER_BEING_ADDED_TO_REGULAR_ORG_ERROR);
}
throw new BadRequestException(MANAGED_USER_AND_MANAGED_ORG_CREATED_WITH_DIFFERENT_OAUTH_CLIENTS_ERROR);
}
}
@@ -13,7 +13,8 @@ import { ZoomVideoService } from "@/modules/conferencing/services/zoom-video.ser
import { CredentialsRepository } from "@/modules/credentials/credentials.repository";
import { EmailModule } from "@/modules/email/email.module";
import { EmailService } from "@/modules/email/email.service";
import { MembershipsRepository } from "@/modules/memberships/memberships.repository";
import { MembershipsModule } from "@/modules/memberships/memberships.module";
import { OAuthClientRepository } from "@/modules/oauth-clients/oauth-client.repository";
import { UserOOORepository } from "@/modules/ooo/repositories/ooo.repository";
import { UserOOOService } from "@/modules/ooo/services/ooo.service";
import { OrganizationsAttributesController } from "@/modules/organizations/attributes/index/controllers/organizations-attributes.controller";
@@ -65,6 +66,8 @@ import { RedisModule } from "@/modules/redis/redis.module";
import { RedisService } from "@/modules/redis/redis.service";
import { StripeModule } from "@/modules/stripe/stripe.module";
import { TeamsEventTypesModule } from "@/modules/teams/event-types/teams-event-types.module";
import { TeamsMembershipsService } from "@/modules/teams/memberships/services/teams-memberships.service";
import { TeamsMembershipsRepository } from "@/modules/teams/memberships/teams-memberships.repository";
import { TeamsSchedulesService } from "@/modules/teams/schedules/services/teams-schedules.service";
import { TeamsModule } from "@/modules/teams/teams/teams.module";
import { TokensRepository } from "@/modules/tokens/tokens.repository";
@@ -93,6 +96,7 @@ import { Module } from "@nestjs/common";
OrganizationsOrganizationsModule,
OrganizationsStripeModule,
OrganizationsTeamsRoutingFormsModule,
MembershipsModule,
OrganizationsConferencingModule,
EventTypesPrivateLinksModule,
],
@@ -101,7 +105,6 @@ import { Module } from "@nestjs/common";
OrganizationsTeamsRepository,
OrganizationsService,
OrganizationsTeamsService,
MembershipsRepository,
OrganizationsSchedulesService,
OrganizationsUsersRepository,
OrganizationsUsersService,
@@ -147,6 +150,9 @@ import { Module } from "@nestjs/common";
TeamsSchedulesService,
SchedulesService_2024_06_11,
InputSchedulesService_2024_06_11,
TeamsMembershipsService,
TeamsMembershipsRepository,
OAuthClientRepository,
],
exports: [
OrganizationsService,
@@ -0,0 +1,470 @@
import { bootstrap } from "@/app";
import { AppModule } from "@/app.module";
import { EmailService } from "@/modules/email/email.service";
import {
CreateManagedUserData,
CreateManagedUserOutput,
} from "@/modules/oauth-clients/controllers/oauth-client-users/outputs/create-managed-user.output";
import {
PLATFORM_USER_BEING_ADDED_TO_REGULAR_ORG_ERROR,
REGULAR_USER_BEING_ADDED_TO_PLATFORM_ORG_ERROR,
} from "@/modules/organizations/memberships/services/organizations-membership.service";
import { CreateOrgTeamDto } from "@/modules/organizations/teams/index/inputs/create-organization-team.input";
import { CreateOrgTeamMembershipDto } from "@/modules/organizations/teams/memberships/inputs/create-organization-team-membership.input";
import { PrismaModule } from "@/modules/prisma/prisma.module";
import {
PLATFORM_USER_BEING_ADDED_TO_REGULAR_TEAM_ERROR,
REGULAR_USER_BEING_ADDED_TO_PLATFORM_TEAM_ERROR,
PLATFORM_USER_AND_PLATFORM_TEAM_CREATED_WITH_DIFFERENT_OAUTH_CLIENTS_ERROR,
} from "@/modules/teams/memberships/services/teams-memberships.service";
import { TeamsMembershipsModule } from "@/modules/teams/memberships/teams-memberships.module";
import { TokensModule } from "@/modules/tokens/tokens.module";
import { CreateManagedUserInput } from "@/modules/users/inputs/create-managed-user.input";
import { CreateUserInput } from "@/modules/users/inputs/create-user.input";
import { UsersModule } from "@/modules/users/users.module";
import { INestApplication } from "@nestjs/common";
import { NestExpressApplication } from "@nestjs/platform-express";
import { Test } from "@nestjs/testing";
import { PlatformOAuthClient, User } from "@prisma/client";
import * as request from "supertest";
import { ApiKeysRepositoryFixture } from "test/fixtures/repository/api-keys.repository.fixture";
import { MembershipRepositoryFixture } from "test/fixtures/repository/membership.repository.fixture";
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
import { OrganizationRepositoryFixture } from "test/fixtures/repository/organization.repository.fixture";
import { ProfileRepositoryFixture } from "test/fixtures/repository/profiles.repository.fixture";
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
import { randomString } from "test/utils/randomString";
import { SUCCESS_STATUS } from "@calcom/platform-constants";
import { ApiSuccessResponse } from "@calcom/platform-types";
import { Team } from "@calcom/prisma/client";
describe("Organizations Teams Memberships Endpoints", () => {
let app: INestApplication;
let userRepositoryFixture: UserRepositoryFixture;
let organizationsRepositoryFixture: OrganizationRepositoryFixture;
let teamsRepositoryFixture: TeamRepositoryFixture;
let membershipsRepositoryFixture: MembershipRepositoryFixture;
let oauthClientRepositoryFixture: OAuthClientRepositoryFixture;
let profilesRepositoryFixture: ProfileRepositoryFixture;
let apiKeysRepositoryFixture: ApiKeysRepositoryFixture;
let org: Team;
let orgOwner: User;
let orgUser: User;
let orgTeam: Team;
let orgApiKey: string;
let platformOrg: Team;
let platformOrgOwner: User;
let platformOrgUser: CreateManagedUserData;
let platformOrgTeam: Team;
let platformOAuthClient: PlatformOAuthClient;
let secondPlatformOAuthClient: PlatformOAuthClient;
let secondPlatformOrgUser: CreateManagedUserData;
beforeAll(async () => {
const moduleRef = await Test.createTestingModule({
imports: [AppModule, PrismaModule, UsersModule, TokensModule, TeamsMembershipsModule],
}).compile();
userRepositoryFixture = new UserRepositoryFixture(moduleRef);
organizationsRepositoryFixture = new OrganizationRepositoryFixture(moduleRef);
teamsRepositoryFixture = new TeamRepositoryFixture(moduleRef);
oauthClientRepositoryFixture = new OAuthClientRepositoryFixture(moduleRef);
membershipsRepositoryFixture = new MembershipRepositoryFixture(moduleRef);
profilesRepositoryFixture = new ProfileRepositoryFixture(moduleRef);
apiKeysRepositoryFixture = new ApiKeysRepositoryFixture(moduleRef);
const orgName = `organizations-teams-memberships-organization-${randomString()}`;
org = await organizationsRepositoryFixture.create({
name: orgName,
slug: orgName,
isOrganization: true,
});
const platformOrgName = `organizations-teams-memberships-platform-organization-${randomString()}`;
platformOrg = await organizationsRepositoryFixture.create({
isPlatform: true,
name: platformOrgName,
slug: platformOrgName,
isOrganization: true,
platformBilling: {
create: {
customerId: "cus_999",
plan: "ESSENTIALS",
subscriptionId: "sub_999",
},
},
});
platformOAuthClient = await createOAuthClient(platformOrg.id);
secondPlatformOAuthClient = await createOAuthClient(platformOrg.id);
orgOwner = await userRepositoryFixture.create({
email: `organizations-teams-memberships-org-owner-${randomString()}@api.com`,
username: `organizations-teams-memberships-org-owner-${randomString()}`,
});
apiKeysRepositoryFixture = new ApiKeysRepositoryFixture(moduleRef);
const { keyString } = await apiKeysRepositoryFixture.createApiKey(orgOwner.id, null);
orgApiKey = `cal_test_${keyString}`;
await membershipsRepositoryFixture.create({
role: "OWNER",
user: { connect: { id: orgOwner.id } },
team: { connect: { id: org.id } },
accepted: true,
});
platformOrgOwner = await userRepositoryFixture.create({
email: `platform-org-owner-${randomString()}@api.com`,
username: `platform-org-owner-${randomString()}`,
});
await membershipsRepositoryFixture.create({
role: "OWNER",
user: { connect: { id: platformOrgOwner.id } },
team: { connect: { id: platformOrg.id } },
accepted: true,
});
await profilesRepositoryFixture.create({
uid: "asd1qwwqeqw-asddsadasd",
username: `platform-org-owner-${randomString()}`,
organization: { connect: { id: platformOrg.id } },
user: {
connect: { id: platformOrgOwner.id },
},
});
await profilesRepositoryFixture.create({
uid: "asd1qwwqeqw-asddsadasd-2",
username: `org-owner-${randomString()}`,
organization: { connect: { id: org.id } },
user: {
connect: { id: orgOwner.id },
},
});
app = moduleRef.createNestApplication();
bootstrap(app as NestExpressApplication);
await app.init();
});
async function createOAuthClient(organizationId: number) {
const data = {
logo: "logo-url",
name: "name",
redirectUris: ["http://localhost:4321"],
permissions: 1023,
};
const secret = "secret";
const client = await oauthClientRepositoryFixture.create(organizationId, data, secret);
return client;
}
describe("should create org users", () => {
it("should create a new org user", async () => {
const newOrgUser: CreateUserInput = {
email: `organization-user-${randomString()}@api.com`,
};
jest
.spyOn(EmailService.prototype, "sendSignupToOrganizationEmail")
.mockImplementation(() => Promise.resolve());
const { body } = await request(app.getHttpServer())
.post(`/v2/organizations/${org.id}/users`)
.send(newOrgUser)
.set("Content-Type", "application/json")
.set("Authorization", `Bearer ${orgApiKey}`)
.set("Accept", "application/json");
const userData = body.data;
expect(body.status).toBe(SUCCESS_STATUS);
expect(userData.email).toBe(newOrgUser.email);
orgUser = userData;
});
it(`should create a new platform org manager user using first oAuth client`, async () => {
const managedUserEmail = `platform-organization-manager-user-${randomString()}@api.com`;
const requestBody: CreateManagedUserInput = {
email: managedUserEmail,
timeZone: "Europe/Berlin",
weekStart: "Monday",
timeFormat: 24,
name: "Alice Smith",
avatarUrl: "https://cal.com/api/avatar/2b735186-b01b-46d3-87da-019b8f61776b.png",
bio: "I am a bio",
metadata: {
key: "value",
},
};
const response = await request(app.getHttpServer())
.post(`/api/v2/oauth-clients/${platformOAuthClient.id}/users`)
.set("x-cal-secret-key", platformOAuthClient.secret)
.send(requestBody)
.expect(201);
const responseBody: CreateManagedUserOutput = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data).toBeDefined();
expect(responseBody.data.user.timeZone).toEqual(requestBody.timeZone);
expect(responseBody.data.user.name).toEqual(requestBody.name);
await userConnectedToOAuth(platformOAuthClient.id, responseBody.data.user.email, 1);
platformOrgUser = responseBody.data;
});
it(`should create a new platform org manager user using second oAuth client`, async () => {
const managedUserEmail = `platform-organization-manager-user-${randomString()}@api.com`;
const requestBody: CreateManagedUserInput = {
email: managedUserEmail,
timeZone: "Europe/Berlin",
weekStart: "Monday",
timeFormat: 24,
name: "Alice Smith",
avatarUrl: "https://cal.com/api/avatar/2b735186-b01b-46d3-87da-019b8f61776b.png",
bio: "I am a bio",
metadata: {
key: "value",
},
};
const response = await request(app.getHttpServer())
.post(`/api/v2/oauth-clients/${secondPlatformOAuthClient.id}/users`)
.set("x-cal-secret-key", secondPlatformOAuthClient.secret)
.send(requestBody)
.expect(201);
const responseBody: CreateManagedUserOutput = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data).toBeDefined();
expect(responseBody.data.user.timeZone).toEqual(requestBody.timeZone);
expect(responseBody.data.user.name).toEqual(requestBody.name);
await userConnectedToOAuth(secondPlatformOAuthClient.id, responseBody.data.user.email, 1);
secondPlatformOrgUser = responseBody.data;
});
});
describe("should create org teams", () => {
it("should create the team for org", async () => {
const teamName = `organization team ${randomString()}`;
return request(app.getHttpServer())
.post(`/v2/organizations/${org.id}/teams`)
.set("Authorization", `Bearer ${orgApiKey}`)
.send({
name: teamName,
} satisfies CreateOrgTeamDto)
.expect(201)
.then(async (response) => {
const responseBody: ApiSuccessResponse<Team> = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
orgTeam = responseBody.data;
expect(orgTeam.name).toEqual(teamName);
expect(orgTeam.parentId).toEqual(org.id);
});
});
it("should create the team for platform org", async () => {
const teamName = `platform organization team ${randomString()}`;
return request(app.getHttpServer())
.post(`/v2/organizations/${platformOrg.id}/teams`)
.set("x-cal-client-id", platformOAuthClient.id)
.set("x-cal-secret-key", platformOAuthClient.secret)
.send({
name: teamName,
} satisfies CreateOrgTeamDto)
.expect(201)
.then(async (response) => {
const responseBody: ApiSuccessResponse<Team> = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
platformOrgTeam = responseBody.data;
expect(platformOrgTeam.name).toEqual(teamName);
expect(platformOrgTeam.parentId).toEqual(platformOrg.id);
});
});
});
describe("organization memberships", () => {
describe("negative tests", () => {
it("should not add managed user to organization", async () => {
const response = await request(app.getHttpServer())
.post(`/v2/organizations/${org.id}/memberships`)
.set("Authorization", `Bearer ${orgApiKey}`)
.send({
userId: platformOrgUser.user.id,
accepted: true,
role: "MEMBER",
} satisfies CreateOrgTeamMembershipDto)
.expect(400);
expect(response.body.error.message).toEqual(PLATFORM_USER_BEING_ADDED_TO_REGULAR_ORG_ERROR);
});
it("should not add managed user to organization team", async () => {
await request(app.getHttpServer())
.post(`/v2/organizations/${org.id}/teams/${orgTeam.id}/memberships`)
.set("Authorization", `Bearer ${orgApiKey}`)
.send({
userId: platformOrgUser.user.id,
accepted: true,
role: "MEMBER",
} satisfies CreateOrgTeamMembershipDto)
.expect(422);
});
it("should not add managed user to organization team", async () => {
const response = await request(app.getHttpServer())
.post(`/v2/teams/${orgTeam.id}/memberships`)
.set("Authorization", `Bearer ${orgApiKey}`)
.send({
userId: platformOrgUser.user.id,
accepted: true,
role: "MEMBER",
} satisfies CreateOrgTeamMembershipDto)
.expect(400);
expect(response.body.error.message).toEqual(PLATFORM_USER_BEING_ADDED_TO_REGULAR_TEAM_ERROR);
});
});
describe("positive tests", () => {
it("should add user to organization", async () => {
await request(app.getHttpServer())
.post(`/v2/organizations/${org.id}/memberships`)
.set("Authorization", `Bearer ${orgApiKey}`)
.send({
userId: orgUser.id,
accepted: true,
role: "MEMBER",
} satisfies CreateOrgTeamMembershipDto)
.expect(201);
});
it("should add user to organization team", async () => {
await request(app.getHttpServer())
.post(`/v2/organizations/${org.id}/teams/${orgTeam.id}/memberships`)
.set("Authorization", `Bearer ${orgApiKey}`)
.send({
userId: orgUser.id,
accepted: true,
role: "MEMBER",
} satisfies CreateOrgTeamMembershipDto)
.expect(201);
});
});
});
describe("platform organization memberships", () => {
describe("negative tests", () => {
it("should not add user to platform organization", async () => {
const response = await request(app.getHttpServer())
.post(`/v2/organizations/${platformOrg.id}/memberships`)
.set("x-cal-client-id", platformOAuthClient.id)
.set("x-cal-secret-key", platformOAuthClient.secret)
.send({
userId: orgUser.id,
accepted: true,
role: "MEMBER",
} satisfies CreateOrgTeamMembershipDto)
.expect(400);
expect(response.body.error.message).toEqual(REGULAR_USER_BEING_ADDED_TO_PLATFORM_ORG_ERROR);
});
it("should not add user to platform organization team", async () => {
await request(app.getHttpServer())
.post(`/v2/organizations/${platformOrg.id}/teams/${platformOrgTeam.id}/memberships`)
.set("x-cal-client-id", platformOAuthClient.id)
.set("x-cal-secret-key", platformOAuthClient.secret)
.send({
userId: orgUser.id,
accepted: true,
role: "MEMBER",
} satisfies CreateOrgTeamMembershipDto)
.expect(422);
});
it("should not add user to platform organization team", async () => {
const response = await request(app.getHttpServer())
.post(`/v2/teams/${platformOrgTeam.id}/memberships`)
.set("x-cal-client-id", platformOAuthClient.id)
.set("x-cal-secret-key", platformOAuthClient.secret)
.send({
userId: orgUser.id,
accepted: true,
role: "MEMBER",
} satisfies CreateOrgTeamMembershipDto)
.expect(400);
expect(response.body.error.message).toEqual(REGULAR_USER_BEING_ADDED_TO_PLATFORM_TEAM_ERROR);
});
it("should not add user to platform organization team because user is created using different oAuth client", async () => {
const response = await request(app.getHttpServer())
.post(`/v2/organizations/${platformOrg.id}/teams/${platformOrgTeam.id}/memberships`)
.set("x-cal-client-id", platformOAuthClient.id)
.set("x-cal-secret-key", platformOAuthClient.secret)
.send({
userId: secondPlatformOrgUser.user.id,
accepted: true,
role: "MEMBER",
} satisfies CreateOrgTeamMembershipDto)
.expect(400);
expect(response.body.error.message).toEqual(
PLATFORM_USER_AND_PLATFORM_TEAM_CREATED_WITH_DIFFERENT_OAUTH_CLIENTS_ERROR
);
});
});
describe("positive tests", () => {
it("should add user to organization", async () => {
await request(app.getHttpServer())
.post(`/v2/organizations/${platformOrg.id}/memberships`)
.set("x-cal-client-id", platformOAuthClient.id)
.set("x-cal-secret-key", platformOAuthClient.secret)
.send({
userId: platformOrgUser.user.id,
accepted: true,
role: "MEMBER",
} satisfies CreateOrgTeamMembershipDto)
.expect(201);
});
it("should add user to organization team", async () => {
await request(app.getHttpServer())
.post(`/v2/organizations/${platformOrg.id}/teams/${platformOrgTeam.id}/memberships`)
.set("x-cal-client-id", platformOAuthClient.id)
.set("x-cal-secret-key", platformOAuthClient.secret)
.send({
userId: platformOrgUser.user.id,
accepted: true,
role: "MEMBER",
} satisfies CreateOrgTeamMembershipDto)
.expect(201);
});
});
});
async function userConnectedToOAuth(oAuthClientId: string, userEmail: string, usersCount: number) {
const oAuthUsers = await oauthClientRepositoryFixture.getUsers(oAuthClientId);
const newOAuthUser = oAuthUsers?.find((user) => user.email === userEmail);
expect(oAuthUsers?.length).toEqual(usersCount);
expect(newOAuthUser?.email).toEqual(userEmail);
}
afterAll(async () => {
await organizationsRepositoryFixture.delete(org.id);
await teamsRepositoryFixture.delete(orgTeam.id);
await organizationsRepositoryFixture.delete(platformOrg.id);
await teamsRepositoryFixture.delete(platformOrgTeam.id);
await userRepositoryFixture.deleteByEmail(platformOrgUser.user.email);
await userRepositoryFixture.deleteByEmail(orgUser.email);
await userRepositoryFixture.deleteByEmail(orgOwner.email);
await userRepositoryFixture.deleteByEmail(platformOrgOwner.email);
await userRepositoryFixture.deleteByEmail(secondPlatformOrgUser.user.email);
await app.close();
});
});
@@ -1,6 +1,7 @@
import { CreateOrgTeamMembershipDto } from "@/modules/organizations/teams/memberships/inputs/create-organization-team-membership.input";
import { UpdateOrgTeamMembershipDto } from "@/modules/organizations/teams/memberships/inputs/update-organization-team-membership.input";
import { OrganizationsTeamsMembershipsRepository } from "@/modules/organizations/teams/memberships/organizations-teams-memberships.repository";
import { TeamsMembershipsService } from "@/modules/teams/memberships/services/teams-memberships.service";
import { Injectable, NotFoundException } from "@nestjs/common";
import { TeamService } from "@calcom/platform-libraries";
@@ -8,10 +9,12 @@ import { TeamService } from "@calcom/platform-libraries";
@Injectable()
export class OrganizationsTeamsMembershipsService {
constructor(
private readonly organizationsTeamsMembershipsRepository: OrganizationsTeamsMembershipsRepository
private readonly organizationsTeamsMembershipsRepository: OrganizationsTeamsMembershipsRepository,
private readonly teamsMembershipsService: TeamsMembershipsService
) {}
async createOrgTeamMembership(teamId: number, data: CreateOrgTeamMembershipDto) {
await this.teamsMembershipsService.canUserBeAddedToTeam(data.userId, teamId);
const teamMembership = await this.organizationsTeamsMembershipsRepository.createOrgTeamMembership(
teamId,
data
@@ -1,15 +1,24 @@
import { OAuthClientRepository } from "@/modules/oauth-clients/oauth-client.repository";
import { CreateTeamMembershipInput } from "@/modules/teams/memberships/inputs/create-team-membership.input";
import { UpdateTeamMembershipInput } from "@/modules/teams/memberships/inputs/update-team-membership.input";
import { TeamsMembershipsRepository } from "@/modules/teams/memberships/teams-memberships.repository";
import { Injectable, NotFoundException } from "@nestjs/common";
import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common";
import { TeamService } from "@calcom/platform-libraries";
export const PLATFORM_USER_BEING_ADDED_TO_REGULAR_TEAM_ERROR = `Can't add user to team - the user is platform managed user but team is not because team probably was not created using OAuth credentials.`;
export const REGULAR_USER_BEING_ADDED_TO_PLATFORM_TEAM_ERROR = `Can't add user to team - the user is not platform managed user but team is platform managed. Both have to be created using OAuth credentials.`;
export const PLATFORM_USER_AND_PLATFORM_TEAM_CREATED_WITH_DIFFERENT_OAUTH_CLIENTS_ERROR = `Can't add user to team - managed user and team were created using different OAuth clients.`;
@Injectable()
export class TeamsMembershipsService {
constructor(private readonly teamsMembershipsRepository: TeamsMembershipsRepository) {}
constructor(
private readonly teamsMembershipsRepository: TeamsMembershipsRepository,
private readonly oAuthClientsRepository: OAuthClientRepository
) {}
async createTeamMembership(teamId: number, data: CreateTeamMembershipInput) {
await this.canUserBeAddedToTeam(data.userId, teamId);
const teamMembership = await this.teamsMembershipsRepository.createTeamMembership(teamId, data);
return teamMembership;
}
@@ -54,4 +63,29 @@ export class TeamsMembershipsService {
return teamMembership;
}
async canUserBeAddedToTeam(userId: number, teamId: number) {
const [userOAuthClient, teamOAuthClient] = await Promise.all([
this.oAuthClientsRepository.getByUserId(userId),
this.oAuthClientsRepository.getByTeamId(teamId),
]);
if (!userOAuthClient && !teamOAuthClient) {
return true;
}
if (userOAuthClient && teamOAuthClient && userOAuthClient.id === teamOAuthClient.id) {
return true;
}
if (!teamOAuthClient) {
throw new BadRequestException(PLATFORM_USER_BEING_ADDED_TO_REGULAR_TEAM_ERROR);
}
if (!userOAuthClient) {
throw new BadRequestException(REGULAR_USER_BEING_ADDED_TO_PLATFORM_TEAM_ERROR);
}
throw new BadRequestException(PLATFORM_USER_AND_PLATFORM_TEAM_CREATED_WITH_DIFFERENT_OAUTH_CLIENTS_ERROR);
}
}
@@ -12,5 +12,6 @@ import { Module } from "@nestjs/common";
imports: [PrismaModule, RedisModule, OrganizationsModule, MembershipsModule, TeamsEventTypesModule],
providers: [TeamsMembershipsRepository, TeamsMembershipsService],
controllers: [TeamsMembershipsController],
exports: [TeamsMembershipsService],
})
export class TeamsMembershipsModule {}