fix: Hotfix in change from @spammer.com to spammer.com (#24479)
* fix: Hotfix in change from @spammer.com to spammer.com * Test fixes + string * Removed redundant test, fixed error message * Comment fixup
This commit is contained in:
+3
-3
@@ -2,7 +2,7 @@
|
||||
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
|
||||
import { domainWithAtRegex, emailRegex } from "@calcom/lib/emailSchema";
|
||||
import { domainRegex, emailRegex } from "@calcom/lib/emailSchema";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { WatchlistType } from "@calcom/prisma/enums";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
@@ -71,7 +71,7 @@ export function CreateBlocklistEntryModal({ isOpen, onClose }: CreateBlocklistEn
|
||||
return t("invalid_email_address");
|
||||
}
|
||||
} else if (watchType === WatchlistType.DOMAIN) {
|
||||
if (!domainWithAtRegex.test(value)) {
|
||||
if (!domainRegex.test(value)) {
|
||||
return t("invalid_domain_format");
|
||||
}
|
||||
}
|
||||
@@ -130,7 +130,7 @@ export function CreateBlocklistEntryModal({ isOpen, onClose }: CreateBlocklistEn
|
||||
render={({ field }) => (
|
||||
<Input
|
||||
{...field}
|
||||
placeholder={watchType === WatchlistType.EMAIL ? "user@example.com" : "@spammer.com"}
|
||||
placeholder={watchType === WatchlistType.EMAIL ? "user@example.com" : "spammer.com"}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -3834,7 +3834,7 @@
|
||||
"no_description_provided": "No description provided",
|
||||
"organization_blocklist": "Organization Blocklist",
|
||||
"manage_blocked_emails_and_domains": "Manage blocked emails and domains for your organization",
|
||||
"invalid_domain_format": "Invalid domain format. Example: @example.com",
|
||||
"invalid_domain_format": "Invalid domain format. Example: example.com",
|
||||
"invalid_email_address": "Invalid email address. Example: user@example.com",
|
||||
"reason_for_adding_to_blocklist": "Reason for adding to blocklist",
|
||||
"what_would_you_like_to_block": "What would you like to block?",
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
import { z } from "zod";
|
||||
|
||||
/** @see https://github.com/colinhacks/zod/issues/3155#issuecomment-2060045794 */
|
||||
|
||||
export const emailRegex =
|
||||
/* eslint-disable-next-line no-useless-escape */
|
||||
/^(?!\.)(?!.*\.\.)([A-Z0-9_+-\.']*)[A-Z0-9_+'-]@([A-Z0-9][A-Z0-9\-]*\.)+[A-Z]{2,}$/i;
|
||||
|
||||
/**
|
||||
* Domain regex for watchlist entries - requires @ prefix
|
||||
* Domain regex for watchlist entries
|
||||
* Supports international domains with Unicode characters
|
||||
* Examples: @example.com, @münchen.de, @example.co.uk
|
||||
* Examples: example.com, münchen.de, example.co.uk
|
||||
*/
|
||||
export const domainWithAtRegex =
|
||||
/^@[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])?)*$/;
|
||||
export const domainRegex =
|
||||
/^[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])?)*$/;
|
||||
|
||||
/**
|
||||
* RFC 5321 Section 4.5.3.1.3 specifies:
|
||||
|
||||
+44
-37
@@ -1,9 +1,12 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
|
||||
import { PermissionCheckService } from "@calcom/features/pbac/services/permission-check.service";
|
||||
import { WatchlistRepository } from "@calcom/lib/server/repository/watchlist.repository";
|
||||
import { WatchlistType, WatchlistAction, MembershipRole } from "@calcom/prisma/enums";
|
||||
|
||||
import { TRPCError } from "@trpc/server";
|
||||
|
||||
import { createWatchlistEntryHandler } from "./createWatchlistEntry.handler";
|
||||
|
||||
vi.mock("@calcom/features/pbac/services/permission-check.service");
|
||||
@@ -35,7 +38,7 @@ describe("createWatchlistEntryHandler", () => {
|
||||
it("should throw FORBIDDEN when user is not part of an organization", async () => {
|
||||
await expect(
|
||||
createWatchlistEntryHandler({
|
||||
ctx: { user: { ...mockUser, organizationId: undefined, profile: null } },
|
||||
ctx: { user: { ...(mockUser as any), organizationId: undefined, profile: null } },
|
||||
input: {
|
||||
type: WatchlistType.EMAIL,
|
||||
value: "spam@example.com",
|
||||
@@ -54,7 +57,7 @@ describe("createWatchlistEntryHandler", () => {
|
||||
|
||||
await expect(
|
||||
createWatchlistEntryHandler({
|
||||
ctx: { user: mockUser },
|
||||
ctx: { user: mockUser as any },
|
||||
input: {
|
||||
type: WatchlistType.EMAIL,
|
||||
value: "spam@example.com",
|
||||
@@ -73,7 +76,7 @@ describe("createWatchlistEntryHandler", () => {
|
||||
mockWatchlistRepo.createEntry.mockResolvedValue({ id: "watchlist-1" });
|
||||
|
||||
await createWatchlistEntryHandler({
|
||||
ctx: { user: mockUser },
|
||||
ctx: { user: mockUser as any },
|
||||
input: {
|
||||
type: WatchlistType.EMAIL,
|
||||
value: "test@example.com",
|
||||
@@ -97,7 +100,7 @@ describe("createWatchlistEntryHandler", () => {
|
||||
it("should throw BAD_REQUEST for invalid email format", async () => {
|
||||
await expect(
|
||||
createWatchlistEntryHandler({
|
||||
ctx: { user: mockUser },
|
||||
ctx: { user: mockUser as any },
|
||||
input: {
|
||||
type: WatchlistType.EMAIL,
|
||||
value: "invalid-email",
|
||||
@@ -114,7 +117,7 @@ describe("createWatchlistEntryHandler", () => {
|
||||
it("should throw BAD_REQUEST for email missing @ symbol", async () => {
|
||||
await expect(
|
||||
createWatchlistEntryHandler({
|
||||
ctx: { user: mockUser },
|
||||
ctx: { user: mockUser as any },
|
||||
input: {
|
||||
type: WatchlistType.EMAIL,
|
||||
value: "notanemail.com",
|
||||
@@ -126,43 +129,43 @@ describe("createWatchlistEntryHandler", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should throw BAD_REQUEST for invalid domain format without @", async () => {
|
||||
await expect(
|
||||
createWatchlistEntryHandler({
|
||||
ctx: { user: mockUser },
|
||||
input: {
|
||||
type: WatchlistType.DOMAIN,
|
||||
value: "example.com",
|
||||
},
|
||||
})
|
||||
).rejects.toMatchObject({
|
||||
code: "BAD_REQUEST",
|
||||
message: "Invalid domain format. Domain must start with @ (e.g., @example.com)",
|
||||
it("should accept valid domain format without @", async () => {
|
||||
mockWatchlistRepo.createEntry.mockResolvedValue({ id: "watchlist-1" });
|
||||
|
||||
const result = await createWatchlistEntryHandler({
|
||||
ctx: { user: mockUser as any },
|
||||
input: {
|
||||
type: WatchlistType.DOMAIN,
|
||||
value: "example.com",
|
||||
},
|
||||
});
|
||||
|
||||
expect(mockWatchlistRepo.createEntry).not.toHaveBeenCalled();
|
||||
expect(result.success).toBe(true);
|
||||
expect(mockWatchlistRepo.createEntry).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should throw BAD_REQUEST for domain with invalid format", async () => {
|
||||
await expect(
|
||||
createWatchlistEntryHandler({
|
||||
ctx: { user: mockUser },
|
||||
ctx: { user: mockUser as any },
|
||||
input: {
|
||||
type: WatchlistType.DOMAIN,
|
||||
value: "not-a-domain",
|
||||
value: "invalid..domain",
|
||||
},
|
||||
})
|
||||
).rejects.toMatchObject({
|
||||
code: "BAD_REQUEST",
|
||||
message: "Invalid domain format. Domain must start with @ (e.g., @example.com)",
|
||||
});
|
||||
).rejects.toStrictEqual(
|
||||
new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: "Invalid domain format (e.g., example.com)",
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("should accept valid email format", async () => {
|
||||
mockWatchlistRepo.createEntry.mockResolvedValue({ id: "watchlist-1" });
|
||||
|
||||
const result = await createWatchlistEntryHandler({
|
||||
ctx: { user: mockUser },
|
||||
ctx: { user: mockUser as any },
|
||||
input: {
|
||||
type: WatchlistType.EMAIL,
|
||||
value: "valid@example.com",
|
||||
@@ -173,19 +176,23 @@ describe("createWatchlistEntryHandler", () => {
|
||||
expect(mockWatchlistRepo.createEntry).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should accept valid domain format with @", async () => {
|
||||
it("should accept valid domain format and normalize it", async () => {
|
||||
mockWatchlistRepo.createEntry.mockResolvedValue({ id: "watchlist-1" });
|
||||
|
||||
const result = await createWatchlistEntryHandler({
|
||||
ctx: { user: mockUser },
|
||||
ctx: { user: mockUser as any },
|
||||
input: {
|
||||
type: WatchlistType.DOMAIN,
|
||||
value: "@example.com",
|
||||
value: "example.com",
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(mockWatchlistRepo.createEntry).toHaveBeenCalled();
|
||||
expect(mockWatchlistRepo.createEntry).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
value: "example.com",
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -205,7 +212,7 @@ describe("createWatchlistEntryHandler", () => {
|
||||
mockWatchlistRepo.createEntry.mockResolvedValue(mockEntry);
|
||||
|
||||
const result = await createWatchlistEntryHandler({
|
||||
ctx: { user: mockUser },
|
||||
ctx: { user: mockUser as any },
|
||||
input: {
|
||||
type: WatchlistType.EMAIL,
|
||||
value: "spam@example.com",
|
||||
@@ -224,21 +231,21 @@ describe("createWatchlistEntryHandler", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should create watchlist entry for DOMAIN type", async () => {
|
||||
it("should create watchlist entry for DOMAIN type and normalize it", async () => {
|
||||
const mockEntry = {
|
||||
id: "watchlist-2",
|
||||
type: WatchlistType.DOMAIN,
|
||||
value: "@spammer.com",
|
||||
value: "spammer.com",
|
||||
organizationId: 100,
|
||||
action: WatchlistAction.BLOCK,
|
||||
};
|
||||
mockWatchlistRepo.createEntry.mockResolvedValue(mockEntry);
|
||||
|
||||
const result = await createWatchlistEntryHandler({
|
||||
ctx: { user: mockUser },
|
||||
ctx: { user: mockUser as any },
|
||||
input: {
|
||||
type: WatchlistType.DOMAIN,
|
||||
value: "@spammer.com",
|
||||
value: "spammer.com",
|
||||
},
|
||||
});
|
||||
|
||||
@@ -246,7 +253,7 @@ describe("createWatchlistEntryHandler", () => {
|
||||
expect(result.entry).toEqual(mockEntry);
|
||||
expect(mockWatchlistRepo.createEntry).toHaveBeenCalledWith({
|
||||
type: WatchlistType.DOMAIN,
|
||||
value: "@spammer.com",
|
||||
value: "spammer.com",
|
||||
organizationId: 100,
|
||||
action: WatchlistAction.BLOCK,
|
||||
description: undefined,
|
||||
@@ -258,7 +265,7 @@ describe("createWatchlistEntryHandler", () => {
|
||||
mockWatchlistRepo.createEntry.mockResolvedValue({ id: "watchlist-4" });
|
||||
|
||||
await createWatchlistEntryHandler({
|
||||
ctx: { user: mockUser },
|
||||
ctx: { user: mockUser as any },
|
||||
input: {
|
||||
type: WatchlistType.EMAIL,
|
||||
value: "SPAM@EXAMPLE.COM",
|
||||
@@ -279,7 +286,7 @@ describe("createWatchlistEntryHandler", () => {
|
||||
mockWatchlistRepo.createEntry.mockResolvedValue({ id: "watchlist-5" });
|
||||
|
||||
await createWatchlistEntryHandler({
|
||||
ctx: { user: mockUser },
|
||||
ctx: { user: mockUser as any },
|
||||
input: {
|
||||
type: WatchlistType.EMAIL,
|
||||
value: "test@example.com",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { PermissionCheckService } from "@calcom/features/pbac/services/permission-check.service";
|
||||
import { domainWithAtRegex, emailRegex } from "@calcom/lib/emailSchema";
|
||||
import { domainRegex, emailRegex } from "@calcom/lib/emailSchema";
|
||||
import { WatchlistRepository } from "@calcom/lib/server/repository/watchlist.repository";
|
||||
import { prisma } from "@calcom/prisma";
|
||||
import { MembershipRole, WatchlistAction } from "@calcom/prisma/enums";
|
||||
@@ -51,10 +51,10 @@ export const createWatchlistEntryHandler = async ({ ctx, input }: CreateWatchlis
|
||||
});
|
||||
}
|
||||
|
||||
if (input.type === "DOMAIN" && !domainWithAtRegex.test(input.value)) {
|
||||
if (input.type === "DOMAIN" && !domainRegex.test(input.value)) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: "Invalid domain format. Domain must start with @ (e.g., @example.com)",
|
||||
message: "Invalid domain format (e.g., example.com)",
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user