Security Fixes (#224)
Fixes - 2,3,4 security vulnerabilities reported in this message. https://calendso.slack.com/archives/C03127U5S5Q/p1671922033089329 More Fixes - Dont't allow a user to add a random attendee to a booking not owned by him - Don't allow a user to add a random cal user as an organizer of the booking. - Membership deletion should be as per the Privileges of Owner,Admin,Member
This commit is contained in:
1 parent
604d937661
commit
9c23a8e5ab
7 files changed
+144
-19
No files matched your search
@@ -1,5 +1,7 @@
|
||||
import type { NextApiRequest } from "next";
|
||||
import { z } from "zod";
|
||||
|
||||
import { HttpError } from "@calcom/lib/http-error";
|
||||
import { defaultResponder } from "@calcom/lib/server";
|
||||
|
||||
import { schemaAttendeeEditBodyParams, schemaAttendeeReadPublic } from "~/lib/validations/attendee";
|
||||
@@ -50,8 +52,21 @@ export async function patchHandler(req: NextApiRequest) {
|
||||
const { prisma, query, body } = req;
|
||||
const { id } = schemaQueryIdParseInt.parse(query);
|
||||
const data = schemaAttendeeEditBodyParams.parse(body);
|
||||
await checkPermissions(req, data);
|
||||
const attendee = await prisma.attendee.update({ where: { id }, data });
|
||||
return { attendee: schemaAttendeeReadPublic.parse(attendee) };
|
||||
}
|
||||
|
||||
async function checkPermissions(req: NextApiRequest, body: z.infer<typeof schemaAttendeeEditBodyParams>) {
|
||||
const { isAdmin, prisma } = req;
|
||||
if (isAdmin) return;
|
||||
const { userId } = req;
|
||||
const { bookingId } = body;
|
||||
if (bookingId) {
|
||||
// Ensure that the booking the attendee is being added to belongs to the user
|
||||
const booking = await prisma.booking.findFirst({ where: { id: bookingId, userId } });
|
||||
if (!booking) throw new HttpError({ statusCode: 403, message: "You don't have access to the booking" });
|
||||
}
|
||||
}
|
||||
|
||||
export default defaultResponder(patchHandler);
|
||||
@@ -1,5 +1,7 @@
|
||||
import type { NextApiRequest } from "next";
|
||||
import { z } from "zod";
|
||||
|
||||
import { HttpError } from "@calcom/lib/http-error";
|
||||
import { defaultResponder } from "@calcom/lib/server";
|
||||
|
||||
import { schemaBookingEditBodyParams, schemaBookingReadPublic } from "~/lib/validations/booking";
|
||||
@@ -49,8 +51,20 @@ export async function patchHandler(req: NextApiRequest) {
|
||||
const { prisma, query, body } = req;
|
||||
const { id } = schemaQueryIdParseInt.parse(query);
|
||||
const data = schemaBookingEditBodyParams.parse(body);
|
||||
await checkPermissions(req, data);
|
||||
const booking = await prisma.booking.update({ where: { id }, data });
|
||||
return { booking: schemaBookingReadPublic.parse(booking) };
|
||||
}
|
||||
|
||||
async function checkPermissions(req: NextApiRequest, body: z.infer<typeof schemaBookingEditBodyParams>) {
|
||||
const { isAdmin } = req;
|
||||
if (body.userId && !isAdmin) {
|
||||
// Organizer has to be a cal user and we can't allow a booking to be transfered to some other cal user's name
|
||||
throw new HttpError({
|
||||
statusCode: 403,
|
||||
message: "Only admin can change the organizer of a booking",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default defaultResponder(patchHandler);
|
||||
@@ -1,11 +1,18 @@
|
||||
import type { NextApiRequest } from "next";
|
||||
import { z } from "zod";
|
||||
|
||||
import { HttpError } from "@calcom/lib/http-error";
|
||||
import { defaultResponder } from "@calcom/lib/server";
|
||||
|
||||
import { schemaEventTypeEditBodyParams, schemaEventTypeReadPublic } from "~/lib/validations/event-type";
|
||||
import {
|
||||
schemaEventTypeEditBodyParams,
|
||||
schemaEventTypeBaseBodyParams,
|
||||
schemaEventTypeReadPublic,
|
||||
} from "~/lib/validations/event-type";
|
||||
import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransformParseInt";
|
||||
|
||||
import checkTeamEventEditPermission from "../_utils/checkTeamEventEditPermission";
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /event-types/{id}:
|
||||
@@ -37,18 +44,19 @@ export async function patchHandler(req: NextApiRequest) {
|
||||
const { prisma, query, body } = req;
|
||||
const { id } = schemaQueryIdParseInt.parse(query);
|
||||
const data = schemaEventTypeEditBodyParams.parse(body);
|
||||
await checkPermissions(req);
|
||||
await checkPermissions(req, body);
|
||||
const event_type = await prisma.eventType.update({ where: { id }, data });
|
||||
return { event_type: schemaEventTypeReadPublic.parse(event_type) };
|
||||
}
|
||||
|
||||
async function checkPermissions(req: NextApiRequest) {
|
||||
async function checkPermissions(req: NextApiRequest, body: z.infer<typeof schemaEventTypeBaseBodyParams>) {
|
||||
const { userId, prisma, isAdmin } = req;
|
||||
const { id } = schemaQueryIdParseInt.parse(req.query);
|
||||
if (isAdmin) return;
|
||||
/** Only event type owners can modify it */
|
||||
const eventType = await prisma.eventType.findFirst({ where: { id, userId } });
|
||||
if (!eventType) throw new HttpError({ statusCode: 403, message: "Forbidden" });
|
||||
await checkTeamEventEditPermission(req, body);
|
||||
}
|
||||
|
||||
export default defaultResponder(patchHandler);
|
||||
@@ -6,6 +6,8 @@ import { defaultResponder } from "@calcom/lib/server";
|
||||
|
||||
import { schemaEventTypeCreateBodyParams, schemaEventTypeReadPublic } from "~/lib/validations/event-type";
|
||||
|
||||
import checkTeamEventEditPermission from "./_utils/checkTeamEventEditPermission";
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /event-types:
|
||||
@@ -61,22 +63,7 @@ async function postHandler(req: NextApiRequest) {
|
||||
|
||||
if (isAdmin && body.userId) args = { data: { ...body, users: { connect: { id: body.userId } } } };
|
||||
|
||||
if (body.teamId) {
|
||||
const hasMembership = await prisma.membership.findFirst({
|
||||
where: {
|
||||
userId,
|
||||
teamId: body.teamId,
|
||||
accepted: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!hasMembership?.role || !["ADMIN", "OWNER"].includes(hasMembership.role)) {
|
||||
throw new HttpError({
|
||||
statusCode: 401,
|
||||
message: "No permission to create an event-type for this team`",
|
||||
});
|
||||
}
|
||||
}
|
||||
await checkTeamEventEditPermission(req, body);
|
||||
|
||||
const data = await prisma.eventType.create(args);
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import type { NextApiRequest } from "next";
|
||||
import { z } from "zod";
|
||||
|
||||
import { HttpError } from "@calcom/lib/http-error";
|
||||
|
||||
import { schemaEventTypeBaseBodyParams } from "~/lib/validations/event-type";
|
||||
|
||||
export default async function checkTeamEventEditPermission(
|
||||
req: NextApiRequest,
|
||||
body: Pick<z.infer<typeof schemaEventTypeBaseBodyParams>, "teamId">
|
||||
) {
|
||||
const { prisma, userId } = req;
|
||||
if (body.teamId) {
|
||||
const membership = await prisma.membership.findFirst({
|
||||
where: {
|
||||
userId,
|
||||
teamId: body.teamId,
|
||||
accepted: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!membership?.role || !["ADMIN", "OWNER"].includes(membership.role)) {
|
||||
throw new HttpError({
|
||||
statusCode: 401,
|
||||
message: "No permission to operate on event-type for this team",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { NextApiRequest } from "next";
|
||||
|
||||
import { HttpError } from "@calcom/lib/http-error";
|
||||
import { defaultResponder } from "@calcom/lib/server";
|
||||
|
||||
import { membershipIdSchema } from "~/lib/validations/membership";
|
||||
@@ -35,8 +36,67 @@ import { membershipIdSchema } from "~/lib/validations/membership";
|
||||
export async function deleteHandler(req: NextApiRequest) {
|
||||
const { prisma, query } = req;
|
||||
const userId_teamId = membershipIdSchema.parse(query);
|
||||
await checkPermissions(req);
|
||||
await prisma.membership.delete({ where: { userId_teamId } });
|
||||
return { message: `Membership with id: ${query.id} deleted successfully` };
|
||||
}
|
||||
|
||||
async function checkPermissions(req: NextApiRequest) {
|
||||
const { prisma, isAdmin, userId, query } = req;
|
||||
const userId_teamId = membershipIdSchema.parse(query);
|
||||
// Admin User can do anything including deletion of Admin Team Member in any team
|
||||
if (isAdmin) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Owner can delete Admin and Member
|
||||
// Admin Team Member can delete Member
|
||||
// Member can't delete anyone
|
||||
const PRIVILEGE_ORDER = ["OWNER", "ADMIN", "MEMBER"];
|
||||
|
||||
const memberShipToBeDeleted = await prisma.membership.findUnique({
|
||||
where: { userId_teamId },
|
||||
});
|
||||
|
||||
if (!memberShipToBeDeleted) {
|
||||
throw new HttpError({ statusCode: 404, message: "Membership not found" });
|
||||
}
|
||||
|
||||
// If a user is deleting their own membership, then they can do it
|
||||
if (userId === memberShipToBeDeleted.userId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentUserMembership = await prisma.membership.findUnique({
|
||||
where: {
|
||||
userId_teamId: {
|
||||
userId,
|
||||
teamId: memberShipToBeDeleted.teamId,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!currentUserMembership) {
|
||||
// Current User isn't a member of the team
|
||||
throw new HttpError({ statusCode: 403, message: "You are not a member of the team" });
|
||||
}
|
||||
|
||||
if (
|
||||
PRIVILEGE_ORDER.indexOf(memberShipToBeDeleted.role) === -1 ||
|
||||
PRIVILEGE_ORDER.indexOf(currentUserMembership.role) === -1
|
||||
) {
|
||||
throw new HttpError({ statusCode: 400, message: "Invalid role" });
|
||||
}
|
||||
|
||||
// If Role that is being deleted comes before the current User's Role, or it's the same ROLE, throw error
|
||||
if (
|
||||
PRIVILEGE_ORDER.indexOf(memberShipToBeDeleted.role) <= PRIVILEGE_ORDER.indexOf(currentUserMembership.role)
|
||||
) {
|
||||
throw new HttpError({
|
||||
statusCode: 403,
|
||||
message: "You don't have the appropriate role to delete this membership",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default defaultResponder(deleteHandler);
|
||||
@@ -1,5 +1,7 @@
|
||||
import type { NextApiRequest } from "next";
|
||||
import { z } from "zod";
|
||||
|
||||
import { HttpError } from "@calcom/lib/http-error";
|
||||
import { defaultResponder } from "@calcom/lib/server";
|
||||
|
||||
import { schemaSchedulePublic, schemaSingleScheduleBodyParams } from "~/lib/validations/schedule";
|
||||
@@ -32,8 +34,18 @@ export async function patchHandler(req: NextApiRequest) {
|
||||
const { prisma, query } = req;
|
||||
const { id } = schemaQueryIdParseInt.parse(query);
|
||||
const data = schemaSingleScheduleBodyParams.parse(req.body);
|
||||
await checkPermissions(req, data);
|
||||
const result = await prisma.schedule.update({ where: { id }, data, include: { availability: true } });
|
||||
return { schedule: schemaSchedulePublic.parse(result) };
|
||||
}
|
||||
|
||||
async function checkPermissions(req: NextApiRequest, body: z.infer<typeof schemaSingleScheduleBodyParams>) {
|
||||
const { isAdmin } = req;
|
||||
if (isAdmin) return;
|
||||
if (body.userId) {
|
||||
throw new HttpError({ statusCode: 403, message: "Non admin cannot change the owner of a schedule" });
|
||||
}
|
||||
//_auth-middleware takes care of verifying the ownership of schedule.
|
||||
}
|
||||
|
||||
export default defaultResponder(patchHandler);
|
||||
Reference in new issue
Block a user