chore: Subdomain blacklist when creating org (#9728)
Co-authored-by: Bailey Pumfleet <bailey@pumfleet.co.uk> Co-authored-by: Omar López <zomars@me.com>
This commit is contained in:
co-authored by
Bailey Pumfleet
Omar López
parent
d8017e4d2d
commit
b799fdcb7e
+6
-2
@@ -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
|
||||
NEW_BOOKER_ENABLED_FOR_NON_EMBED=0
|
||||
|
||||
@@ -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),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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[];
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user