feat(): enable custom domain usage (#9911)
# Content - Introduce the `workspaceUrls` property. It contains two sub-properties: `customUrl, subdomainUrl`. These endpoints are used to access the workspace. Even if the `workspaceUrls` is invalid for multiple reasons, the `subdomainUrl` remains valid. - Introduce `ResolveField` workspaceEndpoints to avoid unnecessary URL computation on the frontend part. - Add a `forceSubdomainUrl` to avoid custom URL using a query parameter
This commit is contained in:
@ -9,6 +9,53 @@ import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DomainManagerService } from './domain-manager.service';
|
||||
|
||||
describe('DomainManagerService', () => {
|
||||
describe('getworkspaceUrls', () => {
|
||||
it('should return a URL containing the correct hostname if hostname is provided', () => {
|
||||
jest
|
||||
.spyOn(environmentService, 'get')
|
||||
.mockImplementation((key: string) => {
|
||||
const env = {
|
||||
FRONT_PROTOCOL: 'https',
|
||||
FRONT_DOMAIN: 'example.com',
|
||||
};
|
||||
|
||||
return env[key];
|
||||
});
|
||||
|
||||
const result = domainManagerService.getworkspaceUrls({
|
||||
subdomain: 'subdomain',
|
||||
hostname: 'custom-host.com',
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
customUrl: 'https://custom-host.com/',
|
||||
subdomainUrl: 'https://subdomain.example.com/',
|
||||
});
|
||||
});
|
||||
|
||||
it('should return a URL containing the correct subdomain if hostname is not provided but subdomain is', () => {
|
||||
jest
|
||||
.spyOn(environmentService, 'get')
|
||||
.mockImplementation((key: string) => {
|
||||
const env = {
|
||||
FRONT_PROTOCOL: 'https',
|
||||
FRONT_DOMAIN: 'example.com',
|
||||
};
|
||||
|
||||
return env[key];
|
||||
});
|
||||
|
||||
const result = domainManagerService.getworkspaceUrls({
|
||||
subdomain: 'subdomain',
|
||||
hostname: undefined,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
customUrl: undefined,
|
||||
subdomainUrl: 'https://subdomain.example.com/',
|
||||
});
|
||||
});
|
||||
});
|
||||
let domainManagerService: DomainManagerService;
|
||||
let environmentService: EnvironmentService;
|
||||
|
||||
@ -106,7 +153,10 @@ describe('DomainManagerService', () => {
|
||||
});
|
||||
|
||||
const result = domainManagerService.buildWorkspaceURL({
|
||||
subdomain: 'test',
|
||||
workspace: {
|
||||
subdomain: 'test',
|
||||
hostname: undefined,
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.toString()).toBe('https://test.example.com/');
|
||||
@ -125,7 +175,10 @@ describe('DomainManagerService', () => {
|
||||
});
|
||||
|
||||
const result = domainManagerService.buildWorkspaceURL({
|
||||
subdomain: 'subdomain',
|
||||
workspace: {
|
||||
subdomain: 'test',
|
||||
hostname: undefined,
|
||||
},
|
||||
pathname: '/path/to/resource',
|
||||
});
|
||||
|
||||
@ -145,8 +198,10 @@ describe('DomainManagerService', () => {
|
||||
});
|
||||
|
||||
const result = domainManagerService.buildWorkspaceURL({
|
||||
subdomain: 'subdomain',
|
||||
|
||||
workspace: {
|
||||
subdomain: 'test',
|
||||
hostname: undefined,
|
||||
},
|
||||
searchParams: {
|
||||
foo: 'bar',
|
||||
baz: 123,
|
||||
|
||||
@ -74,33 +74,31 @@ export class DomainManagerService {
|
||||
buildEmailVerificationURL({
|
||||
emailVerificationToken,
|
||||
email,
|
||||
subdomain,
|
||||
workspace,
|
||||
}: {
|
||||
emailVerificationToken: string;
|
||||
email: string;
|
||||
subdomain: string;
|
||||
workspace: Pick<Workspace, 'subdomain' | 'hostname'>;
|
||||
}) {
|
||||
return this.buildWorkspaceURL({
|
||||
subdomain,
|
||||
workspace,
|
||||
pathname: 'verify-email',
|
||||
searchParams: { emailVerificationToken, email },
|
||||
});
|
||||
}
|
||||
|
||||
buildWorkspaceURL({
|
||||
subdomain,
|
||||
workspace,
|
||||
pathname,
|
||||
searchParams,
|
||||
}: {
|
||||
subdomain: string;
|
||||
workspace: Pick<Workspace, 'subdomain' | 'hostname'>;
|
||||
pathname?: string;
|
||||
searchParams?: Record<string, string | number>;
|
||||
}) {
|
||||
const url = this.getFrontUrl();
|
||||
const workspaceUrls = this.getworkspaceUrls(workspace);
|
||||
|
||||
if (this.environmentService.get('IS_MULTIWORKSPACE_ENABLED')) {
|
||||
url.hostname = `${subdomain}.${url.hostname}`;
|
||||
}
|
||||
const url = new URL(workspaceUrls.customUrl ?? workspaceUrls.subdomainUrl);
|
||||
|
||||
if (pathname) {
|
||||
url.pathname = pathname;
|
||||
@ -117,21 +115,6 @@ export class DomainManagerService {
|
||||
return url;
|
||||
}
|
||||
|
||||
// @Deprecated
|
||||
getWorkspaceSubdomainFromUrl = (url: string) => {
|
||||
const { hostname: originHostname } = new URL(url);
|
||||
|
||||
if (!originHostname.endsWith(this.getFrontUrl().hostname)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const frontDomain = this.getFrontUrl().hostname;
|
||||
|
||||
const subdomain = originHostname.replace(`.${frontDomain}`, '');
|
||||
|
||||
return this.isDefaultSubdomain(subdomain) ? null : subdomain;
|
||||
};
|
||||
|
||||
getSubdomainAndHostnameFromUrl = (url: string) => {
|
||||
const { hostname: originHostname } = new URL(url);
|
||||
|
||||
@ -162,9 +145,12 @@ export class DomainManagerService {
|
||||
return subdomain === this.environmentService.get('DEFAULT_SUBDOMAIN');
|
||||
}
|
||||
|
||||
computeRedirectErrorUrl(errorMessage: string, subdomain: string) {
|
||||
computeRedirectErrorUrl(
|
||||
errorMessage: string,
|
||||
workspace: Pick<Workspace, 'subdomain' | 'hostname'>,
|
||||
) {
|
||||
const url = this.buildWorkspaceURL({
|
||||
subdomain: subdomain,
|
||||
workspace,
|
||||
pathname: '/verify',
|
||||
searchParams: { errorMessage },
|
||||
});
|
||||
@ -352,7 +338,7 @@ export class DomainManagerService {
|
||||
await this.deleteCustomHostname(fromCustomHostname.id);
|
||||
}
|
||||
|
||||
return await this.registerCustomHostname(toHostname);
|
||||
return this.registerCustomHostname(toHostname);
|
||||
}
|
||||
|
||||
async deleteCustomHostnameByHostnameSilently(hostname: string) {
|
||||
@ -378,4 +364,32 @@ export class DomainManagerService {
|
||||
zone_id: this.environmentService.get('CLOUDFLARE_ZONE_ID'),
|
||||
});
|
||||
}
|
||||
|
||||
private getCustomWorkspaceEndpoint(hostname: string) {
|
||||
const url = this.getFrontUrl();
|
||||
|
||||
url.hostname = hostname;
|
||||
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
private getTwentyWorkspaceEndpoint(subdomain: string) {
|
||||
const url = this.getFrontUrl();
|
||||
|
||||
url.hostname = `${subdomain}.${url.hostname}`;
|
||||
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
getworkspaceUrls({
|
||||
subdomain,
|
||||
hostname,
|
||||
}: Pick<Workspace, 'subdomain' | 'hostname'>) {
|
||||
return {
|
||||
customUrl: hostname
|
||||
? this.getCustomWorkspaceEndpoint(hostname)
|
||||
: undefined,
|
||||
subdomainUrl: this.getTwentyWorkspaceEndpoint(subdomain),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user