Skip to content

Commit

Permalink
test: Hotfix/Orgs rewrite causing 404 with 3 or more dots domains (ca…
Browse files Browse the repository at this point in the history
…lcom#9803)

* Fix RegExp

* Used ORGANIZATIONS_ENABLED to opt-out of orgs behaviour

* Add comments

* Add a new testcase

* Add response headers for easier debugging and Checkly tests in production
  • Loading branch information
hariombalhara authored Jun 27, 2023
1 parent 66d4797 commit 8384c3f
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 42 deletions.
15 changes: 15 additions & 0 deletions apps/web/getSubdomainRegExp.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
const getDefaultSubdomain = (url) => {
if (!url.startsWith("http:") && !url.startsWith("https:")) {
// Make it a valid URL. Mabe we can simply return null and opt-out from orgs support till the use a URL scheme.
url = `https://${url}`;
}
const _url = new URL(url);
const regex = new RegExp(/^([a-z]+\:\/{2})?((?<subdomain>[\w-.]+)\.[\w-]+\.\w+)$/);
//console.log(_url.hostname, _url.hostname.match(regex));
return _url.hostname.match(regex)?.groups?.subdomain || null;
};
exports.getSubdomainRegExp = (url) => {
const defaultSubdomain = getDefaultSubdomain(url);
const subdomain = defaultSubdomain ? `(?!${defaultSubdomain})[^.]+` : "[^.]+";
return subdomain;
};
120 changes: 79 additions & 41 deletions apps/web/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const englishTranslation = require("./public/static/locales/en/common.json");
const { withAxiom } = require("next-axiom");
const { i18n } = require("./next-i18next.config");
const { pages } = require("./pages");
const { getSubdomainRegExp } = require("./getSubdomainRegExp");

if (!process.env.NEXTAUTH_SECRET) throw new Error("Please set NEXTAUTH_SECRET");
if (!process.env.CALENDSO_ENCRYPTION_KEY) throw new Error("Please set CALENDSO_ENCRYPTION_KEY");
Expand Down Expand Up @@ -70,14 +71,6 @@ const informAboutDuplicateTranslations = () => {
};

informAboutDuplicateTranslations();

const getSubdomain = () => {
const _url = new URL(process.env.NEXT_PUBLIC_WEBAPP_URL);
const regex = new RegExp(/^([a-z]+\:\/{2})?((?<subdomain>[\w-]+)\.[\w-]+\.\w+)$/);
//console.log(_url.hostname, _url.hostname.match(regex));
return _url.hostname.match(regex)?.groups?.subdomain || null;
};

const plugins = [];
if (process.env.ANALYZE === "true") {
// only load dependency if env `ANALYZE` was set
Expand All @@ -102,6 +95,39 @@ const teamTypeRouteRegExp = "/team/:slug/:type((?!book$)[^/]+)";
const privateLinkRouteRegExp = "/d/:link/:slug((?!book$)[^/]+)";
const embedUserTypeRouteRegExp = `/:user((?!${pages.join("/|")})[^/]*)/:type/embed`;
const embedTeamTypeRouteRegExp = "/team/:slug/:type/embed";
const subdomainRegExp = getSubdomainRegExp(process.env.NEXT_PUBLIC_WEBAPP_URL);
// Important Note: Do update the RegExp in apps/web/test/lib/next-config.test.ts when changing it.
const orgHostRegExp = `^(?<orgSlug>${subdomainRegExp})\\..*`;

const matcherConfigRootPath = {
has: [
{
type: "host",
value: orgHostRegExp,
},
],
source: "/",
};

const matcherConfigOrgMemberPath = {
has: [
{
type: "host",
value: orgHostRegExp,
},
],
source: `/:user((?!${pages.join("|")}|_next|public)[a-zA-Z0-9\-_]+)`,
};

const matcherConfigUserPath = {
has: [
{
type: "host",
value: `^(?<orgSlug>${subdomainRegExp}[^.]+)\\..*`,
},
],
source: `/:user((?!${pages.join("|")}|_next|public))/:path*`,
};

/** @type {import("next").NextConfig} */
const nextConfig = {
Expand Down Expand Up @@ -193,40 +219,23 @@ const nextConfig = {
return config;
},
async rewrites() {
const defaultSubdomain = getSubdomain();
const subdomain = defaultSubdomain ? `(?!${defaultSubdomain})[^.]+` : "[^.]+";

const beforeFiles = [
{
has: [
{
type: "host",
value: `^(?<orgSlug>${subdomain})\\..*`,
},
],
source: "/",
destination: "/team/:orgSlug",
},
{
has: [
{
type: "host",
value: `^(?<orgSlug>${subdomain})\\..*`,
},
],
source: `/:user((?!${pages.join("|")}|_next|public)[a-zA-Z0-9\-_]+)`,
destination: "/org/:orgSlug/:user",
},
{
has: [
{
type: "host",
value: `^(?<orgSlug>${subdomain}[^.]+)\\..*`,
},
],
source: `/:user((?!${pages.join("|")}|_next|public))/:path*`,
destination: "/:user/:path*",
},
...(process.env.ORGANIZATIONS_ENABLED
? [
{
...matcherConfigRootPath,
destination: "/team/:orgSlug",
},
{
...matcherConfigOrgMemberPath,
destination: "/org/:orgSlug/:user",
},
{
...matcherConfigUserPath,
destination: "/:user/:path*",
},
]
: []),
];

let afterFiles = [
Expand Down Expand Up @@ -381,6 +390,35 @@ const nextConfig = {
},
],
},
...[
{
...matcherConfigRootPath,
headers: [
{
key: "X-Cal-Org-path",
value: "/team/:orgSlug",
},
],
},
{
...matcherConfigOrgMemberPath,
headers: [
{
key: "X-Cal-Org-path",
value: "/org/:orgSlug/:user",
},
],
},
{
...matcherConfigUserPath,
headers: [
{
key: "X-Cal-Org-path",
value: "/:user/:path",
},
],
},
],
];
},
async redirects() {
Expand Down
35 changes: 35 additions & 0 deletions apps/web/test/lib/next-config.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@

import { it, expect, describe, beforeAll, afterAll } from "vitest";
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { getSubdomainRegExp } = require("../../getSubdomainRegExp");
let userTypeRouteRegExp: RegExp;
let teamTypeRouteRegExp:RegExp;
let privateLinkRouteRegExp:RegExp
Expand Down Expand Up @@ -161,3 +163,36 @@ describe('next.config.js - RegExp', ()=>{
})
})


describe('next.config.js - Org Rewrite', ()=> {
// RegExp copied from next.config.js
const orgHostRegExp = (subdomainRegExp:string)=> new RegExp(`^(?<orgSlug>${subdomainRegExp})\\..*`)
describe('SubDomain Retrieval from NEXT_PUBLIC_WEBAPP_URL', ()=>{
it('https://app.cal.com', ()=>{
const subdomainRegExp = getSubdomainRegExp('https://app.cal.com');
expect(orgHostRegExp(subdomainRegExp).exec('app.cal.com')).toEqual(null)
expect(orgHostRegExp(subdomainRegExp).exec('company.app.cal.com')?.groups?.orgSlug).toEqual('company')
expect(orgHostRegExp(subdomainRegExp).exec('org.cal.com')?.groups?.orgSlug).toEqual('org')
})

it('app.cal.com', ()=>{
const subdomainRegExp = getSubdomainRegExp('app.cal.com');
expect(orgHostRegExp(subdomainRegExp).exec('app.cal.com')).toEqual(null)
expect(orgHostRegExp(subdomainRegExp).exec('company.app.cal.com')?.groups?.orgSlug).toEqual('company')
})

it('https://calcom.app.company.com', ()=>{
const subdomainRegExp = getSubdomainRegExp('https://calcom.app.company.com');
expect(orgHostRegExp(subdomainRegExp).exec('calcom.app.company.com')).toEqual(null)
expect(orgHostRegExp(subdomainRegExp).exec('acme.calcom.app.company.com')?.groups?.orgSlug).toEqual('acme')
})

it('https://calcom.example.com', ()=>{
const subdomainRegExp = getSubdomainRegExp('https://calcom.example.com');
expect(orgHostRegExp(subdomainRegExp).exec('calcom.example.com')).toEqual(null)
expect(orgHostRegExp(subdomainRegExp).exec('acme.calcom.example.com')?.groups?.orgSlug).toEqual('acme')
// The following also matches which causes anything other than the domain in NEXT_PUBLIC_WEBAPP_URL to give 404
expect(orgHostRegExp(subdomainRegExp).exec('some-other.company.com')?.groups?.orgSlug).toEqual('some-other')
})
})
})
3 changes: 2 additions & 1 deletion packages/lib/default-cookies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ export function defaultCookies(useSecureCookies: boolean): CookiesOptions {
const defaultOptions: CookieOption["options"] = {
domain: isENVDev
? process.env.ORGANIZATIONS_ENABLED
? ".cal.local"
? //FIXME: This is causing login to not work if someone uses anything other .cal.local for testing
".cal.local"
: undefined
: NEXTAUTH_COOKIE_DOMAIN,
// To enable cookies on widgets,
Expand Down

0 comments on commit 8384c3f

Please sign in to comment.