diff --git a/apps/web/app/(use-page-wrapper)/settings/organizations/new/page.tsx b/apps/web/app/(use-page-wrapper)/settings/organizations/new/page.tsx index d913f69a7b..b72fed1e07 100644 --- a/apps/web/app/(use-page-wrapper)/settings/organizations/new/page.tsx +++ b/apps/web/app/(use-page-wrapper)/settings/organizations/new/page.tsx @@ -1,7 +1,11 @@ +import { getServerSession } from "@calcom/features/auth/lib/getServerSession"; +import { isCompanyEmail } from "@calcom/features/ee/organizations/lib/utils"; +import { UserPermissionRole } from "@calcom/prisma/enums"; +import { buildLegacyRequest } from "@lib/buildLegacyCtx"; import { _generateMetadata } from "app/_utils"; - +import { cookies, headers } from "next/headers"; +import { redirect } from "next/navigation"; import LicenseRequired from "~/ee/common/components/LicenseRequired"; - import LegacyPage, { LayoutWrapper } from "~/ee/organizations/new/create-new-view"; export const generateMetadata = async () => @@ -14,6 +18,19 @@ export const generateMetadata = async () => ); const ServerPage = async () => { + const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) }); + + if (!session?.user?.id) { + return redirect("/auth/login"); + } + + const isAdmin = session.user.role === UserPermissionRole.ADMIN; + const userEmail = session.user.email || ""; + + if (!isAdmin && !isCompanyEmail(userEmail)) { + return redirect("/settings/my-account/profile"); + } + return ( diff --git a/apps/web/modules/ee/organizations/components/CreateANewOrganizationForm.tsx b/apps/web/modules/ee/organizations/components/CreateANewOrganizationForm.tsx index 2cc4aa756d..64db8628a9 100644 --- a/apps/web/modules/ee/organizations/components/CreateANewOrganizationForm.tsx +++ b/apps/web/modules/ee/organizations/components/CreateANewOrganizationForm.tsx @@ -1,12 +1,7 @@ "use client"; -import type { SessionContextValue } from "next-auth/react"; -import { useSession } from "next-auth/react"; -import { useRouter } from "next/navigation"; -import { useState } from "react"; -import { Controller, useForm } from "react-hook-form"; - import { subdomainSuffix } from "@calcom/features/ee/organizations/lib/orgDomains"; +import { isCompanyEmail } from "@calcom/features/ee/organizations/lib/utils"; import { IS_SELF_HOSTED } from "@calcom/lib/constants"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import slugify from "@calcom/lib/slugify"; @@ -16,13 +11,14 @@ import type { Ensure } from "@calcom/types/utils"; import classNames from "@calcom/ui/classNames"; import { Alert } from "@calcom/ui/components/alert"; import { Button } from "@calcom/ui/components/button"; -import { ToggleGroup } from "@calcom/ui/components/form"; -import { Form } from "@calcom/ui/components/form"; -import { Label } from "@calcom/ui/components/form"; -import { TextField } from "@calcom/ui/components/form"; +import { Form, Label, TextField, ToggleGroup } from "@calcom/ui/components/form"; import { RadioAreaGroup as RadioArea } from "@calcom/ui/components/radio"; - import { useOnboarding } from "@calcom/web/modules/ee/organizations/lib/onboardingStore"; +import { useRouter } from "next/navigation"; +import type { SessionContextValue } from "next-auth/react"; +import { useSession } from "next-auth/react"; +import { useState } from "react"; +import { Controller, useForm } from "react-hook-form"; function extractDomainFromEmail(email: string) { const match = email.match(/^(?:.*?:\/\/)?.*?([\w-]*(?:\.\w{2,}|\.\w{2,}\.\w{2}))(?:[/?#:]|$)/); @@ -52,6 +48,11 @@ const CreateANewOrganizationFormChild = ({ session }: { session: Ensure({ defaultValues: { billingPeriod: billingPeriod ?? BillingPeriod.MONTHLY, - slug: slug ?? (!isAdmin ? deriveSlugFromEmail(defaultOrgOwnerEmail) : undefined), - orgOwnerEmail: orgOwnerEmail || defaultOrgOwnerEmail, - name: name ?? (!isAdmin ? deriveOrgNameFromEmail(defaultOrgOwnerEmail) : undefined), + slug: slug ?? (!isAdmin ? deriveSlugFromEmail(effectiveOrgOwnerEmail) : undefined), + orgOwnerEmail: effectiveOrgOwnerEmail, + name: name ?? (!isAdmin ? deriveOrgNameFromEmail(effectiveOrgOwnerEmail) : undefined), seats: seats ?? null, pricePerSeat: pricePerSeat ?? null, }, @@ -111,6 +112,24 @@ const CreateANewOrganizationFormChild = ({ session }: { session: Ensure + + {t("update_email_address")} + + } + /> + + ); + } + return ( <>
{ + describe("isCompanyEmail", () => { + it("should return false for gmail.com email", () => { + expect(isCompanyEmail("user@gmail.com")).toBe(false); + }); + + it("should return false for yahoo.com email", () => { + expect(isCompanyEmail("user@yahoo.com")).toBe(false); + }); + + it("should return false for outlook.com email", () => { + expect(isCompanyEmail("user@outlook.com")).toBe(false); + }); + + it("should return false for hotmail.com email", () => { + expect(isCompanyEmail("user@hotmail.com")).toBe(false); + }); + + it("should return false for protonmail.com email", () => { + expect(isCompanyEmail("user@protonmail.com")).toBe(false); + }); + + it("should return false for icloud.com email", () => { + expect(isCompanyEmail("user@icloud.com")).toBe(false); + }); + + it("should return true for company email", () => { + expect(isCompanyEmail("user@acme.com")).toBe(true); + }); + + it("should return true for cal.com email", () => { + expect(isCompanyEmail("user@cal.com")).toBe(true); + }); + + it("should return false for email without @", () => { + expect(isCompanyEmail("invalidemail")).toBe(false); + }); + + it("should return false for empty email", () => { + expect(isCompanyEmail("")).toBe(false); + }); + + it("should return false for all popular personal email providers", () => { + const personalProviders = [ + "gmail.com", + "googlemail.com", + "yahoo.com", + "ymail.com", + "rocketmail.com", + "sbcglobal.net", + "att.net", + "outlook.com", + "hotmail.com", + "live.com", + "msn.com", + "outlook.co", + "hotmail.co.uk", + "aol.com", + "icloud.com", + "me.com", + "mac.com", + "mail.com", + "email.com", + "protonmail.com", + "proton.me", + "zoho.com", + "yandex.com", + "gmx.com", + "fastmail.com", + "tutanota.com", + "mail.ru", + "qq.com", + ]; + + for (const provider of personalProviders) { + expect(isCompanyEmail(`user@${provider}`)).toBe(false); + } + }); + + it("should return true for various company domains", () => { + const companyDomains = [ + "acme.com", + "techcorp.io", + "business.co", + "startup.ai", + "enterprise.net", + "cal.com", + ]; + + for (const domain of companyDomains) { + expect(isCompanyEmail(`user@${domain}`)).toBe(true); + } + }); + + it("should be case-insensitive for domain matching", () => { + expect(isCompanyEmail("user@GMAIL.COM")).toBe(false); + expect(isCompanyEmail("user@Gmail.Com")).toBe(false); + }); + }); + + describe("extractDomainFromEmail", () => { + it("should extract domain from a standard email", () => { + expect(extractDomainFromEmail("user@acme.com")).toBe("acme"); + }); + + it("should return empty string for invalid input", () => { + expect(extractDomainFromEmail("invalid")).toBe(""); + }); + + it("should return empty string for empty input", () => { + expect(extractDomainFromEmail("")).toBe(""); + }); + }); +});