Skip to content

Commit b410316

Browse files
authored
chore: when using pii for rate limit (email, ip) hash (#22922)
1 parent a1fc4bc commit b410316

File tree

14 files changed

+69
-17
lines changed

14 files changed

+69
-17
lines changed

apps/web/app/api/auth/forgot-password/route.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { passwordResetRequest } from "@calcom/features/auth/lib/passwordResetReq
77
import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError";
88
import { emailSchema } from "@calcom/lib/emailSchema";
99
import prisma from "@calcom/prisma";
10+
import { piiHasher } from "@calcom/lib/server/PiiHasher";
1011

1112
async function handler(req: NextRequest) {
1213
const body = await parseRequestData(req);
@@ -28,7 +29,7 @@ async function handler(req: NextRequest) {
2829

2930
await checkRateLimitAndThrowError({
3031
rateLimitingType: "core",
31-
identifier: ip,
32+
identifier: piiHasher.hash(ip),
3233
});
3334

3435
try {

apps/web/pages/api/book/event.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import getIP from "@calcom/lib/getIP";
77
import { checkCfTurnstileToken } from "@calcom/lib/server/checkCfTurnstileToken";
88
import { defaultResponder } from "@calcom/lib/server/defaultResponder";
99
import { CreationSource } from "@calcom/prisma/enums";
10+
import { piiHasher } from "@calcom/lib/server/PiiHasher";
1011

1112
async function handler(req: NextApiRequest & { userId?: number }) {
1213
const userIp = getIP(req);
@@ -20,7 +21,7 @@ async function handler(req: NextApiRequest & { userId?: number }) {
2021

2122
await checkRateLimitAndThrowError({
2223
rateLimitingType: "core",
23-
identifier: userIp,
24+
identifier: piiHasher.hash(userIp),
2425
});
2526

2627
const session = await getServerSession({ req });

apps/web/pages/api/book/instant-event.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@ import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowE
66
import getIP from "@calcom/lib/getIP";
77
import { defaultResponder } from "@calcom/lib/server/defaultResponder";
88
import { CreationSource } from "@calcom/prisma/enums";
9+
import { piiHasher } from "@calcom/lib/server/PiiHasher";
910

1011
async function handler(req: NextApiRequest & { userId?: number }) {
1112
const userIp = getIP(req);
1213

1314
await checkRateLimitAndThrowError({
1415
rateLimitingType: "core",
15-
identifier: `instant.event-${userIp}`,
16+
identifier: `instant.event-${piiHasher.hash(userIp)}`,
1617
});
1718

1819
const session = await getServerSession({ req });

apps/web/pages/api/book/recurring-event.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowE
77
import getIP from "@calcom/lib/getIP";
88
import { checkCfTurnstileToken } from "@calcom/lib/server/checkCfTurnstileToken";
99
import { defaultResponder } from "@calcom/lib/server/defaultResponder";
10+
import { piiHasher } from "@calcom/lib/server/PiiHasher";
1011

1112
// @TODO: Didn't look at the contents of this function in order to not break old booking page.
1213

@@ -37,7 +38,7 @@ async function handler(req: NextApiRequest & RequestMeta) {
3738

3839
await checkRateLimitAndThrowError({
3940
rateLimitingType: "core",
40-
identifier: userIp,
41+
identifier: piiHasher.hash(userIp),
4142
});
4243
const session = await getServerSession({ req });
4344
/* To mimic API behavior and comply with types */

packages/features/auth/lib/next-auth-options.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ import { dub } from "./dub";
4848
import { isPasswordValid } from "./isPasswordValid";
4949
import CalComAdapter from "./next-auth-custom-adapter";
5050
import { verifyPassword } from "./verifyPassword";
51+
import { hashEmail } from "@calcom/lib/server/PiiHasher";
5152

5253
const log = logger.getSubLogger({ prefix: ["next-auth-options"] });
5354
const GOOGLE_API_CREDENTIALS = process.env.GOOGLE_API_CREDENTIALS || "{}";
@@ -131,7 +132,7 @@ const providers: Provider[] = [
131132
}
132133

133134
await checkRateLimitAndThrowError({
134-
identifier: user.email,
135+
identifier: hashEmail(user.email),
135136
});
136137

137138
if (!user.password?.hash && user.identityProvider !== IdentityProvider.CAL && !credentials.totpCode) {

packages/features/auth/lib/verifyEmail.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { WEBAPP_URL } from "@calcom/lib/constants";
1313
import logger from "@calcom/lib/logger";
1414
import { getTranslation } from "@calcom/lib/server/i18n";
1515
import { prisma } from "@calcom/prisma";
16+
import { hashEmail } from "@calcom/lib/server/PiiHasher";
1617

1718
const log = logger.getSubLogger({ prefix: [`[[Auth] `] });
1819

@@ -54,7 +55,7 @@ export const sendEmailVerification = async ({
5455

5556
await checkRateLimitAndThrowError({
5657
rateLimitingType: "core",
57-
identifier: email,
58+
identifier: hashEmail(email),
5859
});
5960

6061
await prisma.verificationToken.create({
@@ -142,7 +143,7 @@ export const sendChangeOfEmailVerification = async ({ user, language }: ChangeOf
142143

143144
await checkRateLimitAndThrowError({
144145
rateLimitingType: "core",
145-
identifier: user.emailFrom,
146+
identifier: hashEmail(user.emailFrom),
146147
});
147148

148149
await prisma.verificationToken.create({
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { describe, it, expect } from "vitest";
2+
import { hashEmail, Md5PiiHasher } from "./PiiHasher";
3+
4+
describe("PII Hasher Test Suite", () => {
5+
6+
const hasher = new Md5PiiHasher("test-salt");
7+
8+
it("can hash email addresses", async () => {
9+
const email = "[email protected]";
10+
const hashedEmail = hashEmail(email, hasher);
11+
expect(hashedEmail).toBe("[email protected]");
12+
});
13+
14+
it("can hash PII with saltyMd5", async () => {
15+
const pii = "sensitive_data";
16+
const hashedPii = hasher.hash(pii);
17+
expect(hashedPii).toBe("2e74ca9edc8add1709b0d049aa2a0959");
18+
});
19+
20+
it("handles hashing with different salt", () => {
21+
const differentHasher = new Md5PiiHasher("different-salt");
22+
const pii = "sensitive_data";
23+
const hashedPii = differentHasher.hash(pii);
24+
expect(hashedPii).not.toBe(hasher.hash(pii));
25+
});
26+
});

packages/lib/server/PiiHasher.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { createHash } from "crypto";
2+
3+
export interface PiiHasher {
4+
hash(input: string): string;
5+
}
6+
7+
export class Md5PiiHasher implements PiiHasher {
8+
constructor(private readonly salt: string) {}
9+
hash(input: string) {
10+
return createHash("md5").update(this.salt + input).digest("hex");
11+
}
12+
}
13+
14+
export const piiHasher: PiiHasher = new Md5PiiHasher(process.env.CALENDSO_ENCRYPTION_KEY!);
15+
16+
export const hashEmail = (email: string, hasher: PiiHasher = piiHasher): string => {
17+
const [localPart, domain] = email.split("@");
18+
// Simple hash function for email, can be replaced with a more complex one if needed
19+
return hasher.hash(localPart) + "@" + domain;
20+
}

packages/sms/sms-manager.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { sendSmsOrFallbackEmail } from "@calcom/features/ee/workflows/lib/remind
44
import { checkSMSRateLimit } from "@calcom/lib/checkRateLimitAndThrowError";
55
import { SENDER_ID } from "@calcom/lib/constants";
66
import isSmsCalEmail from "@calcom/lib/isSmsCalEmail";
7+
import { piiHasher } from "@calcom/lib/server/PiiHasher";
78
import { TimeFormat } from "@calcom/lib/timeFormat";
89
import prisma from "@calcom/prisma";
910
import type { CalendarEvent, Person } from "@calcom/types/Calendar";
@@ -32,7 +33,7 @@ const handleSendingSMS = async ({
3233
? `handleSendingSMS:team:${teamId}`
3334
: organizerUserId
3435
? `handleSendingSMS:user:${organizerUserId}`
35-
: `handleSendingSMS:user:${reminderPhone}`,
36+
: `handleSendingSMS:user:${piiHasher.hash(reminderPhone)}`,
3637
rateLimitingType: "sms",
3738
});
3839

packages/trpc/server/routers/loggedInViewer/addSecondaryEmail.handler.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export const addSecondaryEmailHandler = async ({ ctx, input }: AddSecondaryEmail
2222

2323
await checkRateLimitAndThrowError({
2424
rateLimitingType: "core",
25-
identifier: `addSecondaryEmail.${user.email}`,
25+
identifier: `addSecondaryEmail.${user.id}`,
2626
});
2727

2828
const existingPrimaryEmail = await prisma.user.findUnique({

0 commit comments

Comments
 (0)