From b799fdcb7e63fa047287a169cc7a15ae35b0618c Mon Sep 17 00:00:00 2001 From: Leo Giovanetti Date: Tue, 27 Jun 2023 17:32:19 -0300 Subject: [PATCH] chore: Subdomain blacklist when creating org (#9728) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bailey Pumfleet Co-authored-by: Omar López --- .env.example | 8 +++-- .../ee/organizations/lib/orgDomains.ts | 18 ++---------- packages/features/test/orgDomains.test.ts | 29 ++++++++++++------- packages/lib/constants.ts | 4 +++ .../viewer/organizations/create.handler.ts | 5 ++-- turbo.json | 2 ++ 6 files changed, 37 insertions(+), 29 deletions(-) diff --git a/.env.example b/.env.example index 31b3c60756..b82c907d70 100644 --- a/.env.example +++ b/.env.example @@ -59,6 +59,11 @@ SAML_CLIENT_SECRET_VERIFIER= # PGSSLMODE='no-verify' PGSSLMODE= +# Define which hostnames are expected for the app to work on +ALLOWED_HOSTNAMES='"cal.com","cal.dev","cal-staging.com","cal.community","cal.local:3000","localhost:3000"' +# Reserved orgs subdomains for our own usage +RESERVED_SUBDOMAINS='"app","auth","docs","design","console","go","status","api","saml","www","matrix","developer","cal","my","team","support","security","blog","learn","admin"' + # - NEXTAUTH # @see: https://github.com/calendso/calendso/issues/263 # @see: https://next-auth.js.org/configuration/options#nextauth_url @@ -172,7 +177,6 @@ CLOSECOM_API_KEY= # Sendgrid internal sync service SENDGRID_SYNC_API_KEY= - # Change your Brand NEXT_PUBLIC_APP_NAME="Cal.com" NEXT_PUBLIC_SUPPORT_MAIL_ADDRESS="help@cal.com" @@ -210,4 +214,4 @@ AUTH_BEARER_TOKEN_VERCEL= NEW_BOOKER_ENABLED_FOR_EMBED=0 #Enables New booker for All but Embed requests -NEW_BOOKER_ENABLED_FOR_NON_EMBED=0 \ No newline at end of file +NEW_BOOKER_ENABLED_FOR_NON_EMBED=0 diff --git a/packages/features/ee/organizations/lib/orgDomains.ts b/packages/features/ee/organizations/lib/orgDomains.ts index 489b08d41e..141719b2cd 100644 --- a/packages/features/ee/organizations/lib/orgDomains.ts +++ b/packages/features/ee/organizations/lib/orgDomains.ts @@ -1,15 +1,4 @@ -import { WEBAPP_URL } from "@calcom/lib/constants"; - -// Define which hostnames are expected for the app -export const appHostnames = [ - "cal.com", - "cal.dev", - "cal-staging.com", - "cal.community", - "cal.local:3000", - // ⬇️ Prevents 404 error for normal localhost development, makes it backwards compatible - "localhost:3000", -]; +import { ALLOWED_HOSTNAMES, RESERVED_SUBDOMAINS, WEBAPP_URL } from "@calcom/lib/constants"; /** * return the org slug @@ -17,7 +6,7 @@ export const appHostnames = [ */ export function getOrgDomain(hostname: string) { // Find which hostname is being currently used - const currentHostname = appHostnames.find((ahn) => { + const currentHostname = ALLOWED_HOSTNAMES.find((ahn) => { const url = new URL(WEBAPP_URL); const testHostname = `${url.hostname}${url.port ? `:${url.port}` : ""}`; return testHostname.endsWith(`.${ahn}`); @@ -34,8 +23,7 @@ export function orgDomainConfig(hostname: string) { const currentOrgDomain = getOrgDomain(hostname); return { currentOrgDomain, - isValidOrgDomain: - currentOrgDomain !== null && currentOrgDomain !== "app" && !appHostnames.includes(currentOrgDomain), + isValidOrgDomain: currentOrgDomain !== null && !RESERVED_SUBDOMAINS.includes(currentOrgDomain), }; } diff --git a/packages/features/test/orgDomains.test.ts b/packages/features/test/orgDomains.test.ts index 8104c2320a..5e459f42b6 100644 --- a/packages/features/test/orgDomains.test.ts +++ b/packages/features/test/orgDomains.test.ts @@ -1,47 +1,56 @@ import { describe, expect, it } from "vitest"; import { orgDomainConfig, getOrgDomain } from "@calcom/features/ee/organizations/lib/orgDomains"; - import * as constants from "@calcom/lib/constants"; +function setupEnvs({ WEBAPP_URL = "https://app.cal.com" } = {}) { + Object.defineProperty(constants, "WEBAPP_URL", { value: WEBAPP_URL }); + Object.defineProperty(constants, "ALLOWED_HOSTNAMES", { + value: ["cal.com", "cal.dev", "cal-staging.com", "cal.community", "cal.local:3000", "localhost:3000"], + }); + Object.defineProperty(constants, "RESERVED_SUBDOMAINS", { + value: [ "app", "auth", "docs", "design", "console", "go", "status", "api", "saml", "www", "matrix", "developer", "cal", "my", "team", "support", "security", "blog", "learn", "admin"], + }); +} + describe("Org Domains Utils", () => { describe("orgDomainConfig", () => { it("should return a valid org domain", () => { - Object.defineProperty(constants, 'WEBAPP_URL', {value:"https://app.cal.com"}); + setupEnvs(); expect(orgDomainConfig("acme.cal.com")).toEqual({ currentOrgDomain: "acme", - isValidOrgDomain: true + isValidOrgDomain: true, }); }); it("should return a non valid org domain", () => { - Object.defineProperty(constants, 'WEBAPP_URL', {value:"https://app.cal.com"}); + setupEnvs(); expect(orgDomainConfig("app.cal.com")).toEqual({ currentOrgDomain: "app", - isValidOrgDomain: false + isValidOrgDomain: false, }); }); }); describe("getOrgDomain", () => { it("should handle a prod web app url with a prod subdomain hostname", () => { - Object.defineProperty(constants, 'WEBAPP_URL', {value:"https://app.cal.com"}); + setupEnvs(); expect(getOrgDomain("acme.cal.com")).toEqual("acme"); }); it("should handle a prod web app url with a staging subdomain hostname", () => { - Object.defineProperty(constants, 'WEBAPP_URL', {value:"https://app.cal.com"}); + setupEnvs(); expect(getOrgDomain("acme.cal.dev")).toEqual(null); }); it("should handle a local web app with port url with a local subdomain hostname", () => { - Object.defineProperty(constants, 'WEBAPP_URL', {value:"http://app.cal.local:3000"}); + setupEnvs({ WEBAPP_URL: "http://app.cal.local:3000" }); expect(getOrgDomain("acme.cal.local:3000")).toEqual("acme"); }); it("should handle a local web app with port url with a non-local subdomain hostname", () => { - Object.defineProperty(constants, 'WEBAPP_URL', {value:"http://app.cal.local:3000"}); + setupEnvs({ WEBAPP_URL: "http://app.cal.local:3000" }); expect(getOrgDomain("acme.cal.com:3000")).toEqual(null); }); - }) + }); }); diff --git a/packages/lib/constants.ts b/packages/lib/constants.ts index 7b5997144f..d1e16f9804 100644 --- a/packages/lib/constants.ts +++ b/packages/lib/constants.ts @@ -70,3 +70,7 @@ export const IS_STRIPE_ENABLED = !!( export const IS_TEAM_BILLING_ENABLED = IS_STRIPE_ENABLED && (!IS_SELF_HOSTED || HOSTED_CAL_FEATURES); export const FULL_NAME_LENGTH_MAX_LIMIT = 50; export const MINUTES_TO_BOOK = process.env.NEXT_PUBLIC_MINUTES_TO_BOOK || "5"; + +// Needed for orgs +export const ALLOWED_HOSTNAMES = JSON.parse(`[${process.env.ALLOWED_HOSTNAMES || ""}]`) as string[]; +export const RESERVED_SUBDOMAINS = JSON.parse(`[${process.env.RESERVED_SUBDOMAINS || ""}]`) as string[]; diff --git a/packages/trpc/server/routers/viewer/organizations/create.handler.ts b/packages/trpc/server/routers/viewer/organizations/create.handler.ts index 0df4c3cb7d..e24e3dc9de 100644 --- a/packages/trpc/server/routers/viewer/organizations/create.handler.ts +++ b/packages/trpc/server/routers/viewer/organizations/create.handler.ts @@ -4,7 +4,7 @@ import { totp } from "otplib"; import { sendOrganizationEmailVerification } from "@calcom/emails"; import { hashPassword } from "@calcom/features/auth/lib/hashPassword"; import { subdomainSuffix } from "@calcom/features/ee/organizations/lib/orgDomains"; -import { IS_PRODUCTION, IS_TEAM_BILLING_ENABLED } from "@calcom/lib/constants"; +import { IS_PRODUCTION, IS_TEAM_BILLING_ENABLED, RESERVED_SUBDOMAINS } from "@calcom/lib/constants"; import { getTranslation } from "@calcom/lib/server/i18n"; import { prisma } from "@calcom/prisma"; import { MembershipRole } from "@calcom/prisma/enums"; @@ -66,7 +66,8 @@ export const createHandler = async ({ input }: CreateOptions) => { }, }); - if (slugCollisions) throw new TRPCError({ code: "BAD_REQUEST", message: "organization_url_taken" }); + if (slugCollisions || RESERVED_SUBDOMAINS.includes(slug)) + throw new TRPCError({ code: "BAD_REQUEST", message: "organization_url_taken" }); if (userCollisions) throw new TRPCError({ code: "BAD_REQUEST", message: "admin_email_taken" }); const password = createHash("md5") diff --git a/turbo.json b/turbo.json index 2ddfd9cc76..c2b5fb3608 100644 --- a/turbo.json +++ b/turbo.json @@ -174,6 +174,7 @@ }, "globalDependencies": ["yarn.lock"], "globalEnv": [ + "ALLOWED_HOSTNAMES", "ANALYZE", "API_KEY_PREFIX", "APP_USER_NAME", @@ -246,6 +247,7 @@ "QUICK", "RAILWAY_STATIC_URL", "RENDER_EXTERNAL_URL", + "RESERVED_SUBDOMAINS", "SALESFORCE_CONSUMER_KEY", "SALESFORCE_CONSUMER_SECRET", "SAML_ADMINS",