-
Notifications
You must be signed in to change notification settings - Fork 8.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Single domain setup with one org #18383
base: main
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
const isSingleOrgModeEnabled = !!process.env.NEXT_PUBLIC_SINGLE_ORG_SLUG; | ||
const orgSlugCaptureGroupName = "orgSlug"; | ||
/** | ||
* Returns the leftmost subdomain from a given URL. | ||
* It needs the URL domain to have atleast two dots. | ||
* app.cal.com -> app | ||
* app.company.cal.com -> app | ||
* app.company.com -> app | ||
*/ | ||
const getLeftMostSubdomain = (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; | ||
}; | ||
|
||
const getRegExpNotMatchingLeftMostSubdomain = (url) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Renamed |
||
const leftMostSubdomain = getLeftMostSubdomain(url); | ||
const subdomain = leftMostSubdomain ? `(?!${leftMostSubdomain})[^.]+` : "[^.]+"; | ||
return subdomain; | ||
}; | ||
|
||
// For app.cal.com, it will match all domains that are not starting with "app". Technically we would want to match domains like acme.cal.com, dunder.cal.com and not app.cal.com | ||
const getRegExpThatMatchesAllOrgDomains = (exports.getRegExpThatMatchesAllOrgDomains = ({ webAppUrl }) => { | ||
if (isSingleOrgModeEnabled) { | ||
console.log("Single-Org-Mode enabled - Consider all domains to be org domains"); | ||
// It works in combination with next.config.js where in this case we use orgSlug=NEXT_PUBLIC_SINGLE_ORG_SLUG | ||
return `.*`; | ||
} | ||
const subdomainRegExp = getRegExpNotMatchingLeftMostSubdomain(webAppUrl); | ||
return `^(?<${orgSlugCaptureGroupName}>${subdomainRegExp})\\.(?!vercel\.app).*`; | ||
}); | ||
|
||
const nextJsOrgRewriteConfig = { | ||
// :orgSlug is special value which would get matching group from the regex in orgHostPath | ||
orgSlug: process.env.NEXT_PUBLIC_SINGLE_ORG_SLUG || `:${orgSlugCaptureGroupName}`, | ||
orgHostPath: getRegExpThatMatchesAllOrgDomains({ | ||
webAppUrl: process.env.NEXT_PUBLIC_WEBAPP_URL || `https://${process.env.VERCEL_URL}`, | ||
}), | ||
}; | ||
|
||
exports.nextJsOrgRewriteConfig = nextJsOrgRewriteConfig; |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Renamed to getNextjsOrgRewriteConfig and made it more robust with better tests. |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,12 +7,11 @@ const { withSentryConfig } = require("@sentry/nextjs"); | |
const { version } = require("./package.json"); | ||
const { i18n } = require("./next-i18next.config"); | ||
const { | ||
orgHostPath, | ||
nextJsOrgRewriteConfig, | ||
orgUserRoutePath, | ||
orgUserTypeRoutePath, | ||
orgUserTypeEmbedRoutePath, | ||
} = require("./pagesAndRewritePaths"); | ||
|
||
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"); | ||
const isOrganizationsEnabled = | ||
|
@@ -124,7 +123,7 @@ const matcherConfigRootPath = { | |
has: [ | ||
{ | ||
type: "host", | ||
value: orgHostPath, | ||
value: nextJsOrgRewriteConfig.orgHostPath, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if Single org setup isn't enabled the regex here should remain same |
||
}, | ||
], | ||
source: "/", | ||
|
@@ -134,7 +133,7 @@ const matcherConfigRootPathEmbed = { | |
has: [ | ||
{ | ||
type: "host", | ||
value: orgHostPath, | ||
value: nextJsOrgRewriteConfig.orgHostPath, | ||
}, | ||
], | ||
source: "/embed", | ||
|
@@ -144,7 +143,7 @@ const matcherConfigUserRoute = { | |
has: [ | ||
{ | ||
type: "host", | ||
value: orgHostPath, | ||
value: nextJsOrgRewriteConfig.orgHostPath, | ||
}, | ||
], | ||
source: orgUserRoutePath, | ||
|
@@ -154,7 +153,7 @@ const matcherConfigUserTypeRoute = { | |
has: [ | ||
{ | ||
type: "host", | ||
value: orgHostPath, | ||
value: nextJsOrgRewriteConfig.orgHostPath, | ||
}, | ||
], | ||
source: orgUserTypeRoutePath, | ||
|
@@ -164,7 +163,7 @@ const matcherConfigUserTypeEmbedRoute = { | |
has: [ | ||
{ | ||
type: "host", | ||
value: orgHostPath, | ||
value: nextJsOrgRewriteConfig.orgHostPath, | ||
}, | ||
], | ||
source: orgUserTypeEmbedRoutePath, | ||
|
@@ -287,6 +286,7 @@ const nextConfig = { | |
return config; | ||
}, | ||
async rewrites() { | ||
const { orgSlug } = nextJsOrgRewriteConfig; | ||
const beforeFiles = [ | ||
{ | ||
source: "/forms/:formQuery*", | ||
|
@@ -328,23 +328,23 @@ const nextConfig = { | |
? [ | ||
{ | ||
...matcherConfigRootPath, | ||
destination: "/team/:orgSlug?isOrgProfile=1", | ||
destination: `/team/${orgSlug}?isOrgProfile=1`, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if single org setup isn't enabled, orgSlug = ":orgSlug" |
||
}, | ||
{ | ||
...matcherConfigRootPathEmbed, | ||
destination: "/team/:orgSlug/embed?isOrgProfile=1", | ||
destination: `/team/${orgSlug}/embed?isOrgProfile=1`, | ||
}, | ||
{ | ||
...matcherConfigUserRoute, | ||
destination: "/org/:orgSlug/:user", | ||
destination: `/org/${orgSlug}/:user`, | ||
}, | ||
{ | ||
...matcherConfigUserTypeRoute, | ||
destination: "/org/:orgSlug/:user/:type", | ||
destination: `/org/${orgSlug}/:user/:type`, | ||
}, | ||
{ | ||
...matcherConfigUserTypeEmbedRoute, | ||
destination: "/org/:orgSlug/:user/:type/embed", | ||
destination: `/org/${orgSlug}/:user/:type/embed`, | ||
}, | ||
] | ||
: []), | ||
|
@@ -392,6 +392,7 @@ const nextConfig = { | |
}; | ||
}, | ||
async headers() { | ||
const { orgSlug } = nextJsOrgRewriteConfig; | ||
// This header can be set safely as it ensures the browser will load the resources even when COEP is set. | ||
// But this header must be set only on those resources that are safe to be loaded in a cross-origin context e.g. all embeddable pages's resources | ||
const CORP_CROSS_ORIGIN_HEADER = { | ||
|
@@ -478,7 +479,7 @@ const nextConfig = { | |
headers: [ | ||
{ | ||
key: "X-Cal-Org-path", | ||
value: "/team/:orgSlug", | ||
value: `/team/${orgSlug}`, | ||
}, | ||
], | ||
}, | ||
|
@@ -487,7 +488,7 @@ const nextConfig = { | |
headers: [ | ||
{ | ||
key: "X-Cal-Org-path", | ||
value: "/org/:orgSlug/:user", | ||
value: `/org/${orgSlug}/:user`, | ||
}, | ||
], | ||
}, | ||
|
@@ -496,7 +497,7 @@ const nextConfig = { | |
headers: [ | ||
{ | ||
key: "X-Cal-Org-path", | ||
value: "/org/:orgSlug/:user/:type", | ||
value: `/org/${orgSlug}/:user/:type`, | ||
}, | ||
], | ||
}, | ||
|
@@ -505,7 +506,7 @@ const nextConfig = { | |
headers: [ | ||
{ | ||
key: "X-Cal-Org-path", | ||
value: "/org/:orgSlug/:user/:type/embed", | ||
value: `/org/${orgSlug}/:user/:type/embed`, | ||
}, | ||
], | ||
}, | ||
|
@@ -591,7 +592,7 @@ const nextConfig = { | |
{ | ||
type: "header", | ||
key: "host", | ||
value: orgHostPath, | ||
value: nextJsOrgRewriteConfig.orgHostPath, | ||
}, | ||
], | ||
destination: "/event-types?openPlain=true", | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Renamed
getDefaultSubdomain
->getLeftMostSubdomain