Skip to content

Commit 56ef5d6

Browse files
authored
fix: Hotfix in change from @spammer.com to spammer.com (calcom#24479)
* fix: Hotfix in change from @spammer.com to spammer.com * Test fixes + string * Removed redundant test, fixed error message * Comment fixup
1 parent bb9d99c commit 56ef5d6

5 files changed

Lines changed: 57 additions & 48 deletions

File tree

apps/web/modules/settings/organizations/privacy/components/create-blocklist-entry-modal.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import { Controller, useForm } from "react-hook-form";
44

5-
import { domainWithAtRegex, emailRegex } from "@calcom/lib/emailSchema";
5+
import { domainRegex, emailRegex } from "@calcom/lib/emailSchema";
66
import { useLocale } from "@calcom/lib/hooks/useLocale";
77
import { WatchlistType } from "@calcom/prisma/enums";
88
import { trpc } from "@calcom/trpc/react";
@@ -71,7 +71,7 @@ export function CreateBlocklistEntryModal({ isOpen, onClose }: CreateBlocklistEn
7171
return t("invalid_email_address");
7272
}
7373
} else if (watchType === WatchlistType.DOMAIN) {
74-
if (!domainWithAtRegex.test(value)) {
74+
if (!domainRegex.test(value)) {
7575
return t("invalid_domain_format");
7676
}
7777
}
@@ -130,7 +130,7 @@ export function CreateBlocklistEntryModal({ isOpen, onClose }: CreateBlocklistEn
130130
render={({ field }) => (
131131
<Input
132132
{...field}
133-
placeholder={watchType === WatchlistType.EMAIL ? "user@example.com" : "@spammer.com"}
133+
placeholder={watchType === WatchlistType.EMAIL ? "user@example.com" : "spammer.com"}
134134
/>
135135
)}
136136
/>

apps/web/public/static/locales/en/common.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3834,7 +3834,7 @@
38343834
"no_description_provided": "No description provided",
38353835
"organization_blocklist": "Organization Blocklist",
38363836
"manage_blocked_emails_and_domains": "Manage blocked emails and domains for your organization",
3837-
"invalid_domain_format": "Invalid domain format. Example: @example.com",
3837+
"invalid_domain_format": "Invalid domain format. Example: example.com",
38383838
"invalid_email_address": "Invalid email address. Example: user@example.com",
38393839
"reason_for_adding_to_blocklist": "Reason for adding to blocklist",
38403840
"what_would_you_like_to_block": "What would you like to block?",

packages/lib/emailSchema.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
import { z } from "zod";
22

33
/** @see https://github.com/colinhacks/zod/issues/3155#issuecomment-2060045794 */
4+
45
export const emailRegex =
6+
/* eslint-disable-next-line no-useless-escape */
57
/^(?!\.)(?!.*\.\.)([A-Z0-9_+-\.']*)[A-Z0-9_+'-]@([A-Z0-9][A-Z0-9\-]*\.)+[A-Z]{2,}$/i;
68

79
/**
8-
* Domain regex for watchlist entries - requires @ prefix
10+
* Domain regex for watchlist entries
911
* Supports international domains with Unicode characters
10-
* Examples: @example.com, @münchen.de, @example.co.uk
12+
* Examples: example.com, münchen.de, example.co.uk
1113
*/
12-
export const domainWithAtRegex =
13-
/^@[a-zA-Z0-9\u00a1-\uffff]([a-zA-Z0-9\u00a1-\uffff-]*[a-zA-Z0-9\u00a1-\uffff])?(\.[a-zA-Z0-9\u00a1-\uffff]([a-zA-Z0-9\u00a1-\uffff-]*[a-zA-Z0-9\u00a1-\uffff])?)*$/;
14+
export const domainRegex =
15+
/^[a-zA-Z0-9\u00a1-\uffff]([a-zA-Z0-9\u00a1-\uffff-]*[a-zA-Z0-9\u00a1-\uffff])?(\.[a-zA-Z0-9\u00a1-\uffff]([a-zA-Z0-9\u00a1-\uffff-]*[a-zA-Z0-9\u00a1-\uffff])?)*$/;
1416

1517
/**
1618
* RFC 5321 Section 4.5.3.1.3 specifies:

packages/trpc/server/routers/viewer/organizations/createWatchlistEntry.handler.test.ts

Lines changed: 44 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1+
/* eslint-disable @typescript-eslint/no-explicit-any */
12
import { describe, it, expect, vi, beforeEach } from "vitest";
23

34
import { PermissionCheckService } from "@calcom/features/pbac/services/permission-check.service";
45
import { WatchlistRepository } from "@calcom/lib/server/repository/watchlist.repository";
56
import { WatchlistType, WatchlistAction, MembershipRole } from "@calcom/prisma/enums";
67

8+
import { TRPCError } from "@trpc/server";
9+
710
import { createWatchlistEntryHandler } from "./createWatchlistEntry.handler";
811

912
vi.mock("@calcom/features/pbac/services/permission-check.service");
@@ -35,7 +38,7 @@ describe("createWatchlistEntryHandler", () => {
3538
it("should throw FORBIDDEN when user is not part of an organization", async () => {
3639
await expect(
3740
createWatchlistEntryHandler({
38-
ctx: { user: { ...mockUser, organizationId: undefined, profile: null } },
41+
ctx: { user: { ...(mockUser as any), organizationId: undefined, profile: null } },
3942
input: {
4043
type: WatchlistType.EMAIL,
4144
value: "spam@example.com",
@@ -54,7 +57,7 @@ describe("createWatchlistEntryHandler", () => {
5457

5558
await expect(
5659
createWatchlistEntryHandler({
57-
ctx: { user: mockUser },
60+
ctx: { user: mockUser as any },
5861
input: {
5962
type: WatchlistType.EMAIL,
6063
value: "spam@example.com",
@@ -73,7 +76,7 @@ describe("createWatchlistEntryHandler", () => {
7376
mockWatchlistRepo.createEntry.mockResolvedValue({ id: "watchlist-1" });
7477

7578
await createWatchlistEntryHandler({
76-
ctx: { user: mockUser },
79+
ctx: { user: mockUser as any },
7780
input: {
7881
type: WatchlistType.EMAIL,
7982
value: "test@example.com",
@@ -97,7 +100,7 @@ describe("createWatchlistEntryHandler", () => {
97100
it("should throw BAD_REQUEST for invalid email format", async () => {
98101
await expect(
99102
createWatchlistEntryHandler({
100-
ctx: { user: mockUser },
103+
ctx: { user: mockUser as any },
101104
input: {
102105
type: WatchlistType.EMAIL,
103106
value: "invalid-email",
@@ -114,7 +117,7 @@ describe("createWatchlistEntryHandler", () => {
114117
it("should throw BAD_REQUEST for email missing @ symbol", async () => {
115118
await expect(
116119
createWatchlistEntryHandler({
117-
ctx: { user: mockUser },
120+
ctx: { user: mockUser as any },
118121
input: {
119122
type: WatchlistType.EMAIL,
120123
value: "notanemail.com",
@@ -126,43 +129,43 @@ describe("createWatchlistEntryHandler", () => {
126129
});
127130
});
128131

129-
it("should throw BAD_REQUEST for invalid domain format without @", async () => {
130-
await expect(
131-
createWatchlistEntryHandler({
132-
ctx: { user: mockUser },
133-
input: {
134-
type: WatchlistType.DOMAIN,
135-
value: "example.com",
136-
},
137-
})
138-
).rejects.toMatchObject({
139-
code: "BAD_REQUEST",
140-
message: "Invalid domain format. Domain must start with @ (e.g., @example.com)",
132+
it("should accept valid domain format without @", async () => {
133+
mockWatchlistRepo.createEntry.mockResolvedValue({ id: "watchlist-1" });
134+
135+
const result = await createWatchlistEntryHandler({
136+
ctx: { user: mockUser as any },
137+
input: {
138+
type: WatchlistType.DOMAIN,
139+
value: "example.com",
140+
},
141141
});
142142

143-
expect(mockWatchlistRepo.createEntry).not.toHaveBeenCalled();
143+
expect(result.success).toBe(true);
144+
expect(mockWatchlistRepo.createEntry).toHaveBeenCalled();
144145
});
145146

146147
it("should throw BAD_REQUEST for domain with invalid format", async () => {
147148
await expect(
148149
createWatchlistEntryHandler({
149-
ctx: { user: mockUser },
150+
ctx: { user: mockUser as any },
150151
input: {
151152
type: WatchlistType.DOMAIN,
152-
value: "not-a-domain",
153+
value: "invalid..domain",
153154
},
154155
})
155-
).rejects.toMatchObject({
156-
code: "BAD_REQUEST",
157-
message: "Invalid domain format. Domain must start with @ (e.g., @example.com)",
158-
});
156+
).rejects.toStrictEqual(
157+
new TRPCError({
158+
code: "BAD_REQUEST",
159+
message: "Invalid domain format (e.g., example.com)",
160+
})
161+
);
159162
});
160163

161164
it("should accept valid email format", async () => {
162165
mockWatchlistRepo.createEntry.mockResolvedValue({ id: "watchlist-1" });
163166

164167
const result = await createWatchlistEntryHandler({
165-
ctx: { user: mockUser },
168+
ctx: { user: mockUser as any },
166169
input: {
167170
type: WatchlistType.EMAIL,
168171
value: "valid@example.com",
@@ -173,19 +176,23 @@ describe("createWatchlistEntryHandler", () => {
173176
expect(mockWatchlistRepo.createEntry).toHaveBeenCalled();
174177
});
175178

176-
it("should accept valid domain format with @", async () => {
179+
it("should accept valid domain format and normalize it", async () => {
177180
mockWatchlistRepo.createEntry.mockResolvedValue({ id: "watchlist-1" });
178181

179182
const result = await createWatchlistEntryHandler({
180-
ctx: { user: mockUser },
183+
ctx: { user: mockUser as any },
181184
input: {
182185
type: WatchlistType.DOMAIN,
183-
value: "@example.com",
186+
value: "example.com",
184187
},
185188
});
186189

187190
expect(result.success).toBe(true);
188-
expect(mockWatchlistRepo.createEntry).toHaveBeenCalled();
191+
expect(mockWatchlistRepo.createEntry).toHaveBeenCalledWith(
192+
expect.objectContaining({
193+
value: "example.com",
194+
})
195+
);
189196
});
190197
});
191198

@@ -205,7 +212,7 @@ describe("createWatchlistEntryHandler", () => {
205212
mockWatchlistRepo.createEntry.mockResolvedValue(mockEntry);
206213

207214
const result = await createWatchlistEntryHandler({
208-
ctx: { user: mockUser },
215+
ctx: { user: mockUser as any },
209216
input: {
210217
type: WatchlistType.EMAIL,
211218
value: "spam@example.com",
@@ -224,29 +231,29 @@ describe("createWatchlistEntryHandler", () => {
224231
});
225232
});
226233

227-
it("should create watchlist entry for DOMAIN type", async () => {
234+
it("should create watchlist entry for DOMAIN type and normalize it", async () => {
228235
const mockEntry = {
229236
id: "watchlist-2",
230237
type: WatchlistType.DOMAIN,
231-
value: "@spammer.com",
238+
value: "spammer.com",
232239
organizationId: 100,
233240
action: WatchlistAction.BLOCK,
234241
};
235242
mockWatchlistRepo.createEntry.mockResolvedValue(mockEntry);
236243

237244
const result = await createWatchlistEntryHandler({
238-
ctx: { user: mockUser },
245+
ctx: { user: mockUser as any },
239246
input: {
240247
type: WatchlistType.DOMAIN,
241-
value: "@spammer.com",
248+
value: "spammer.com",
242249
},
243250
});
244251

245252
expect(result.success).toBe(true);
246253
expect(result.entry).toEqual(mockEntry);
247254
expect(mockWatchlistRepo.createEntry).toHaveBeenCalledWith({
248255
type: WatchlistType.DOMAIN,
249-
value: "@spammer.com",
256+
value: "spammer.com",
250257
organizationId: 100,
251258
action: WatchlistAction.BLOCK,
252259
description: undefined,
@@ -258,7 +265,7 @@ describe("createWatchlistEntryHandler", () => {
258265
mockWatchlistRepo.createEntry.mockResolvedValue({ id: "watchlist-4" });
259266

260267
await createWatchlistEntryHandler({
261-
ctx: { user: mockUser },
268+
ctx: { user: mockUser as any },
262269
input: {
263270
type: WatchlistType.EMAIL,
264271
value: "SPAM@EXAMPLE.COM",
@@ -279,7 +286,7 @@ describe("createWatchlistEntryHandler", () => {
279286
mockWatchlistRepo.createEntry.mockResolvedValue({ id: "watchlist-5" });
280287

281288
await createWatchlistEntryHandler({
282-
ctx: { user: mockUser },
289+
ctx: { user: mockUser as any },
283290
input: {
284291
type: WatchlistType.EMAIL,
285292
value: "test@example.com",

packages/trpc/server/routers/viewer/organizations/createWatchlistEntry.handler.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { PermissionCheckService } from "@calcom/features/pbac/services/permission-check.service";
2-
import { domainWithAtRegex, emailRegex } from "@calcom/lib/emailSchema";
2+
import { domainRegex, emailRegex } from "@calcom/lib/emailSchema";
33
import { WatchlistRepository } from "@calcom/lib/server/repository/watchlist.repository";
44
import { prisma } from "@calcom/prisma";
55
import { MembershipRole, WatchlistAction } from "@calcom/prisma/enums";
@@ -51,10 +51,10 @@ export const createWatchlistEntryHandler = async ({ ctx, input }: CreateWatchlis
5151
});
5252
}
5353

54-
if (input.type === "DOMAIN" && !domainWithAtRegex.test(input.value)) {
54+
if (input.type === "DOMAIN" && !domainRegex.test(input.value)) {
5555
throw new TRPCError({
5656
code: "BAD_REQUEST",
57-
message: "Invalid domain format. Domain must start with @ (e.g., @example.com)",
57+
message: "Invalid domain format (e.g., example.com)",
5858
});
5959
}
6060

0 commit comments

Comments
 (0)