refactor: use permission check service for isTeamAdmin (#24026)

* saml

* addGuests.handler.ts

* rename canAccess to canAccessOrganization and use organization.read

* update usages

* team update handler

* team member invite

* event type update

* remove isTeamAdmin util

* updateInternalNotesPresets

* setInviteExpiration

* event type update

* deleteInvite

* createInvite

* team publish

* fixes

* final

* address feedback

* update test
This commit is contained in:
Benny Joo
2025-09-25 08:54:16 +01:00
committed by GitHub
parent 2ed0380e4f
commit 8d7cdf9d3b
21 changed files with 238 additions and 145 deletions
@@ -1,4 +1,4 @@
import { canAccess } from "@calcom/features/ee/sso/lib/saml";
import { canAccessOrganization } from "@calcom/features/ee/sso/lib/saml";
import prisma from "@calcom/prisma";
import type { TrpcSessionUser } from "@calcom/trpc/server/types";
@@ -16,7 +16,7 @@ const userCanCreateTeamGroupMapping = async (
});
}
const { message, access } = await canAccess(user, organizationId);
const { message, access } = await canAccessOrganization(user, organizationId);
if (!access) {
throw new TRPCError({
code: "BAD_REQUEST",
+19 -3
View File
@@ -1,7 +1,8 @@
import type { SAMLSSORecord, OIDCSSORecord } from "@boxyhq/saml-jackson";
import { PermissionCheckService } from "@calcom/features/pbac/services/permission-check.service";
import { HOSTED_CAL_FEATURES } from "@calcom/lib/constants";
import { isTeamAdmin } from "@calcom/features/ee/teams/lib/queries";
import { MembershipRole } from "@calcom/prisma/enums";
export const samlDatabaseUrl = process.env.SAML_DATABASE_URL || "";
export const isSAMLLoginEnabled = samlDatabaseUrl.length > 0;
@@ -28,7 +29,7 @@ export const isSAMLAdmin = (email: string) => {
return false;
};
export const canAccess = async (user: { id: number; email: string }, teamId: number | null) => {
export const canAccessOrganization = async (user: { id: number; email: string }, teamId: number | null) => {
const { id: userId, email } = user;
if (!isSAMLLoginEnabled) {
@@ -40,7 +41,22 @@ export const canAccess = async (user: { id: number; email: string }, teamId: num
// Hosted
if (HOSTED_CAL_FEATURES) {
if (teamId === null || !(await isTeamAdmin(userId, teamId))) {
if (teamId === null) {
return {
message: "dont_have_permission",
access: false,
};
}
const permissionCheckService = new PermissionCheckService();
const hasPermission = await permissionCheckService.checkPermission({
userId,
teamId,
permission: "organization.read",
fallbackRoles: [MembershipRole.OWNER, MembershipRole.ADMIN],
});
if (!hasPermission) {
return {
message: "dont_have_permission",
access: false,
-23
View File
@@ -385,29 +385,6 @@ export async function getTeamWithoutMembers(args: {
};
}
// also returns team
export async function isTeamAdmin(userId: number, teamId: number) {
const team = await prisma.membership.findFirst({
where: {
userId,
teamId,
accepted: true,
OR: [{ role: "ADMIN" }, { role: "OWNER" }],
},
include: {
team: {
select: {
metadata: true,
parentId: true,
isOrganization: true,
},
},
},
});
if (!team) return false;
return team;
}
export async function isTeamOwner(userId: number, teamId: number) {
return !!(await prisma.membership.findFirst({
where: {
@@ -1,6 +1,5 @@
import { FeaturesRepository } from "@calcom/features/flags/features.repository";
import { isOrganisationAdmin } from "@calcom/lib/server/queries/organisations";
import { isTeamAdmin } from "@calcom/features/ee/teams/lib/queries";
import { prisma } from "@calcom/prisma";
import { MembershipRole } from "@calcom/prisma/enums";
@@ -97,12 +96,23 @@ class PBACRoleManager implements IRoleManager {
class LegacyRoleManager implements IRoleManager {
public isPBACEnabled = false;
async checkPermissionToChangeRole(userId: number, targetId: number, scope: "org" | "team"): Promise<void> {
const membership =
scope === "team"
? !!(await isTeamAdmin(userId, targetId))
: !!(await isOrganisationAdmin(userId, targetId));
let hasPermission = false;
if (scope === "team") {
const team = await prisma.membership.findFirst({
where: {
userId,
teamId: targetId,
accepted: true,
OR: [{ role: "ADMIN" }, { role: "OWNER" }],
},
});
hasPermission = !!team;
} else {
hasPermission = !!(await isOrganisationAdmin(userId, targetId));
}
// Only OWNER/ADMIN can update role
if (!membership) {
if (!hasPermission) {
throw new RoleManagementError(
"Only owners or admin can update roles",
RoleManagementErrorCode.UNAUTHORIZED
@@ -1,11 +1,12 @@
import dayjs from "@calcom/dayjs";
import { sendAddGuestsEmails } from "@calcom/emails";
import EventManager from "@calcom/features/bookings/lib/EventManager";
import { PermissionCheckService } from "@calcom/features/pbac/services/permission-check.service";
import { parseRecurringEvent } from "@calcom/lib/isRecurringEvent";
import { getUsersCredentialsIncludeServiceAccountKey } from "@calcom/lib/server/getUsersCredentials";
import { getTranslation } from "@calcom/lib/server/i18n";
import { isTeamAdmin, isTeamOwner } from "@calcom/features/ee/teams/lib/queries";
import { prisma } from "@calcom/prisma";
import { MembershipRole } from "@calcom/prisma/enums";
import type { BookingResponses } from "@calcom/prisma/zod-utils";
import type { CalendarEvent } from "@calcom/types/Calendar";
@@ -44,15 +45,21 @@ export const addGuestsHandler = async ({ ctx, input }: AddGuestsOptions) => {
if (!booking) throw new TRPCError({ code: "NOT_FOUND", message: "booking_not_found" });
const isTeamAdminOrOwner =
(await isTeamAdmin(user.id, booking.eventType?.teamId ?? 0)) ||
(await isTeamOwner(user.id, booking.eventType?.teamId ?? 0));
const isOrganizer = booking.userId === user.id;
const isAttendee = !!booking.attendees.find((attendee) => attendee.email === user.email);
if (!isTeamAdminOrOwner && !isOrganizer && !isAttendee) {
let hasBookingUpdatePermission = false;
if (booking.eventType?.teamId) {
const permissionCheckService = new PermissionCheckService();
hasBookingUpdatePermission = await permissionCheckService.checkPermission({
userId: user.id,
teamId: booking.eventType.teamId,
permission: "booking.update",
fallbackRoles: [MembershipRole.OWNER, MembershipRole.ADMIN],
});
}
if (!hasBookingUpdatePermission && !isOrganizer && !isAttendee) {
throw new TRPCError({ code: "FORBIDDEN", message: "you_do_not_have_permission" });
}
@@ -1,7 +1,7 @@
import type { DirectoryType } from "@boxyhq/saml-jackson";
import jackson from "@calcom/features/ee/sso/lib/jackson";
import { canAccess, samlProductID, samlTenantID } from "@calcom/features/ee/sso/lib/saml";
import { canAccessOrganization, samlProductID, samlTenantID } from "@calcom/features/ee/sso/lib/saml";
import prisma from "@calcom/prisma";
import { TRPCError } from "@trpc/server";
@@ -21,7 +21,7 @@ export const createHandler = async ({ ctx, input }: Options) => {
const { organizationId } = input;
const { dsyncController } = await jackson();
const { message, access } = await canAccess(ctx.user, organizationId);
const { message, access } = await canAccessOrganization(ctx.user, organizationId);
if (!access) {
throw new TRPCError({
@@ -1,5 +1,5 @@
import jackson from "@calcom/features/ee/sso/lib/jackson";
import { canAccess } from "@calcom/features/ee/sso/lib/saml";
import { canAccessOrganization } from "@calcom/features/ee/sso/lib/saml";
import prisma from "@calcom/prisma";
import { TRPCError } from "@trpc/server";
@@ -18,7 +18,7 @@ type Options = {
export const deleteHandler = async ({ ctx, input }: Options) => {
const { dsyncController } = await jackson();
const { message, access } = await canAccess(ctx.user, input.organizationId);
const { message, access } = await canAccessOrganization(ctx.user, input.organizationId);
if (!access) {
throw new TRPCError({
@@ -1,5 +1,5 @@
import jackson from "@calcom/features/ee/sso/lib/jackson";
import { canAccess } from "@calcom/features/ee/sso/lib/saml";
import { canAccessOrganization } from "@calcom/features/ee/sso/lib/saml";
import prisma from "@calcom/prisma";
import { TRPCError } from "@trpc/server";
@@ -18,7 +18,7 @@ type Options = {
export const getHandler = async ({ ctx, input }: Options) => {
const { dsyncController } = await jackson();
const { message, access } = await canAccess(ctx.user, input.organizationId);
const { message, access } = await canAccessOrganization(ctx.user, input.organizationId);
if (!access) {
throw new TRPCError({
@@ -1,5 +1,10 @@
import jackson from "@calcom/features/ee/sso/lib/jackson";
import { canAccess, samlProductID, samlTenantID, tenantPrefix } from "@calcom/features/ee/sso/lib/saml";
import {
canAccessOrganization,
samlProductID,
samlTenantID,
tenantPrefix,
} from "@calcom/features/ee/sso/lib/saml";
import { TRPCError } from "@trpc/server";
@@ -18,7 +23,7 @@ export const deleteHandler = async ({ ctx, input }: DeleteOptions) => {
const { teamId } = input;
const { message, access } = await canAccess(ctx.user, teamId);
const { message, access } = await canAccessOrganization(ctx.user, teamId);
if (!access) {
throw new TRPCError({
@@ -1,7 +1,7 @@
import jackson from "@calcom/features/ee/sso/lib/jackson";
import type { SSOConnection } from "@calcom/features/ee/sso/lib/saml";
import {
canAccess,
canAccessOrganization,
oidcPath,
samlProductID,
samlTenantID,
@@ -23,7 +23,7 @@ type GetOptions = {
export const getHandler = async ({ ctx, input }: GetOptions): Promise<SSOConnection | null> => {
const { teamId } = input;
const { message, access } = await canAccess(ctx.user, teamId);
const { message, access } = await canAccessOrganization(ctx.user, teamId);
if (!access) {
throw new TRPCError({
@@ -1,5 +1,10 @@
import jackson from "@calcom/features/ee/sso/lib/jackson";
import { canAccess, samlProductID, samlTenantID, tenantPrefix } from "@calcom/features/ee/sso/lib/saml";
import {
canAccessOrganization,
samlProductID,
samlTenantID,
tenantPrefix,
} from "@calcom/features/ee/sso/lib/saml";
import { TRPCError } from "@trpc/server";
@@ -18,7 +23,7 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => {
const { encodedRawMetadata, teamId } = input;
const { message, access } = await canAccess(ctx.user, teamId);
const { message, access } = await canAccessOrganization(ctx.user, teamId);
if (!access) {
throw new TRPCError({
@@ -1,5 +1,10 @@
import jackson from "@calcom/features/ee/sso/lib/jackson";
import { canAccess, samlProductID, samlTenantID, tenantPrefix } from "@calcom/features/ee/sso/lib/saml";
import {
canAccessOrganization,
samlProductID,
samlTenantID,
tenantPrefix,
} from "@calcom/features/ee/sso/lib/saml";
import { TRPCError } from "@trpc/server";
@@ -16,7 +21,7 @@ type UpdateOIDCOptions = {
export const updateOIDCHandler = async ({ ctx, input }: UpdateOIDCOptions) => {
const { teamId, clientId, clientSecret, wellKnownUrl } = input;
const { message, access } = await canAccess(ctx.user, teamId);
const { message, access } = await canAccessOrganization(ctx.user, teamId);
if (!access) {
throw new TRPCError({
@@ -1,8 +1,9 @@
import { randomBytes } from "crypto";
import { PermissionCheckService } from "@calcom/features/pbac/services/permission-check.service";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { isTeamAdmin } from "@calcom/features/ee/teams/lib/queries";
import { prisma } from "@calcom/prisma";
import { MembershipRole } from "@calcom/prisma/enums";
import type { TrpcSessionUser } from "@calcom/trpc/server/types";
import { TRPCError } from "@trpc/server";
@@ -18,10 +19,26 @@ type CreateInviteOptions = {
export const createInviteHandler = async ({ ctx, input }: CreateInviteOptions) => {
const { teamId } = input;
const membership = await isTeamAdmin(ctx.user.id, teamId);
if (!membership || !membership?.team) throw new TRPCError({ code: "UNAUTHORIZED" });
const isOrganizationOrATeamInOrganization = !!(membership.team?.parentId || membership.team.isOrganization);
const team = await prisma.team.findUnique({
where: { id: teamId },
select: { parentId: true, isOrganization: true },
});
if (!team) throw new TRPCError({ code: "NOT_FOUND" });
const permissionCheckService = new PermissionCheckService();
const permission = team.isOrganization ? "organization.invite" : "team.invite";
const hasInvitePermission = await permissionCheckService.checkPermission({
userId: ctx.user.id,
teamId,
permission,
fallbackRoles: [MembershipRole.OWNER, MembershipRole.ADMIN],
});
if (!hasInvitePermission) throw new TRPCError({ code: "UNAUTHORIZED" });
const isOrganizationOrATeamInOrganization = !!(team.parentId || team.isOrganization);
if (input.token) {
const existingToken = await prisma.verificationToken.findFirst({
@@ -1,5 +1,6 @@
import { isTeamAdmin } from "@calcom/features/ee/teams/lib/queries";
import { PermissionCheckService } from "@calcom/features/pbac/services/permission-check.service";
import { prisma } from "@calcom/prisma";
import { MembershipRole } from "@calcom/prisma/enums";
import type { TrpcSessionUser } from "@calcom/trpc/server/types";
import { TRPCError } from "@trpc/server";
@@ -23,12 +24,26 @@ export const deleteInviteHandler = async ({ ctx, input }: DeleteInviteOptions) =
select: {
teamId: true,
id: true,
team: {
select: { isOrganization: true },
},
},
});
if (!verificationToken) throw new TRPCError({ code: "NOT_FOUND" });
if (!verificationToken.teamId || !(await isTeamAdmin(ctx.user.id, verificationToken.teamId)))
throw new TRPCError({ code: "UNAUTHORIZED" });
if (!verificationToken.teamId) throw new TRPCError({ code: "UNAUTHORIZED" });
const permissionCheckService = new PermissionCheckService();
const isOrgContext = !!verificationToken.team?.isOrganization;
const permission = isOrgContext ? "organization.invite" : "team.invite";
const hasInvitePermission = await permissionCheckService.checkPermission({
userId: ctx.user.id,
teamId: verificationToken.teamId,
permission,
fallbackRoles: [MembershipRole.OWNER, MembershipRole.ADMIN],
});
if (!hasInvitePermission) throw new TRPCError({ code: "UNAUTHORIZED" });
await prisma.verificationToken.delete({ where: { id: verificationToken.id } });
};
@@ -1,7 +1,6 @@
import { describe, it, vi, expect } from "vitest";
import { isTeamAdmin } from "@calcom/features/ee/teams/lib/queries";
import { isOrganisationAdmin } from "@calcom/lib/server/queries/organisations";
import { PermissionCheckService } from "@calcom/features/pbac/services/permission-check.service";
import { MembershipRole } from "@calcom/prisma/enums";
import { TRPCError } from "@trpc/server";
@@ -25,12 +24,6 @@ vi.mock("@calcom/prisma", () => {
};
});
vi.mock("@calcom/features/ee/teams/lib/queries", () => {
return {
isTeamAdmin: vi.fn(),
};
});
vi.mock("@calcom/lib/server/queries/organisations", () => {
return {
isOrganisationAdmin: vi.fn(),
@@ -38,42 +31,11 @@ vi.mock("@calcom/lib/server/queries/organisations", () => {
};
});
const mockedReturnSuccessCheckPerms = {
accepted: true,
disableImpersonation: false,
id: 1,
role: MembershipRole.ADMIN,
userId: 1,
teamId: 1,
createdAt: null,
updatedAt: null,
customRoleId: null,
team: {
id: 1,
name: "Team A",
slug: null,
logo: null,
appLogo: null,
appIconLogo: null,
bio: null,
hideBranding: false,
hideBookATeamMember: false,
createdAt: new Date(),
brandColor: "#292929",
darkBrandColor: "#fafafa",
timeZone: "Europe/London",
weekStart: "Sunday",
theme: null,
timeFormat: null,
metadata: null,
parentId: null,
parent: null,
isPrivate: false,
logoUrl: "",
calVideoLogo: "",
isOrganization: false,
},
};
vi.mock("@calcom/features/pbac/services/permission-check.service", () => {
return {
PermissionCheckService: vi.fn(),
};
});
const mockedRegularTeam: TeamWithParent = {
id: 1,
@@ -142,27 +104,45 @@ const userInTeamNotAccepted: UserWithMembership = {
describe("Invite Member Utils", () => {
describe("ensureAtleastAdminPermissions", () => {
it("It should throw an error if the user is not an admin of the ORG", async () => {
vi.mocked(isOrganisationAdmin).mockResolvedValue(false);
const mockCheckPermission = vi.fn().mockResolvedValue(false);
vi.mocked(PermissionCheckService).mockImplementation(
() =>
({
checkPermission: mockCheckPermission,
} as any)
);
await expect(ensureAtleastAdminPermissions({ userId: 1, teamId: 1, isOrg: true })).rejects.toThrow(
"UNAUTHORIZED"
);
expect(mockCheckPermission).toHaveBeenCalledWith({
userId: 1,
teamId: 1,
permission: "organization.invite",
fallbackRoles: ["OWNER", "ADMIN"],
});
});
it("It should NOT throw an error if the user is an admin of the ORG", async () => {
vi.mocked(isOrganisationAdmin).mockResolvedValue(mockedReturnSuccessCheckPerms);
const mockCheckPermission = vi.fn().mockResolvedValue(true);
vi.mocked(PermissionCheckService).mockImplementation(
() =>
({
checkPermission: mockCheckPermission,
} as any)
);
await expect(
ensureAtleastAdminPermissions({ userId: 1, teamId: 1, isOrg: true })
).resolves.not.toThrow();
});
it("It should throw an error if the user is not an admin of the team", async () => {
vi.mocked(isTeamAdmin).mockResolvedValue(false);
await expect(ensureAtleastAdminPermissions({ userId: 1, teamId: 1 })).rejects.toThrow("UNAUTHORIZED");
});
it("It should NOT throw an error if the user is an admin of a team", async () => {
vi.mocked(isTeamAdmin).mockResolvedValue(mockedReturnSuccessCheckPerms);
await expect(ensureAtleastAdminPermissions({ userId: 1, teamId: 1 })).resolves.not.toThrow();
expect(mockCheckPermission).toHaveBeenCalledWith({
userId: 1,
teamId: 1,
permission: "organization.invite",
fallbackRoles: ["OWNER", "ADMIN"],
});
});
});
@@ -4,15 +4,14 @@ import type { TFunction } from "i18next";
import { getOrgFullOrigin } from "@calcom/ee/organizations/lib/orgDomains";
import { sendTeamInviteEmail } from "@calcom/emails";
import { checkAdminOrOwner } from "@calcom/features/auth/lib/checkAdminOrOwner";
import { updateNewTeamMemberEventTypes } from "@calcom/features/ee/teams/lib/queries";
import { PermissionCheckService } from "@calcom/features/pbac/services/permission-check.service";
import { DEFAULT_SCHEDULE, getAvailabilityFromSchedule } from "@calcom/lib/availability";
import { ENABLE_PROFILE_SWITCHER, WEBAPP_URL } from "@calcom/lib/constants";
import { createAProfileForAnExistingUser } from "@calcom/lib/createAProfileForAnExistingUser";
import logger from "@calcom/lib/logger";
import { safeStringify } from "@calcom/lib/safeStringify";
import { getTranslation } from "@calcom/lib/server/i18n";
import { isOrganisationAdmin } from "@calcom/lib/server/queries/organisations";
import { updateNewTeamMemberEventTypes } from "@calcom/features/ee/teams/lib/queries";
import { isTeamAdmin } from "@calcom/features/ee/teams/lib/queries";
import { ProfileRepository } from "@calcom/lib/server/repository/profile";
import { getParsedTeam } from "@calcom/lib/server/repository/teamUtils";
import { UserRepository } from "@calcom/lib/server/repository/user";
@@ -66,12 +65,20 @@ export async function ensureAtleastAdminPermissions({
teamId: number;
isOrg?: boolean;
}) {
const permissionCheckService = new PermissionCheckService();
// Checks if the team they are inviting to IS the org. Not a child team
if (isOrg) {
if (!(await isOrganisationAdmin(userId, teamId))) throw new TRPCError({ code: "UNAUTHORIZED" });
} else {
// TODO: do some logic here to check if the user is inviting a NEW user to a team that ISNT in the same org
if (!(await isTeamAdmin(userId, teamId))) throw new TRPCError({ code: "UNAUTHORIZED" });
// TODO: do some logic here to check if the user is inviting a NEW user to a team that ISNT in the same org
const permission = isOrg ? "organization.invite" : "team.invite";
const hasInvitePermission = await permissionCheckService.checkPermission({
userId,
teamId,
permission,
fallbackRoles: [MembershipRole.OWNER, MembershipRole.ADMIN],
});
if (!hasInvitePermission) {
throw new TRPCError({ code: "UNAUTHORIZED" });
}
}
@@ -1,16 +1,16 @@
import { purchaseTeamOrOrgSubscription } from "@calcom/features/ee/teams/lib/payments";
import { PermissionCheckService } from "@calcom/features/pbac/services/permission-check.service";
import { IS_TEAM_BILLING_ENABLED, WEBAPP_URL } from "@calcom/lib/constants";
import { Redirect } from "@calcom/lib/redirect";
import { isOrganisationAdmin } from "@calcom/lib/server/queries/organisations";
import { isTeamAdmin } from "@calcom/features/ee/teams/lib/queries";
import { TeamService } from "@calcom/lib/server/service/teamService";
import type { Prisma } from "@calcom/prisma/client";
import { MembershipRole } from "@calcom/prisma/enums";
import { teamMetadataSchema } from "@calcom/prisma/zod-utils";
import { TRPCError } from "@trpc/server";
import type { TrpcSessionUser } from "../../../types";
import type { TPublishInputSchema } from "./publish.schema";
import { Prisma } from "@calcom/prisma/client";
type PublishOptions = {
ctx: {
@@ -54,10 +54,20 @@ const generateCheckoutSession = async ({
async function checkPermissions({ ctx, input }: PublishOptions) {
const { profile } = ctx.user;
if (profile?.organizationId && !isOrganisationAdmin(ctx.user.id, profile.organizationId))
throw new TRPCError({ code: "UNAUTHORIZED" });
if (!profile?.organizationId && !(await isTeamAdmin(ctx.user.id, input.teamId)))
throw new TRPCError({ code: "UNAUTHORIZED" });
const permissionCheckService = new PermissionCheckService();
const isOrg = !!profile?.organizationId;
const permission = isOrg ? "organization.update" : "team.update";
const teamId = isOrg ? profile.organizationId : input.teamId;
const hasUpdatePermission = await permissionCheckService.checkPermission({
userId: ctx.user.id,
teamId,
permission,
fallbackRoles: [MembershipRole.OWNER, MembershipRole.ADMIN],
});
if (!hasUpdatePermission) throw new TRPCError({ code: "UNAUTHORIZED" });
}
export const publishHandler = async ({ ctx, input }: PublishOptions) => {
@@ -1,5 +1,6 @@
import { isTeamAdmin, isTeamOwner } from "@calcom/features/ee/teams/lib/queries";
import { PermissionCheckService } from "@calcom/features/pbac/services/permission-check.service";
import prisma from "@calcom/prisma";
import { MembershipRole } from "@calcom/prisma/enums";
import { TRPCError } from "@trpc/server";
@@ -15,11 +16,17 @@ type RemoveHostsFromEventTypes = {
export async function removeHostsFromEventTypesHandler({ ctx, input }: RemoveHostsFromEventTypes) {
const { userIds, eventTypeIds, teamId } = input;
const isTeamAdminOrOwner =
(await isTeamAdmin(ctx.user.id, teamId)) || (await isTeamOwner(ctx.user.id, teamId));
// check if user is admin or owner of team
if (!isTeamAdminOrOwner) throw new TRPCError({ code: "UNAUTHORIZED" });
const permissionCheckService = new PermissionCheckService();
const hasEventTypeUpdatePermission = await permissionCheckService.checkPermission({
userId: ctx.user.id,
teamId,
permission: "eventType.update",
fallbackRoles: [MembershipRole.OWNER, MembershipRole.ADMIN],
});
// check if user has permission to update event types
if (!hasEventTypeUpdatePermission) throw new TRPCError({ code: "UNAUTHORIZED" });
return await prisma.host.deleteMany({
where: {
@@ -1,5 +1,6 @@
import { isTeamAdmin } from "@calcom/features/ee/teams/lib/queries";
import { PermissionCheckService } from "@calcom/features/pbac/services/permission-check.service";
import { prisma } from "@calcom/prisma";
import { MembershipRole } from "@calcom/prisma/enums";
import type { TrpcSessionUser } from "@calcom/trpc/server/types";
import { TRPCError } from "@trpc/server";
@@ -22,12 +23,26 @@ export const setInviteExpirationHandler = async ({ ctx, input }: SetInviteExpira
},
select: {
teamId: true,
team: {
select: { isOrganization: true },
},
},
});
if (!verificationToken) throw new TRPCError({ code: "NOT_FOUND" });
if (!verificationToken.teamId || !(await isTeamAdmin(ctx.user.id, verificationToken.teamId)))
throw new TRPCError({ code: "UNAUTHORIZED" });
if (!verificationToken.teamId) throw new TRPCError({ code: "UNAUTHORIZED" });
const permissionCheckService = new PermissionCheckService();
const isOrgContext = !!verificationToken.team?.isOrganization;
const permission = isOrgContext ? "organization.invite" : "team.invite";
const hasInvitePermission = await permissionCheckService.checkPermission({
userId: ctx.user.id,
teamId: verificationToken.teamId,
permission,
fallbackRoles: [MembershipRole.OWNER, MembershipRole.ADMIN],
});
if (!hasInvitePermission) throw new TRPCError({ code: "UNAUTHORIZED" });
const oneDay = 24 * 60 * 60 * 1000;
const expires = expiresInDays
@@ -1,12 +1,12 @@
import { getOrgFullOrigin } from "@calcom/ee/organizations/lib/orgDomains";
import { PermissionCheckService } from "@calcom/features/pbac/services/permission-check.service";
import { IS_TEAM_BILLING_ENABLED } from "@calcom/lib/constants";
import type { IntervalLimit } from "@calcom/lib/intervalLimits/intervalLimitSchema";
import { validateIntervalLimitOrder } from "@calcom/lib/intervalLimits/validateIntervalLimitOrder";
import { uploadLogo } from "@calcom/lib/server/avatar";
import { isTeamAdmin } from "@calcom/features/ee/teams/lib/queries";
import { prisma } from "@calcom/prisma";
import type { Prisma } from "@calcom/prisma/client";
import { RedirectType, RRTimestampBasis } from "@calcom/prisma/enums";
import { MembershipRole, RedirectType, RRTimestampBasis } from "@calcom/prisma/enums";
import { teamMetadataStrictSchema } from "@calcom/prisma/zod-utils";
import { TRPCError } from "@trpc/server";
@@ -25,7 +25,15 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => {
const isOrgAdmin = ctx.user?.organization?.isOrgAdmin;
if (!isOrgAdmin) {
if (!(await isTeamAdmin(ctx.user?.id, input.id))) {
const permissionCheckService = new PermissionCheckService();
const hasTeamUpdatePermission = await permissionCheckService.checkPermission({
userId: ctx.user?.id || 0,
teamId: input.id,
permission: "team.update",
fallbackRoles: [MembershipRole.OWNER, MembershipRole.ADMIN],
});
if (!hasTeamUpdatePermission) {
throw new TRPCError({ code: "UNAUTHORIZED" });
}
}
@@ -1,5 +1,6 @@
import { isTeamAdmin } from "@calcom/features/ee/teams/lib/queries";
import { PermissionCheckService } from "@calcom/features/pbac/services/permission-check.service";
import { prisma } from "@calcom/prisma";
import { MembershipRole } from "@calcom/prisma/enums";
import type { TrpcSessionUser } from "@calcom/trpc/server/types";
import { TRPCError } from "@trpc/server";
@@ -20,7 +21,15 @@ export const updateInternalNotesPresetsHandler = async ({
const isOrgAdmin = ctx.user?.organization?.isOrgAdmin;
if (!isOrgAdmin) {
if (!(await isTeamAdmin(ctx.user?.id, input.teamId))) {
const permissionCheckService = new PermissionCheckService();
const hasTeamUpdatePermission = await permissionCheckService.checkPermission({
userId: ctx.user?.id || 0,
teamId: input.teamId,
permission: "team.update",
fallbackRoles: [MembershipRole.OWNER, MembershipRole.ADMIN],
});
if (!hasTeamUpdatePermission) {
throw new TRPCError({ code: "UNAUTHORIZED" });
}
}