chore: when using pii for rate limit (email, ip) hash (#22922)
This commit is contained in:
1 parent
a1fc4bccb9
commit
b41031626f
14 files changed
+69
-17
No files matched your search
@@ -7,6 +7,7 @@ import { passwordResetRequest } from "@calcom/features/auth/lib/passwordResetReq
|
||||
import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError";
|
||||
import { emailSchema } from "@calcom/lib/emailSchema";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { piiHasher } from "@calcom/lib/server/PiiHasher";
|
||||
|
||||
async function handler(req: NextRequest) {
|
||||
const body = await parseRequestData(req);
|
||||
@@ -28,7 +29,7 @@ async function handler(req: NextRequest) {
|
||||
|
||||
await checkRateLimitAndThrowError({
|
||||
rateLimitingType: "core",
|
||||
identifier: ip,
|
||||
identifier: piiHasher.hash(ip),
|
||||
});
|
||||
|
||||
try {
|
||||
|
||||
@@ -7,6 +7,7 @@ import getIP from "@calcom/lib/getIP";
|
||||
import { checkCfTurnstileToken } from "@calcom/lib/server/checkCfTurnstileToken";
|
||||
import { defaultResponder } from "@calcom/lib/server/defaultResponder";
|
||||
import { CreationSource } from "@calcom/prisma/enums";
|
||||
import { piiHasher } from "@calcom/lib/server/PiiHasher";
|
||||
|
||||
async function handler(req: NextApiRequest & { userId?: number }) {
|
||||
const userIp = getIP(req);
|
||||
@@ -20,7 +21,7 @@ async function handler(req: NextApiRequest & { userId?: number }) {
|
||||
|
||||
await checkRateLimitAndThrowError({
|
||||
rateLimitingType: "core",
|
||||
identifier: userIp,
|
||||
identifier: piiHasher.hash(userIp),
|
||||
});
|
||||
|
||||
const session = await getServerSession({ req });
|
||||
|
||||
@@ -6,13 +6,14 @@ import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowE
|
||||
import getIP from "@calcom/lib/getIP";
|
||||
import { defaultResponder } from "@calcom/lib/server/defaultResponder";
|
||||
import { CreationSource } from "@calcom/prisma/enums";
|
||||
import { piiHasher } from "@calcom/lib/server/PiiHasher";
|
||||
|
||||
async function handler(req: NextApiRequest & { userId?: number }) {
|
||||
const userIp = getIP(req);
|
||||
|
||||
await checkRateLimitAndThrowError({
|
||||
rateLimitingType: "core",
|
||||
identifier: `instant.event-${userIp}`,
|
||||
identifier: `instant.event-${piiHasher.hash(userIp)}`,
|
||||
});
|
||||
|
||||
const session = await getServerSession({ req });
|
||||
|
||||
@@ -7,6 +7,7 @@ import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowE
|
||||
import getIP from "@calcom/lib/getIP";
|
||||
import { checkCfTurnstileToken } from "@calcom/lib/server/checkCfTurnstileToken";
|
||||
import { defaultResponder } from "@calcom/lib/server/defaultResponder";
|
||||
import { piiHasher } from "@calcom/lib/server/PiiHasher";
|
||||
|
||||
// @TODO: Didn't look at the contents of this function in order to not break old booking page.
|
||||
|
||||
@@ -37,7 +38,7 @@ async function handler(req: NextApiRequest & RequestMeta) {
|
||||
|
||||
await checkRateLimitAndThrowError({
|
||||
rateLimitingType: "core",
|
||||
identifier: userIp,
|
||||
identifier: piiHasher.hash(userIp),
|
||||
});
|
||||
const session = await getServerSession({ req });
|
||||
/* To mimic API behavior and comply with types */
|
||||
|
||||
@@ -48,6 +48,7 @@ import { dub } from "./dub";
|
||||
import { isPasswordValid } from "./isPasswordValid";
|
||||
import CalComAdapter from "./next-auth-custom-adapter";
|
||||
import { verifyPassword } from "./verifyPassword";
|
||||
import { hashEmail } from "@calcom/lib/server/PiiHasher";
|
||||
|
||||
const log = logger.getSubLogger({ prefix: ["next-auth-options"] });
|
||||
const GOOGLE_API_CREDENTIALS = process.env.GOOGLE_API_CREDENTIALS || "{}";
|
||||
@@ -131,7 +132,7 @@ const providers: Provider[] = [
|
||||
}
|
||||
|
||||
await checkRateLimitAndThrowError({
|
||||
identifier: user.email,
|
||||
identifier: hashEmail(user.email),
|
||||
});
|
||||
|
||||
if (!user.password?.hash && user.identityProvider !== IdentityProvider.CAL && !credentials.totpCode) {
|
||||
|
||||
@@ -13,6 +13,7 @@ import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import { getTranslation } from "@calcom/lib/server/i18n";
|
||||
import { prisma } from "@calcom/prisma";
|
||||
import { hashEmail } from "@calcom/lib/server/PiiHasher";
|
||||
|
||||
const log = logger.getSubLogger({ prefix: [`[[Auth] `] });
|
||||
|
||||
@@ -54,7 +55,7 @@ export const sendEmailVerification = async ({
|
||||
|
||||
await checkRateLimitAndThrowError({
|
||||
rateLimitingType: "core",
|
||||
identifier: email,
|
||||
identifier: hashEmail(email),
|
||||
});
|
||||
|
||||
await prisma.verificationToken.create({
|
||||
@@ -142,7 +143,7 @@ export const sendChangeOfEmailVerification = async ({ user, language }: ChangeOf
|
||||
|
||||
await checkRateLimitAndThrowError({
|
||||
rateLimitingType: "core",
|
||||
identifier: user.emailFrom,
|
||||
identifier: hashEmail(user.emailFrom),
|
||||
});
|
||||
|
||||
await prisma.verificationToken.create({
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { hashEmail, Md5PiiHasher } from "./PiiHasher";
|
||||
|
||||
describe("PII Hasher Test Suite", () => {
|
||||
|
||||
const hasher = new Md5PiiHasher("test-salt");
|
||||
|
||||
it("can hash email addresses", async () => {
|
||||
const email = "sensitive_data@example.com";
|
||||
const hashedEmail = hashEmail(email, hasher);
|
||||
expect(hashedEmail).toBe("2e74ca9edc8add1709b0d049aa2a0959@example.com");
|
||||
});
|
||||
|
||||
it("can hash PII with saltyMd5", async () => {
|
||||
const pii = "sensitive_data";
|
||||
const hashedPii = hasher.hash(pii);
|
||||
expect(hashedPii).toBe("2e74ca9edc8add1709b0d049aa2a0959");
|
||||
});
|
||||
|
||||
it("handles hashing with different salt", () => {
|
||||
const differentHasher = new Md5PiiHasher("different-salt");
|
||||
const pii = "sensitive_data";
|
||||
const hashedPii = differentHasher.hash(pii);
|
||||
expect(hashedPii).not.toBe(hasher.hash(pii));
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,20 @@
|
||||
import { createHash } from "crypto";
|
||||
|
||||
export interface PiiHasher {
|
||||
hash(input: string): string;
|
||||
}
|
||||
|
||||
export class Md5PiiHasher implements PiiHasher {
|
||||
constructor(private readonly salt: string) {}
|
||||
hash(input: string) {
|
||||
return createHash("md5").update(this.salt + input).digest("hex");
|
||||
}
|
||||
}
|
||||
|
||||
export const piiHasher: PiiHasher = new Md5PiiHasher(process.env.CALENDSO_ENCRYPTION_KEY!);
|
||||
|
||||
export const hashEmail = (email: string, hasher: PiiHasher = piiHasher): string => {
|
||||
const [localPart, domain] = email.split("@");
|
||||
// Simple hash function for email, can be replaced with a more complex one if needed
|
||||
return hasher.hash(localPart) + "@" + domain;
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import { sendSmsOrFallbackEmail } from "@calcom/features/ee/workflows/lib/remind
|
||||
import { checkSMSRateLimit } from "@calcom/lib/checkRateLimitAndThrowError";
|
||||
import { SENDER_ID } from "@calcom/lib/constants";
|
||||
import isSmsCalEmail from "@calcom/lib/isSmsCalEmail";
|
||||
import { piiHasher } from "@calcom/lib/server/PiiHasher";
|
||||
import { TimeFormat } from "@calcom/lib/timeFormat";
|
||||
import prisma from "@calcom/prisma";
|
||||
import type { CalendarEvent, Person } from "@calcom/types/Calendar";
|
||||
@@ -32,7 +33,7 @@ const handleSendingSMS = async ({
|
||||
? `handleSendingSMS:team:${teamId}`
|
||||
: organizerUserId
|
||||
? `handleSendingSMS:user:${organizerUserId}`
|
||||
: `handleSendingSMS:user:${reminderPhone}`,
|
||||
: `handleSendingSMS:user:${piiHasher.hash(reminderPhone)}`,
|
||||
rateLimitingType: "sms",
|
||||
});
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ export const addSecondaryEmailHandler = async ({ ctx, input }: AddSecondaryEmail
|
||||
|
||||
await checkRateLimitAndThrowError({
|
||||
rateLimitingType: "core",
|
||||
identifier: `addSecondaryEmail.${user.email}`,
|
||||
identifier: `addSecondaryEmail.${user.id}`,
|
||||
});
|
||||
|
||||
const existingPrimaryEmail = await prisma.user.findUnique({
|
||||
|
||||
@@ -21,11 +21,10 @@ const log = logger.getSubLogger({ prefix: [`[[Auth] `] });
|
||||
|
||||
export const resendVerifyEmail = async ({ input, ctx }: ResendEmailOptions) => {
|
||||
let emailToVerify = ctx.user.email;
|
||||
const identifer = emailToVerify;
|
||||
|
||||
await checkRateLimitAndThrowError({
|
||||
rateLimitingType: "core",
|
||||
identifier: `resendVerifyEmail.${identifer}`,
|
||||
identifier: `resendVerifyEmail.${ctx.user.id}`,
|
||||
});
|
||||
|
||||
let emailVerified = Boolean(ctx.user.emailVerified);
|
||||
|
||||
@@ -6,6 +6,7 @@ import getIP from "@calcom/lib/getIP";
|
||||
|
||||
import type { TRPCContext } from "../../../createContext";
|
||||
import type { TSendVerifyEmailCodeSchema } from "./sendVerifyEmailCode.schema";
|
||||
import { hashEmail, piiHasher } from "@calcom/lib/server/PiiHasher";
|
||||
|
||||
type SendVerifyEmailCode = {
|
||||
input: TSendVerifyEmailCodeSchema;
|
||||
@@ -13,11 +14,11 @@ type SendVerifyEmailCode = {
|
||||
};
|
||||
|
||||
export const sendVerifyEmailCodeHandler = async ({ input, req }: SendVerifyEmailCode) => {
|
||||
const identifer = req ? getIP(req as NextApiRequest) : input.email;
|
||||
const identifier = req ? piiHasher.hash(getIP(req as NextApiRequest)) : hashEmail(input.email);
|
||||
|
||||
await checkRateLimitAndThrowError({
|
||||
rateLimitingType: "core",
|
||||
identifier: `emailVerifyByCode.${identifer}`,
|
||||
identifier: `emailVerifyByCode.${identifier}`,
|
||||
});
|
||||
|
||||
const email = await sendEmailVerificationByCode({
|
||||
|
||||
@@ -8,6 +8,7 @@ import type { ZVerifyCodeInputSchema } from "@calcom/prisma/zod-utils";
|
||||
import type { TrpcSessionUser } from "@calcom/trpc/server/types";
|
||||
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { hashEmail } from "@calcom/lib/server/PiiHasher";
|
||||
|
||||
type VerifyCodeOptions = {
|
||||
ctx: {
|
||||
@@ -34,7 +35,7 @@ export const verifyCodeHandler = async ({ ctx, input }: VerifyCodeOptions) => {
|
||||
|
||||
await checkRateLimitAndThrowError({
|
||||
rateLimitingType: "core",
|
||||
identifier: email,
|
||||
identifier: hashEmail(email),
|
||||
});
|
||||
|
||||
const secret = createHash("md5")
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import { isTeamAdmin, isTeamOwner } from "@calcom/lib/server/queries/teams";
|
||||
import { TeamService } from "@calcom/lib/server/service/teamService";
|
||||
import type { TrpcSessionUser } from "@calcom/trpc/server/types";
|
||||
@@ -8,7 +7,6 @@ import { TRPCError } from "@trpc/server";
|
||||
|
||||
import type { TRemoveMemberInputSchema } from "./removeMember.schema";
|
||||
|
||||
const log = logger.getSubLogger({ prefix: ["viewer/teams/removeMember.handler"] });
|
||||
type RemoveMemberOptions = {
|
||||
ctx: {
|
||||
user: NonNullable<TrpcSessionUser>;
|
||||
@@ -19,7 +17,7 @@ type RemoveMemberOptions = {
|
||||
|
||||
export const removeMemberHandler = async ({ ctx, input }: RemoveMemberOptions) => {
|
||||
await checkRateLimitAndThrowError({
|
||||
identifier: `removeMember.${ctx.sourceIp}`,
|
||||
identifier: `removeMember.${ctx.user.id}`,
|
||||
});
|
||||
|
||||
const { memberIds, teamIds, isOrg } = input;
|
||||
|
||||
Reference in new issue
Block a user