Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions packages/features/bookings/lib/dto/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@ import type { z } from "zod";

import type getBookingDataSchema from "@calcom/features/bookings/lib/getBookingDataSchema";
import type getBookingDataSchemaForApi from "@calcom/features/bookings/lib/getBookingDataSchemaForApi";
import type { SchedulingType } from "@calcom/prisma/enums";
import type { BookingCreateBody as BaseCreateBookingData } from "@calcom/prisma/zod/custom/booking";
import type { extendedBookingCreateBody } from "@calcom/prisma/zod/custom/booking";

import type { Actor } from "../types/actor";

export type ExtendedBookingCreateData = z.input<typeof extendedBookingCreateBody>;
export type BookingDataSchemaGetter = typeof getBookingDataSchema | typeof getBookingDataSchemaForApi;

Expand All @@ -35,6 +38,12 @@ export type CreateBookingMeta = {
hostname?: string;
forcedSlug?: string;
noEmail?: boolean;
/**
* The actor performing the booking action.
* Used for audit logging to track who created/modified the booking.
* Optional for backward compatibility - defaults to System actor if not provided.
*/
actor?: Actor;
} & PlatformParams;

export type BookingHandlerInput = {
Expand Down
9 changes: 8 additions & 1 deletion packages/features/bookings/lib/handleCancelBooking.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { FAKE_DAILY_CREDENTIAL } from "@calcom/app-store/dailyvideo/lib/VideoApi
import { eventTypeMetaDataSchemaWithTypedApps } from "@calcom/app-store/zod-utils";
import dayjs from "@calcom/dayjs";
import { sendCancelledEmailsAndSMS } from "@calcom/emails";
import EventManager from "@calcom/features/bookings/lib/EventManager";
import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses";
import { processNoShowFeeOnCancellation } from "@calcom/features/bookings/lib/payment/processNoShowFeeOnCancellation";
import { processPaymentRefund } from "@calcom/features/bookings/lib/payment/processPaymentRefund";
Expand All @@ -17,7 +18,6 @@ import {
} from "@calcom/features/webhooks/lib/scheduleTrigger";
import sendPayload from "@calcom/features/webhooks/lib/sendOrSchedulePayload";
import type { EventTypeInfo } from "@calcom/features/webhooks/lib/sendPayload";
import EventManager from "@calcom/features/bookings/lib/EventManager";
import { getBookerBaseUrl } from "@calcom/lib/getBookerUrl/server";
import getOrgIdFromMemberOrTeamId from "@calcom/lib/getOrgIdFromMemberOrTeamId";
import { getTeamIdFromEventType } from "@calcom/lib/getTeamIdFromEventType";
Expand All @@ -42,6 +42,7 @@ import { getAllCredentialsIncludeServiceAccountKey } from "./getAllCredentialsFo
import { getBookingToDelete } from "./getBookingToDelete";
import { handleInternalNote } from "./handleInternalNote";
import cancelAttendeeSeat from "./handleSeats/cancel/cancelAttendeeSeat";
import type { Actor } from "./types/actor";

const log = logger.getSubLogger({ prefix: ["handleCancelBooking"] });

Expand All @@ -58,6 +59,12 @@ export type BookingToDelete = Awaited<ReturnType<typeof getBookingToDelete>>;
export type CancelBookingInput = {
userId?: number;
bookingData: z.infer<typeof bookingCancelInput>;
/**
* The actor performing the cancellation.
* Used for audit logging to track who cancelled the booking.
* Optional for backward compatibility - defaults to System actor if not provided.
*/
actor?: Actor;
} & PlatformParams;

export type HandleCancelBookingResponse = {
Expand Down
90 changes: 90 additions & 0 deletions packages/features/bookings/lib/types/actor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/**
* Represents the entity that performed a booking action
*/
export type Actor = {
/**
* The type of actor performing the action
*/
type: "User" | "System" | "Attendee";

/**
* The user ID if the actor is a User or Attendee
* Null if the actor is the System
*/
userId?: number | null;

/**
* Additional metadata about the actor
* e.g., email for Attendee, automation name for System
*/
metadata?: {
email?: string;
name?: string;
automationName?: string;
[key: string]: unknown;
};
};

/**
* Creates an Actor representing a User
*/
export function createUserActor(userId: number, metadata?: Actor["metadata"]): Actor {
return {
type: "User",
userId,
metadata,
};
}

/**
* Creates an Actor representing the System
*/
export function createSystemActor(metadata?: Actor["metadata"]): Actor {
return {
type: "System",
userId: null,
metadata,
};
}

/**
* Creates an Actor representing an Attendee
*/
export function createAttendeeActor(email: string, metadata?: Actor["metadata"]): Actor {
return {
type: "Attendee",
userId: null,
metadata: {
...metadata,
email,
},
};
}

/**
* Extracts the user ID from an actor if available
*/
export function getActorUserId(actor?: Actor): number | null | undefined {
return actor?.userId;
}

/**
* Converts an actor to the string representation needed for audit logs
*/
export function actorToAuditString(actor?: Actor): string | null {
if (!actor) return null;

if (actor.type === "User") {
return `User:${actor.userId}`;
}

if (actor.type === "Attendee" && actor.metadata?.email) {
return `Attendee:${actor.metadata.email}`;
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Oct 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rule violated: Avoid Logging Sensitive Information

Audit log serialization must not expose attendee emails. Returning Attendee:${actor.metadata.email} records PII directly into logs, violating the policy against logging sensitive data. Replace this with a non-PII identifier (e.g., just "Attendee") or a hashed token.

Prompt for AI agents
Address the following comment on packages/features/bookings/lib/types/actor.ts at line 82:

<comment>Audit log serialization must not expose attendee emails. Returning `Attendee:${actor.metadata.email}` records PII directly into logs, violating the policy against logging sensitive data. Replace this with a non-PII identifier (e.g., just &quot;Attendee&quot;) or a hashed token.</comment>

<file context>
@@ -0,0 +1,90 @@
+  }
+
+  if (actor.type === &quot;Attendee&quot; &amp;&amp; actor.metadata?.email) {
+    return `Attendee:${actor.metadata.email}`;
+  }
+
</file context>
Suggested change
return `Attendee:${actor.metadata.email}`;
return "Attendee";
Fix with Cubic

}

if (actor.type === "System") {
return actor.metadata?.automationName ? `System:${actor.metadata.automationName}` : "System";
}

return null;
}
33 changes: 33 additions & 0 deletions packages/features/bookings/lib/utils/auditLog.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import type { Prisma } from "@prisma/client";

import { PrismaBookingAuditRepository } from "@calcom/lib/server/repository/PrismaBookingAuditRepository";
import type { BookingAuditType, BookingAuditAction } from "@calcom/prisma/enums";

import type { Actor } from "../types/actor";
import { getActorUserId } from "../types/actor";

const auditRepository = new PrismaBookingAuditRepository();

export async function logBookingAudit({
bookingId,
actor,
type,
action,
data,
}: {
bookingId: string | number;
actor?: Actor;
type: BookingAuditType;
action?: BookingAuditAction;
data?: Prisma.InputJsonValue;
}): Promise<void> {
const userId = getActorUserId(actor);

await auditRepository.create({
bookingId: String(bookingId),
userId: userId ? String(userId) : null,
type,
action,
data,
});
}
Loading