fix: Add reserved subdomains constant and update validation on generateSubdomain (#15217)

## Description

Fixes #15160 

- Moved the reserved subdomains to a separate shared constant file:
`packages/twenty-server/src/engine/core-modules/workspace/constants/reserved-subdomains.constant.ts`
- Updated the validation while generating subdomain to check if the
extracted subdomain (from email or display name) is reserved
- When a reserved subdomain is detected, the server will automatically
fall back to a random subdomain

---------

Co-authored-by: Naineel Soyantar <naineelsoyantar@gmail.com>
Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Naineel Soyantar
2025-10-21 20:23:48 +05:30
committed by GitHub
parent 139b6dd29b
commit 26e432d8e2
97 changed files with 1369 additions and 754 deletions
@@ -1,13 +1,13 @@
import { Module } from '@nestjs/common';
import { DomainManagerModule } from 'src/engine/core-modules/domain-manager/domain-manager.module';
import { DomainServerConfigModule } from 'src/engine/core-modules/domain/domain-server-config/domain-server-config.module';
import { ClientConfigController } from './client-config.controller';
import { ClientConfigService } from './services/client-config.service';
@Module({
imports: [DomainManagerModule],
imports: [DomainServerConfigModule],
controllers: [ClientConfigController],
providers: [ClientConfigService],
})
@@ -6,14 +6,14 @@ import { SupportDriver } from 'src/engine/core-modules/twenty-config/interfaces/
import { AiModelRegistryService } from 'src/engine/core-modules/ai/services/ai-model-registry.service';
import { CaptchaDriverType } from 'src/engine/core-modules/captcha/interfaces';
import { ClientConfigService } from 'src/engine/core-modules/client-config/services/client-config.service';
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
import { DomainServerConfigService } from 'src/engine/core-modules/domain/domain-server-config/services/domain-server-config.service';
import { PUBLIC_FEATURE_FLAGS } from 'src/engine/core-modules/feature-flag/constants/public-feature-flag.const';
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
describe('ClientConfigService', () => {
let service: ClientConfigService;
let twentyConfigService: TwentyConfigService;
let domainManagerService: DomainManagerService;
let domainServerConfigService: DomainServerConfigService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
@@ -26,7 +26,7 @@ describe('ClientConfigService', () => {
},
},
{
provide: DomainManagerService,
provide: DomainServerConfigService,
useValue: {
getFrontUrl: jest.fn(),
},
@@ -42,8 +42,9 @@ describe('ClientConfigService', () => {
service = module.get<ClientConfigService>(ClientConfigService);
twentyConfigService = module.get<TwentyConfigService>(TwentyConfigService);
domainManagerService =
module.get<DomainManagerService>(DomainManagerService);
domainServerConfigService = module.get<DomainServerConfigService>(
DomainServerConfigService,
);
});
it('should be defined', () => {
@@ -91,7 +92,7 @@ describe('ClientConfigService', () => {
return mockValues[key];
});
jest.spyOn(domainManagerService, 'getFrontUrl').mockReturnValue({
jest.spyOn(domainServerConfigService, 'getFrontUrl').mockReturnValue({
hostname: 'app.twenty.com',
} as URL);
});
@@ -15,7 +15,7 @@ import {
type ClientAIModelConfig,
type ClientConfig,
} from 'src/engine/core-modules/client-config/client-config.entity';
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
import { DomainServerConfigService } from 'src/engine/core-modules/domain/domain-server-config/services/domain-server-config.service';
import { PUBLIC_FEATURE_FLAGS } from 'src/engine/core-modules/feature-flag/constants/public-feature-flag.const';
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
@@ -23,7 +23,7 @@ import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twent
export class ClientConfigService {
constructor(
private twentyConfigService: TwentyConfigService,
private domainManagerService: DomainManagerService,
private domainServerConfigService: DomainServerConfigService,
private aiModelRegistryService: AiModelRegistryService,
) {}
@@ -107,7 +107,7 @@ export class ClientConfigService {
'IS_EMAIL_VERIFICATION_REQUIRED',
),
defaultSubdomain: this.twentyConfigService.get('DEFAULT_SUBDOMAIN'),
frontDomain: this.domainManagerService.getFrontUrl().hostname,
frontDomain: this.domainServerConfigService.getFrontUrl().hostname,
debugMode:
this.twentyConfigService.get('NODE_ENV') ===
NodeEnvironment.DEVELOPMENT,