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:
+4
-4
@@ -13,8 +13,9 @@ import { parseCoreBatchPath } from 'src/engine/api/rest/core/query-builder/utils
|
||||
import { parseCorePath } from 'src/engine/api/rest/core/query-builder/utils/path-parsers/parse-core-path.utils';
|
||||
import { type Query } from 'src/engine/api/rest/core/types/query.type';
|
||||
import { AccessTokenService } from 'src/engine/core-modules/auth/token/services/access-token.service';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { WorkspaceDomainsService } from 'src/engine/core-modules/domain/workspace-domains/services/workspace-domains.service';
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
import { WorkspaceNotFoundDefaultError } from 'src/engine/core-modules/workspace/workspace.exception';
|
||||
import { type ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||
import { type ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
||||
import { getObjectMetadataMapItemByNamePlural } from 'src/engine/metadata-modules/utils/get-object-metadata-map-item-by-name-plural.util';
|
||||
@@ -23,7 +24,6 @@ import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/works
|
||||
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
|
||||
import { standardObjectMetadataDefinitions } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-objects';
|
||||
import { shouldExcludeFromWorkspaceApi } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/should-exclude-from-workspace-api.util';
|
||||
import { WorkspaceNotFoundDefaultError } from 'src/engine/core-modules/workspace/workspace.exception';
|
||||
|
||||
@Injectable()
|
||||
export class CoreQueryBuilderFactory {
|
||||
@@ -33,7 +33,7 @@ export class CoreQueryBuilderFactory {
|
||||
private readonly findDuplicatesQueryFactory: FindDuplicatesQueryFactory,
|
||||
private readonly findDuplicatesVariablesFactory: FindDuplicatesVariablesFactory,
|
||||
private readonly accessTokenService: AccessTokenService,
|
||||
private readonly domainManagerService: DomainManagerService,
|
||||
private readonly workspaceDomainsService: WorkspaceDomainsService,
|
||||
private readonly workspaceCacheStorageService: WorkspaceCacheStorageService,
|
||||
private readonly workspaceMetadataCacheService: WorkspaceMetadataCacheService,
|
||||
private readonly featureFlagService: FeatureFlagService,
|
||||
@@ -69,7 +69,7 @@ export class CoreQueryBuilderFactory {
|
||||
|
||||
if (!objectMetadataMaps) {
|
||||
throw new BadRequestException(
|
||||
`No object was found for the workspace associated with this API key. You may generate a new one here ${this.domainManagerService
|
||||
`No object was found for the workspace associated with this API key. You may generate a new one here ${this.workspaceDomainsService
|
||||
.buildWorkspaceURL({
|
||||
workspace,
|
||||
pathname: getSettingsPath(SettingsPath.ApiWebhooks),
|
||||
|
||||
+2
-2
@@ -4,7 +4,7 @@ import { CoreQueryBuilderFactory } from 'src/engine/api/rest/core/query-builder/
|
||||
import { coreQueryBuilderFactories } from 'src/engine/api/rest/core/query-builder/factories/factories';
|
||||
import { ApiKeyModule } from 'src/engine/core-modules/api-key/api-key.module';
|
||||
import { AuthModule } from 'src/engine/core-modules/auth/auth.module';
|
||||
import { DomainManagerModule } from 'src/engine/core-modules/domain-manager/domain-manager.module';
|
||||
import { WorkspaceDomainsModule } from 'src/engine/core-modules/domain/workspace-domains/workspace-domains.module';
|
||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||
import { WorkspaceMetadataCacheModule } from 'src/engine/metadata-modules/workspace-metadata-cache/workspace-metadata-cache.module';
|
||||
import { WorkspacePermissionsCacheModule } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.module';
|
||||
@@ -14,7 +14,7 @@ import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/
|
||||
imports: [
|
||||
AuthModule,
|
||||
ApiKeyModule,
|
||||
DomainManagerModule,
|
||||
WorkspaceDomainsModule,
|
||||
FeatureFlagModule,
|
||||
WorkspaceCacheStorageModule,
|
||||
WorkspaceMetadataCacheModule,
|
||||
|
||||
@@ -19,7 +19,6 @@ import { RestApiService } from 'src/engine/api/rest/rest-api.service';
|
||||
import { ActorModule } from 'src/engine/core-modules/actor/actor.module';
|
||||
import { ApiKeyModule } from 'src/engine/core-modules/api-key/api-key.module';
|
||||
import { AuthModule } from 'src/engine/core-modules/auth/auth.module';
|
||||
import { DomainManagerModule } from 'src/engine/core-modules/domain-manager/domain-manager.module';
|
||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||
import { RecordTransformerModule } from 'src/engine/core-modules/record-transformer/record-transformer.module';
|
||||
import { UserRoleModule } from 'src/engine/metadata-modules/user-role/user-role.module';
|
||||
@@ -54,7 +53,6 @@ const restApiCoreResolvers = [
|
||||
ActorModule,
|
||||
FeatureFlagModule,
|
||||
CoreCommonApiModule,
|
||||
DomainManagerModule,
|
||||
],
|
||||
controllers: [RestApiCoreController],
|
||||
providers: [
|
||||
|
||||
+2
-2
@@ -6,7 +6,7 @@ import axios from 'axios';
|
||||
import { AdminPanelService } from 'src/engine/core-modules/admin-panel/admin-panel.service';
|
||||
import { AuditService } from 'src/engine/core-modules/audit/services/audit.service';
|
||||
import { LoginTokenService } from 'src/engine/core-modules/auth/token/services/login-token.service';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { WorkspaceDomainsService } from 'src/engine/core-modules/domain/workspace-domains/services/workspace-domains.service';
|
||||
import { FileService } from 'src/engine/core-modules/file/services/file.service';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
import { User } from 'src/engine/core-modules/user/user.entity';
|
||||
@@ -59,7 +59,7 @@ describe('AdminPanelService', () => {
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: DomainManagerService,
|
||||
provide: WorkspaceDomainsService,
|
||||
useValue: {
|
||||
getWorkspaceUrls: jest.fn().mockReturnValue({
|
||||
customUrl: undefined,
|
||||
|
||||
@@ -8,7 +8,7 @@ import { AdminPanelResolver } from 'src/engine/core-modules/admin-panel/admin-pa
|
||||
import { AdminPanelService } from 'src/engine/core-modules/admin-panel/admin-panel.service';
|
||||
import { AuditModule } from 'src/engine/core-modules/audit/audit.module';
|
||||
import { AuthModule } from 'src/engine/core-modules/auth/auth.module';
|
||||
import { DomainManagerModule } from 'src/engine/core-modules/domain-manager/domain-manager.module';
|
||||
import { WorkspaceDomainsModule } from 'src/engine/core-modules/domain/workspace-domains/workspace-domains.module';
|
||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||
import { FileModule } from 'src/engine/core-modules/file/file.module';
|
||||
import { HealthModule } from 'src/engine/core-modules/health/health.module';
|
||||
@@ -22,8 +22,8 @@ import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permi
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([User]),
|
||||
AuthModule,
|
||||
DomainManagerModule,
|
||||
FileModule,
|
||||
WorkspaceDomainsModule,
|
||||
HealthModule,
|
||||
RedisClientModule,
|
||||
TerminusModule,
|
||||
|
||||
@@ -12,13 +12,11 @@ import { type ConfigVariablesGroupDataDTO } from 'src/engine/core-modules/admin-
|
||||
import { type ConfigVariablesOutput } from 'src/engine/core-modules/admin-panel/dtos/config-variables.output';
|
||||
import { type UserLookup } from 'src/engine/core-modules/admin-panel/dtos/user-lookup.entity';
|
||||
import { type VersionInfoDTO } from 'src/engine/core-modules/admin-panel/dtos/version-info.dto';
|
||||
import { AuditService } from 'src/engine/core-modules/audit/services/audit.service';
|
||||
import {
|
||||
AuthException,
|
||||
AuthExceptionCode,
|
||||
} from 'src/engine/core-modules/auth/auth.exception';
|
||||
import { LoginTokenService } from 'src/engine/core-modules/auth/token/services/login-token.service';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { WorkspaceDomainsService } from 'src/engine/core-modules/domain/workspace-domains/services/workspace-domains.service';
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
import { type FeatureFlag } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
|
||||
import { FileService } from 'src/engine/core-modules/file/services/file.service';
|
||||
@@ -32,10 +30,8 @@ import { userValidator } from 'src/engine/core-modules/user/user.validate';
|
||||
@Injectable()
|
||||
export class AdminPanelService {
|
||||
constructor(
|
||||
private readonly loginTokenService: LoginTokenService,
|
||||
private readonly twentyConfigService: TwentyConfigService,
|
||||
private readonly domainManagerService: DomainManagerService,
|
||||
private readonly auditService: AuditService,
|
||||
private readonly workspaceDomainsService: WorkspaceDomainsService,
|
||||
private readonly fileService: FileService,
|
||||
@InjectRepository(User)
|
||||
private readonly userRepository: Repository<User>,
|
||||
@@ -90,7 +86,7 @@ export class AdminPanelService {
|
||||
})
|
||||
: userWorkspace.workspace.logo,
|
||||
allowImpersonation: userWorkspace.workspace.allowImpersonation,
|
||||
workspaceUrls: this.domainManagerService.getWorkspaceUrls({
|
||||
workspaceUrls: this.workspaceDomainsService.getWorkspaceUrls({
|
||||
subdomain: userWorkspace.workspace.subdomain,
|
||||
customDomain: userWorkspace.workspace.customDomain,
|
||||
isCustomDomainEnabled: userWorkspace.workspace.isCustomDomainEnabled,
|
||||
|
||||
+2
-2
@@ -5,12 +5,12 @@ import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm';
|
||||
import { ApprovedAccessDomain } from 'src/engine/core-modules/approved-access-domain/approved-access-domain.entity';
|
||||
import { ApprovedAccessDomainResolver } from 'src/engine/core-modules/approved-access-domain/approved-access-domain.resolver';
|
||||
import { ApprovedAccessDomainService } from 'src/engine/core-modules/approved-access-domain/services/approved-access-domain.service';
|
||||
import { DomainManagerModule } from 'src/engine/core-modules/domain-manager/domain-manager.module';
|
||||
import { WorkspaceDomainsModule } from 'src/engine/core-modules/domain/workspace-domains/workspace-domains.module';
|
||||
import { FileModule } from 'src/engine/core-modules/file/file.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
DomainManagerModule,
|
||||
WorkspaceDomainsModule,
|
||||
FileModule,
|
||||
NestjsQueryTypeOrmModule.forFeature([ApprovedAccessDomain]),
|
||||
],
|
||||
|
||||
+3
-3
@@ -16,7 +16,7 @@ import {
|
||||
ApprovedAccessDomainExceptionCode,
|
||||
} from 'src/engine/core-modules/approved-access-domain/approved-access-domain.exception';
|
||||
import { approvedAccessDomainValidator } from 'src/engine/core-modules/approved-access-domain/approved-access-domain.validate';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { WorkspaceDomainsService } from 'src/engine/core-modules/domain/workspace-domains/services/workspace-domains.service';
|
||||
import { EmailService } from 'src/engine/core-modules/email/email.service';
|
||||
import { FileService } from 'src/engine/core-modules/file/services/file.service';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
@@ -31,8 +31,8 @@ export class ApprovedAccessDomainService {
|
||||
private readonly approvedAccessDomainRepository: Repository<ApprovedAccessDomainEntity>,
|
||||
private readonly emailService: EmailService,
|
||||
private readonly twentyConfigService: TwentyConfigService,
|
||||
private readonly domainManagerService: DomainManagerService,
|
||||
private readonly fileService: FileService,
|
||||
private readonly workspaceDomainsService: WorkspaceDomainsService,
|
||||
) {}
|
||||
|
||||
async sendApprovedAccessDomainValidationEmail(
|
||||
@@ -61,7 +61,7 @@ export class ApprovedAccessDomainService {
|
||||
);
|
||||
}
|
||||
|
||||
const link = this.domainManagerService.buildWorkspaceURL({
|
||||
const link = this.workspaceDomainsService.buildWorkspaceURL({
|
||||
workspace,
|
||||
pathname: getSettingsPath(SettingsPath.Domains),
|
||||
searchParams: {
|
||||
|
||||
+8
-7
@@ -10,7 +10,7 @@ import {
|
||||
ApprovedAccessDomainException,
|
||||
ApprovedAccessDomainExceptionCode,
|
||||
} from 'src/engine/core-modules/approved-access-domain/approved-access-domain.exception';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { WorkspaceDomainsService } from 'src/engine/core-modules/domain/workspace-domains/services/workspace-domains.service';
|
||||
import { EmailService } from 'src/engine/core-modules/email/email.service';
|
||||
import { FileService } from 'src/engine/core-modules/file/services/file.service';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
@@ -35,7 +35,7 @@ describe('ApprovedAccessDomainService', () => {
|
||||
let approvedAccessDomainRepository: Repository<ApprovedAccessDomain>;
|
||||
let emailService: EmailService;
|
||||
let twentyConfigService: TwentyConfigService;
|
||||
let domainManagerService: DomainManagerService;
|
||||
let workspaceDomainsService: WorkspaceDomainsService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
@@ -63,7 +63,7 @@ describe('ApprovedAccessDomainService', () => {
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: DomainManagerService,
|
||||
provide: WorkspaceDomainsService,
|
||||
useValue: {
|
||||
buildWorkspaceURL: jest.fn(),
|
||||
},
|
||||
@@ -87,8 +87,9 @@ describe('ApprovedAccessDomainService', () => {
|
||||
);
|
||||
emailService = module.get<EmailService>(EmailService);
|
||||
twentyConfigService = module.get<TwentyConfigService>(TwentyConfigService);
|
||||
domainManagerService =
|
||||
module.get<DomainManagerService>(DomainManagerService);
|
||||
workspaceDomainsService = module.get<WorkspaceDomainsService>(
|
||||
WorkspaceDomainsService,
|
||||
);
|
||||
});
|
||||
|
||||
describe('createApprovedAccessDomain', () => {
|
||||
@@ -286,7 +287,7 @@ describe('ApprovedAccessDomainService', () => {
|
||||
.mockResolvedValue(approvedAccessDomain);
|
||||
|
||||
jest
|
||||
.spyOn(domainManagerService, 'buildWorkspaceURL')
|
||||
.spyOn(workspaceDomainsService, 'buildWorkspaceURL')
|
||||
.mockReturnValue(new URL('https://sub.twenty.com'));
|
||||
|
||||
jest
|
||||
@@ -303,7 +304,7 @@ describe('ApprovedAccessDomainService', () => {
|
||||
approvedAccessDomain,
|
||||
);
|
||||
|
||||
expect(domainManagerService.buildWorkspaceURL).toHaveBeenCalledWith({
|
||||
expect(workspaceDomainsService.buildWorkspaceURL).toHaveBeenCalledWith({
|
||||
workspace: workspace,
|
||||
pathname: getSettingsPath(SettingsPath.Domains),
|
||||
searchParams: { validationToken: expect.any(String) },
|
||||
|
||||
@@ -33,7 +33,9 @@ import { LoginTokenService } from 'src/engine/core-modules/auth/token/services/l
|
||||
import { RefreshTokenService } from 'src/engine/core-modules/auth/token/services/refresh-token.service';
|
||||
import { TransientTokenService } from 'src/engine/core-modules/auth/token/services/transient-token.service';
|
||||
import { TokenModule } from 'src/engine/core-modules/auth/token/token.module';
|
||||
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 { SubdomainManagerModule } from 'src/engine/core-modules/domain/subdomain-manager/subdomain-manager.module';
|
||||
import { WorkspaceDomainsModule } from 'src/engine/core-modules/domain/workspace-domains/workspace-domains.module';
|
||||
import { EmailVerificationModule } from 'src/engine/core-modules/email-verification/email-verification.module';
|
||||
import { FeatureFlag } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
|
||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||
@@ -74,7 +76,7 @@ import { JwtAuthStrategy } from './strategies/jwt.auth.strategy';
|
||||
JwtModule,
|
||||
FileUploadModule,
|
||||
DataSourceModule,
|
||||
DomainManagerModule,
|
||||
WorkspaceDomainsModule,
|
||||
TokenModule,
|
||||
UserModule,
|
||||
WorkspaceManagerModule,
|
||||
@@ -109,6 +111,8 @@ import { JwtAuthStrategy } from './strategies/jwt.auth.strategy';
|
||||
TwoFactorAuthenticationModule,
|
||||
ApiKeyModule,
|
||||
AuditModule,
|
||||
SubdomainManagerModule,
|
||||
DomainServerConfigModule,
|
||||
],
|
||||
controllers: [
|
||||
GoogleAuthController,
|
||||
|
||||
@@ -9,7 +9,7 @@ import { SignInUpService } from 'src/engine/core-modules/auth/services/sign-in-u
|
||||
import { RefreshTokenService } from 'src/engine/core-modules/auth/token/services/refresh-token.service';
|
||||
import { WorkspaceAgnosticTokenService } from 'src/engine/core-modules/auth/token/services/workspace-agnostic-token.service';
|
||||
import { CaptchaGuard } from 'src/engine/core-modules/captcha/captcha.guard';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { WorkspaceDomainsService } from 'src/engine/core-modules/domain/workspace-domains/services/workspace-domains.service';
|
||||
import { EmailVerificationService } from 'src/engine/core-modules/email-verification/services/email-verification.service';
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
import { SSOService } from 'src/engine/core-modules/sso/services/sso.service';
|
||||
@@ -64,7 +64,7 @@ describe('AuthResolver', () => {
|
||||
useValue: {},
|
||||
},
|
||||
{
|
||||
provide: DomainManagerService,
|
||||
provide: WorkspaceDomainsService,
|
||||
useValue: {
|
||||
buildWorkspaceURL: jest
|
||||
.fn()
|
||||
|
||||
@@ -49,7 +49,7 @@ import {
|
||||
} from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import { CaptchaGuard } from 'src/engine/core-modules/captcha/captcha.guard';
|
||||
import { CaptchaGraphqlApiExceptionFilter } from 'src/engine/core-modules/captcha/filters/captcha-graphql-api-exception.filter';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { WorkspaceDomainsService } from 'src/engine/core-modules/domain/workspace-domains/services/workspace-domains.service';
|
||||
import { EmailVerificationExceptionFilter } from 'src/engine/core-modules/email-verification/email-verification-exception-filter.util';
|
||||
import { EmailVerificationService } from 'src/engine/core-modules/email-verification/services/email-verification.service';
|
||||
import { PreventNestToAutoLogGraphqlErrorsFilter } from 'src/engine/core-modules/graphql/filters/prevent-nest-to-auto-log-graphql-errors.filter';
|
||||
@@ -61,6 +61,7 @@ import { TwoFactorAuthenticationExceptionFilter } from 'src/engine/core-modules/
|
||||
import { TwoFactorAuthenticationService } from 'src/engine/core-modules/two-factor-authentication/two-factor-authentication.service';
|
||||
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||
import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/user-workspace.service';
|
||||
import { UserService } from 'src/engine/core-modules/user/services/user.service';
|
||||
import { User } from 'src/engine/core-modules/user/user.entity';
|
||||
import { AuthProviderEnum } from 'src/engine/core-modules/workspace/types/workspace.type';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
@@ -74,7 +75,6 @@ import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants';
|
||||
import { PermissionsService } from 'src/engine/metadata-modules/permissions/permissions.service';
|
||||
import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter';
|
||||
import { UserService } from 'src/engine/core-modules/user/services/user.service';
|
||||
|
||||
import { GetAuthTokensFromLoginTokenInput } from './dto/get-auth-tokens-from-login-token.input';
|
||||
import { LoginToken } from './dto/login-token.entity';
|
||||
@@ -115,8 +115,7 @@ export class AuthResolver {
|
||||
private signInUpService: SignInUpService,
|
||||
private transientTokenService: TransientTokenService,
|
||||
private emailVerificationService: EmailVerificationService,
|
||||
// private oauthService: OAuthService,
|
||||
private domainManagerService: DomainManagerService,
|
||||
private workspaceDomainsService: WorkspaceDomainsService,
|
||||
private userWorkspaceService: UserWorkspaceService,
|
||||
private emailVerificationTokenService: EmailVerificationTokenService,
|
||||
private sSOService: SSOService,
|
||||
@@ -173,7 +172,7 @@ export class AuthResolver {
|
||||
@Args('origin') origin: string,
|
||||
): Promise<LoginToken> {
|
||||
const workspace =
|
||||
await this.domainManagerService.getWorkspaceByOriginOrDefaultWorkspace(
|
||||
await this.workspaceDomainsService.getWorkspaceByOriginOrDefaultWorkspace(
|
||||
origin,
|
||||
);
|
||||
|
||||
@@ -252,7 +251,7 @@ export class AuthResolver {
|
||||
);
|
||||
|
||||
const workspace =
|
||||
(await this.domainManagerService.getWorkspaceByOriginOrDefaultWorkspace(
|
||||
(await this.workspaceDomainsService.getWorkspaceByOriginOrDefaultWorkspace(
|
||||
origin,
|
||||
)) ??
|
||||
(await this.userWorkspaceService.findFirstWorkspaceByUserId(
|
||||
@@ -268,7 +267,8 @@ export class AuthResolver {
|
||||
authProvider,
|
||||
);
|
||||
|
||||
const workspaceUrls = this.domainManagerService.getWorkspaceUrls(workspace);
|
||||
const workspaceUrls =
|
||||
this.workspaceDomainsService.getWorkspaceUrls(workspace);
|
||||
|
||||
return { loginToken, workspaceUrls };
|
||||
}
|
||||
@@ -330,7 +330,7 @@ export class AuthResolver {
|
||||
);
|
||||
|
||||
const workspace =
|
||||
await this.domainManagerService.getWorkspaceByOriginOrDefaultWorkspace(
|
||||
await this.workspaceDomainsService.getWorkspaceByOriginOrDefaultWorkspace(
|
||||
origin,
|
||||
);
|
||||
|
||||
@@ -475,7 +475,7 @@ export class AuthResolver {
|
||||
loginToken,
|
||||
workspace: {
|
||||
id: workspace.id,
|
||||
workspaceUrls: this.domainManagerService.getWorkspaceUrls(workspace),
|
||||
workspaceUrls: this.workspaceDomainsService.getWorkspaceUrls(workspace),
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -504,20 +504,11 @@ export class AuthResolver {
|
||||
loginToken,
|
||||
workspace: {
|
||||
id: workspace.id,
|
||||
workspaceUrls: this.domainManagerService.getWorkspaceUrls(workspace),
|
||||
workspaceUrls: this.workspaceDomainsService.getWorkspaceUrls(workspace),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// @Mutation(() => ExchangeAuthCode)
|
||||
// async exchangeAuthorizationCode(
|
||||
// @Args() exchangeAuthCodeInput: ExchangeAuthCodeInput,
|
||||
// ) {
|
||||
// return await this.oauthService.verifyAuthorizationCode(
|
||||
// exchangeAuthCodeInput,
|
||||
// );
|
||||
// }
|
||||
|
||||
@Mutation(() => TransientToken)
|
||||
@UseGuards(UserAuthGuard)
|
||||
async generateTransientToken(
|
||||
@@ -606,7 +597,7 @@ export class AuthResolver {
|
||||
tokenWorkspaceId: string,
|
||||
): Promise<Workspace> {
|
||||
const workspace =
|
||||
await this.domainManagerService.getWorkspaceByOriginOrDefaultWorkspace(
|
||||
await this.workspaceDomainsService.getWorkspaceByOriginOrDefaultWorkspace(
|
||||
origin,
|
||||
);
|
||||
|
||||
|
||||
+3
-3
@@ -23,12 +23,12 @@ import { GoogleAPIsOauthRequestCodeGuard } from 'src/engine/core-modules/auth/gu
|
||||
import { GoogleAPIsService } from 'src/engine/core-modules/auth/services/google-apis.service';
|
||||
import { TransientTokenService } from 'src/engine/core-modules/auth/token/services/transient-token.service';
|
||||
import { GoogleAPIsRequest } from 'src/engine/core-modules/auth/types/google-api-request.type';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { GuardRedirectService } from 'src/engine/core-modules/guard-redirect/services/guard-redirect.service';
|
||||
import { OnboardingService } from 'src/engine/core-modules/onboarding/onboarding.service';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { PublicEndpointGuard } from 'src/engine/guards/public-endpoint.guard';
|
||||
import { WorkspaceDomainsService } from 'src/engine/core-modules/domain/workspace-domains/services/workspace-domains.service';
|
||||
|
||||
@Controller('auth/google-apis')
|
||||
@UseFilters(AuthRestApiExceptionFilter)
|
||||
@@ -38,7 +38,7 @@ export class GoogleAPIsAuthController {
|
||||
private readonly transientTokenService: TransientTokenService,
|
||||
private readonly twentyConfigService: TwentyConfigService,
|
||||
private readonly onboardingService: OnboardingService,
|
||||
private readonly domainManagerService: DomainManagerService,
|
||||
private readonly workspaceDomainsService: WorkspaceDomainsService,
|
||||
private readonly guardRedirectService: GuardRedirectService,
|
||||
@InjectRepository(Workspace)
|
||||
private readonly workspaceRepository: Repository<Workspace>,
|
||||
@@ -120,7 +120,7 @@ export class GoogleAPIsAuthController {
|
||||
connectedAccountId,
|
||||
});
|
||||
|
||||
const url = this.domainManagerService.buildWorkspaceURL({
|
||||
const url = this.workspaceDomainsService.buildWorkspaceURL({
|
||||
workspace,
|
||||
pathname,
|
||||
});
|
||||
|
||||
+3
-3
@@ -23,7 +23,7 @@ import { MicrosoftAPIsOauthRequestCodeGuard } from 'src/engine/core-modules/auth
|
||||
import { MicrosoftAPIsService } from 'src/engine/core-modules/auth/services/microsoft-apis.service';
|
||||
import { TransientTokenService } from 'src/engine/core-modules/auth/token/services/transient-token.service';
|
||||
import { MicrosoftAPIsRequest } from 'src/engine/core-modules/auth/types/microsoft-api-request.type';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { WorkspaceDomainsService } from 'src/engine/core-modules/domain/workspace-domains/services/workspace-domains.service';
|
||||
import { GuardRedirectService } from 'src/engine/core-modules/guard-redirect/services/guard-redirect.service';
|
||||
import { OnboardingService } from 'src/engine/core-modules/onboarding/onboarding.service';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
@@ -37,7 +37,7 @@ export class MicrosoftAPIsAuthController {
|
||||
private readonly microsoftAPIsService: MicrosoftAPIsService,
|
||||
private readonly transientTokenService: TransientTokenService,
|
||||
private readonly twentyConfigService: TwentyConfigService,
|
||||
private readonly domainManagerService: DomainManagerService,
|
||||
private readonly workspaceDomainsService: WorkspaceDomainsService,
|
||||
private readonly onboardingService: OnboardingService,
|
||||
private readonly guardRedirectService: GuardRedirectService,
|
||||
@InjectRepository(Workspace)
|
||||
@@ -127,7 +127,7 @@ export class MicrosoftAPIsAuthController {
|
||||
connectedAccountId,
|
||||
});
|
||||
|
||||
const url = this.domainManagerService.buildWorkspaceURL({
|
||||
const url = this.workspaceDomainsService.buildWorkspaceURL({
|
||||
workspace,
|
||||
pathname,
|
||||
});
|
||||
|
||||
+5
-5
@@ -14,8 +14,8 @@ import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { generateServiceProviderMetadata } from '@node-saml/node-saml';
|
||||
import { Response } from 'express';
|
||||
import { AppPath } from 'twenty-shared/types';
|
||||
import { Repository } from 'typeorm';
|
||||
import { assertIsDefinedOrThrow } from 'twenty-shared/utils';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import {
|
||||
AuthException,
|
||||
@@ -29,17 +29,17 @@ import { AuthService } from 'src/engine/core-modules/auth/services/auth.service'
|
||||
import { OIDCRequest } from 'src/engine/core-modules/auth/strategies/oidc.auth.strategy';
|
||||
import { SAMLRequest } from 'src/engine/core-modules/auth/strategies/saml.auth.strategy';
|
||||
import { LoginTokenService } from 'src/engine/core-modules/auth/token/services/login-token.service';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { WorkspaceDomainsService } from 'src/engine/core-modules/domain/workspace-domains/services/workspace-domains.service';
|
||||
import { GuardRedirectService } from 'src/engine/core-modules/guard-redirect/services/guard-redirect.service';
|
||||
import { SSOService } from 'src/engine/core-modules/sso/services/sso.service';
|
||||
import {
|
||||
IdentityProviderType,
|
||||
WorkspaceSSOIdentityProvider,
|
||||
} from 'src/engine/core-modules/sso/workspace-sso-identity-provider.entity';
|
||||
import { UserService } from 'src/engine/core-modules/user/services/user.service';
|
||||
import { AuthProviderEnum } from 'src/engine/core-modules/workspace/types/workspace.type';
|
||||
import { type Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { PublicEndpointGuard } from 'src/engine/guards/public-endpoint.guard';
|
||||
import { UserService } from 'src/engine/core-modules/user/services/user.service';
|
||||
|
||||
@Controller('auth')
|
||||
@UseFilters(AuthRestApiExceptionFilter)
|
||||
@@ -48,7 +48,7 @@ export class SSOAuthController {
|
||||
private readonly loginTokenService: LoginTokenService,
|
||||
private readonly authService: AuthService,
|
||||
private readonly guardRedirectService: GuardRedirectService,
|
||||
private readonly domainManagerService: DomainManagerService,
|
||||
private readonly workspaceDomainsService: WorkspaceDomainsService,
|
||||
private readonly userService: UserService,
|
||||
private readonly sSOService: SSOService,
|
||||
@InjectRepository(WorkspaceSSOIdentityProvider)
|
||||
@@ -158,7 +158,7 @@ export class SSOAuthController {
|
||||
this.guardRedirectService.getRedirectErrorUrlAndCaptureExceptions({
|
||||
error,
|
||||
workspace:
|
||||
this.domainManagerService.getSubdomainAndCustomDomainFromWorkspaceFallbackOnDefaultSubdomain(
|
||||
this.workspaceDomainsService.getSubdomainAndCustomDomainFromWorkspaceFallbackOnDefaultSubdomain(
|
||||
workspaceIdentityProvider?.workspace,
|
||||
),
|
||||
pathname: AppPath.Verify,
|
||||
|
||||
+3
-3
@@ -10,13 +10,13 @@ import {
|
||||
AuthException,
|
||||
AuthExceptionCode,
|
||||
} from 'src/engine/core-modules/auth/auth.exception';
|
||||
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 { HttpExceptionHandlerService } from 'src/engine/core-modules/exception-handler/http-exception-handler.service';
|
||||
|
||||
@Catch(AuthException)
|
||||
export class AuthOAuthExceptionFilter implements ExceptionFilter {
|
||||
constructor(
|
||||
private readonly domainManagerService: DomainManagerService,
|
||||
private readonly domainServerConfigService: DomainServerConfigService,
|
||||
private readonly httpExceptionHandlerService: HttpExceptionHandlerService,
|
||||
) {}
|
||||
|
||||
@@ -28,7 +28,7 @@ export class AuthOAuthExceptionFilter implements ExceptionFilter {
|
||||
case AuthExceptionCode.OAUTH_ACCESS_DENIED:
|
||||
response
|
||||
.status(403)
|
||||
.redirect(this.domainManagerService.getBaseUrl().toString());
|
||||
.redirect(this.domainServerConfigService.getBaseUrl().toString());
|
||||
break;
|
||||
default:
|
||||
return this.httpExceptionHandlerService.handleError(
|
||||
|
||||
+3
-3
@@ -11,7 +11,7 @@ import {
|
||||
import { GoogleAPIsOauthExchangeCodeForTokenStrategy } from 'src/engine/core-modules/auth/strategies/google-apis-oauth-exchange-code-for-token.auth.strategy';
|
||||
import { TransientTokenService } from 'src/engine/core-modules/auth/token/services/transient-token.service';
|
||||
import { setRequestExtraParams } from 'src/engine/core-modules/auth/utils/google-apis-set-request-extra-params.util';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { WorkspaceDomainsService } from 'src/engine/core-modules/domain/workspace-domains/services/workspace-domains.service';
|
||||
import { GuardRedirectService } from 'src/engine/core-modules/guard-redirect/services/guard-redirect.service';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
@@ -24,9 +24,9 @@ export class GoogleAPIsOauthExchangeCodeForTokenGuard extends AuthGuard(
|
||||
private readonly guardRedirectService: GuardRedirectService,
|
||||
private readonly twentyConfigService: TwentyConfigService,
|
||||
private readonly transientTokenService: TransientTokenService,
|
||||
private readonly domainManagerService: DomainManagerService,
|
||||
@InjectRepository(Workspace)
|
||||
private readonly workspaceRepository: Repository<Workspace>,
|
||||
private readonly workspaceDomainsService: WorkspaceDomainsService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
@@ -66,7 +66,7 @@ export class GoogleAPIsOauthExchangeCodeForTokenGuard extends AuthGuard(
|
||||
);
|
||||
|
||||
const redirectErrorUrl =
|
||||
this.domainManagerService.computeRedirectErrorUrl(
|
||||
this.workspaceDomainsService.computeWorkspaceRedirectErrorUrl(
|
||||
'We cannot connect to your Google account, please try again with more permissions, or a valid account',
|
||||
{
|
||||
subdomain: workspace.subdomain,
|
||||
|
||||
+3
-3
@@ -11,7 +11,7 @@ import {
|
||||
import { GoogleAPIsOauthRequestCodeStrategy } from 'src/engine/core-modules/auth/strategies/google-apis-oauth-request-code.auth.strategy';
|
||||
import { TransientTokenService } from 'src/engine/core-modules/auth/token/services/transient-token.service';
|
||||
import { setRequestExtraParams } from 'src/engine/core-modules/auth/utils/google-apis-set-request-extra-params.util';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { WorkspaceDomainsService } from 'src/engine/core-modules/domain/workspace-domains/services/workspace-domains.service';
|
||||
import { GuardRedirectService } from 'src/engine/core-modules/guard-redirect/services/guard-redirect.service';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
@@ -24,7 +24,7 @@ export class GoogleAPIsOauthRequestCodeGuard extends AuthGuard('google-apis') {
|
||||
private readonly guardRedirectService: GuardRedirectService,
|
||||
@InjectRepository(Workspace)
|
||||
private readonly workspaceRepository: Repository<Workspace>,
|
||||
private readonly domainManagerService: DomainManagerService,
|
||||
private readonly workspaceDomainsService: WorkspaceDomainsService,
|
||||
) {
|
||||
super({
|
||||
prompt: 'select_account',
|
||||
@@ -73,7 +73,7 @@ export class GoogleAPIsOauthRequestCodeGuard extends AuthGuard('google-apis') {
|
||||
this.guardRedirectService.dispatchErrorFromGuard(
|
||||
context,
|
||||
err,
|
||||
this.domainManagerService.getSubdomainAndCustomDomainFromWorkspaceFallbackOnDefaultSubdomain(
|
||||
this.workspaceDomainsService.getSubdomainAndCustomDomainFromWorkspaceFallbackOnDefaultSubdomain(
|
||||
workspace,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -2,16 +2,16 @@ import { type ExecutionContext, Injectable } from '@nestjs/common';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Repository } from 'typeorm';
|
||||
import { type Request } from 'express';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import {
|
||||
AuthException,
|
||||
AuthExceptionCode,
|
||||
} from 'src/engine/core-modules/auth/auth.exception';
|
||||
import { WorkspaceDomainsService } from 'src/engine/core-modules/domain/workspace-domains/services/workspace-domains.service';
|
||||
import { GuardRedirectService } from 'src/engine/core-modules/guard-redirect/services/guard-redirect.service';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
|
||||
@Injectable()
|
||||
export class GoogleOauthGuard extends AuthGuard('google') {
|
||||
@@ -19,7 +19,7 @@ export class GoogleOauthGuard extends AuthGuard('google') {
|
||||
private readonly guardRedirectService: GuardRedirectService,
|
||||
@InjectRepository(Workspace)
|
||||
private readonly workspaceRepository: Repository<Workspace>,
|
||||
private readonly domainManagerService: DomainManagerService,
|
||||
private readonly workspaceDomainsService: WorkspaceDomainsService,
|
||||
) {
|
||||
super({
|
||||
prompt: 'select_account',
|
||||
@@ -53,7 +53,7 @@ export class GoogleOauthGuard extends AuthGuard('google') {
|
||||
this.guardRedirectService.dispatchErrorFromGuard(
|
||||
context,
|
||||
err,
|
||||
this.domainManagerService.getSubdomainAndCustomDomainFromWorkspaceFallbackOnDefaultSubdomain(
|
||||
this.workspaceDomainsService.getSubdomainAndCustomDomainFromWorkspaceFallbackOnDefaultSubdomain(
|
||||
workspace,
|
||||
),
|
||||
);
|
||||
|
||||
+3
-3
@@ -11,10 +11,10 @@ import {
|
||||
import { MicrosoftAPIsOauthRequestCodeStrategy } from 'src/engine/core-modules/auth/strategies/microsoft-apis-oauth-request-code.auth.strategy';
|
||||
import { TransientTokenService } from 'src/engine/core-modules/auth/token/services/transient-token.service';
|
||||
import { setRequestExtraParams } from 'src/engine/core-modules/auth/utils/google-apis-set-request-extra-params.util';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { GuardRedirectService } from 'src/engine/core-modules/guard-redirect/services/guard-redirect.service';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { WorkspaceDomainsService } from 'src/engine/core-modules/domain/workspace-domains/services/workspace-domains.service';
|
||||
|
||||
@Injectable()
|
||||
export class MicrosoftAPIsOauthRequestCodeGuard extends AuthGuard(
|
||||
@@ -26,7 +26,7 @@ export class MicrosoftAPIsOauthRequestCodeGuard extends AuthGuard(
|
||||
private readonly guardRedirectService: GuardRedirectService,
|
||||
@InjectRepository(Workspace)
|
||||
private readonly workspaceRepository: Repository<Workspace>,
|
||||
private readonly domainManagerService: DomainManagerService,
|
||||
private readonly workspaceDomainsService: WorkspaceDomainsService,
|
||||
) {
|
||||
super({
|
||||
prompt: 'select_account',
|
||||
@@ -72,7 +72,7 @@ export class MicrosoftAPIsOauthRequestCodeGuard extends AuthGuard(
|
||||
this.guardRedirectService.dispatchErrorFromGuard(
|
||||
context,
|
||||
err,
|
||||
this.domainManagerService.getSubdomainAndCustomDomainFromWorkspaceFallbackOnDefaultSubdomain(
|
||||
this.workspaceDomainsService.getSubdomainAndCustomDomainFromWorkspaceFallbackOnDefaultSubdomain(
|
||||
workspace,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -4,9 +4,9 @@ import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { WorkspaceDomainsService } from 'src/engine/core-modules/domain/workspace-domains/services/workspace-domains.service';
|
||||
import { GuardRedirectService } from 'src/engine/core-modules/guard-redirect/services/guard-redirect.service';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
|
||||
@Injectable()
|
||||
export class MicrosoftOAuthGuard extends AuthGuard('microsoft') {
|
||||
@@ -14,7 +14,7 @@ export class MicrosoftOAuthGuard extends AuthGuard('microsoft') {
|
||||
private readonly guardRedirectService: GuardRedirectService,
|
||||
@InjectRepository(Workspace)
|
||||
private readonly workspaceRepository: Repository<Workspace>,
|
||||
private readonly domainManagerService: DomainManagerService,
|
||||
private readonly workspaceDomainsService: WorkspaceDomainsService,
|
||||
) {
|
||||
super({
|
||||
prompt: 'select_account',
|
||||
@@ -41,7 +41,7 @@ export class MicrosoftOAuthGuard extends AuthGuard('microsoft') {
|
||||
this.guardRedirectService.dispatchErrorFromGuard(
|
||||
context,
|
||||
err,
|
||||
this.domainManagerService.getSubdomainAndCustomDomainFromWorkspaceFallbackOnDefaultSubdomain(
|
||||
this.workspaceDomainsService.getSubdomainAndCustomDomainFromWorkspaceFallbackOnDefaultSubdomain(
|
||||
workspace,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -10,18 +10,18 @@ import {
|
||||
AuthExceptionCode,
|
||||
} from 'src/engine/core-modules/auth/auth.exception';
|
||||
import { OIDCAuthStrategy } from 'src/engine/core-modules/auth/strategies/oidc.auth.strategy';
|
||||
import { SSOService } from 'src/engine/core-modules/sso/services/sso.service';
|
||||
import { WorkspaceDomainsService } from 'src/engine/core-modules/domain/workspace-domains/services/workspace-domains.service';
|
||||
import { GuardRedirectService } from 'src/engine/core-modules/guard-redirect/services/guard-redirect.service';
|
||||
import { SSOService } from 'src/engine/core-modules/sso/services/sso.service';
|
||||
import { type SSOConfiguration } from 'src/engine/core-modules/sso/types/SSOConfigurations.type';
|
||||
import { type WorkspaceSSOIdentityProvider } from 'src/engine/core-modules/sso/workspace-sso-identity-provider.entity';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
|
||||
@Injectable()
|
||||
export class OIDCAuthGuard extends AuthGuard('openidconnect') {
|
||||
constructor(
|
||||
private readonly sSOService: SSOService,
|
||||
private readonly guardRedirectService: GuardRedirectService,
|
||||
private readonly domainManagerService: DomainManagerService,
|
||||
private readonly workspaceDomainsService: WorkspaceDomainsService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
@@ -91,7 +91,7 @@ export class OIDCAuthGuard extends AuthGuard('openidconnect') {
|
||||
this.guardRedirectService.dispatchErrorFromGuard(
|
||||
context,
|
||||
err,
|
||||
this.domainManagerService.getSubdomainAndCustomDomainFromWorkspaceFallbackOnDefaultSubdomain(
|
||||
this.workspaceDomainsService.getSubdomainAndCustomDomainFromWorkspaceFallbackOnDefaultSubdomain(
|
||||
identityProvider?.workspace,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { type ExecutionContext } from '@nestjs/common';
|
||||
import { Test, type TestingModule } from '@nestjs/testing';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
import { Test, type TestingModule } from '@nestjs/testing';
|
||||
|
||||
import { type Issuer } from 'openid-client';
|
||||
|
||||
import { SSOService } from 'src/engine/core-modules/sso/services/sso.service';
|
||||
import { GuardRedirectService } from 'src/engine/core-modules/guard-redirect/services/guard-redirect.service';
|
||||
import { OIDCAuthGuard } from 'src/engine/core-modules/auth/guards/oidc-auth.guard';
|
||||
import { WorkspaceDomainsService } from 'src/engine/core-modules/domain/workspace-domains/services/workspace-domains.service';
|
||||
import { GuardRedirectService } from 'src/engine/core-modules/guard-redirect/services/guard-redirect.service';
|
||||
import { SSOService } from 'src/engine/core-modules/sso/services/sso.service';
|
||||
import { type SSOConfiguration } from 'src/engine/core-modules/sso/types/SSOConfigurations.type';
|
||||
import { type WorkspaceSSOIdentityProvider } from 'src/engine/core-modules/sso/workspace-sso-identity-provider.entity';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
|
||||
const createMockExecutionContext = (mockedRequest: any): ExecutionContext => {
|
||||
return {
|
||||
@@ -60,7 +60,7 @@ describe('OIDCAuthGuard', () => {
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: DomainManagerService,
|
||||
provide: WorkspaceDomainsService,
|
||||
useValue: {
|
||||
getSubdomainAndCustomDomainFromWorkspaceFallbackOnDefaultSubdomain:
|
||||
jest.fn(),
|
||||
|
||||
@@ -10,18 +10,18 @@ import {
|
||||
AuthExceptionCode,
|
||||
} from 'src/engine/core-modules/auth/auth.exception';
|
||||
import { SamlAuthStrategy } from 'src/engine/core-modules/auth/strategies/saml.auth.strategy';
|
||||
import { SSOService } from 'src/engine/core-modules/sso/services/sso.service';
|
||||
import { WorkspaceDomainsService } from 'src/engine/core-modules/domain/workspace-domains/services/workspace-domains.service';
|
||||
import { GuardRedirectService } from 'src/engine/core-modules/guard-redirect/services/guard-redirect.service';
|
||||
import { SSOService } from 'src/engine/core-modules/sso/services/sso.service';
|
||||
import { type SSOConfiguration } from 'src/engine/core-modules/sso/types/SSOConfigurations.type';
|
||||
import { type WorkspaceSSOIdentityProvider } from 'src/engine/core-modules/sso/workspace-sso-identity-provider.entity';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
|
||||
@Injectable()
|
||||
export class SAMLAuthGuard extends AuthGuard('saml') {
|
||||
constructor(
|
||||
private readonly sSOService: SSOService,
|
||||
private readonly guardRedirectService: GuardRedirectService,
|
||||
private readonly domainManagerService: DomainManagerService,
|
||||
private readonly workspaceDomainsService: WorkspaceDomainsService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
@@ -51,7 +51,7 @@ export class SAMLAuthGuard extends AuthGuard('saml') {
|
||||
this.guardRedirectService.dispatchErrorFromGuard(
|
||||
context,
|
||||
err,
|
||||
this.domainManagerService.getSubdomainAndCustomDomainFromWorkspaceFallbackOnDefaultSubdomain(
|
||||
this.workspaceDomainsService.getSubdomainAndCustomDomainFromWorkspaceFallbackOnDefaultSubdomain(
|
||||
identityProvider?.workspace,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -17,7 +17,8 @@ import { LoginTokenService } from 'src/engine/core-modules/auth/token/services/l
|
||||
import { RefreshTokenService } from 'src/engine/core-modules/auth/token/services/refresh-token.service';
|
||||
import { WorkspaceAgnosticTokenService } from 'src/engine/core-modules/auth/token/services/workspace-agnostic-token.service';
|
||||
import { type ExistingUserOrNewUser } from 'src/engine/core-modules/auth/types/signInUp.type';
|
||||
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 { WorkspaceDomainsService } from 'src/engine/core-modules/domain/workspace-domains/services/workspace-domains.service';
|
||||
import { EmailService } from 'src/engine/core-modules/email/email.service';
|
||||
import { GuardRedirectService } from 'src/engine/core-modules/guard-redirect/services/guard-redirect.service';
|
||||
import { I18nService } from 'src/engine/core-modules/i18n/i18n.service';
|
||||
@@ -76,7 +77,11 @@ describe('AuthService', () => {
|
||||
useValue: {},
|
||||
},
|
||||
{
|
||||
provide: DomainManagerService,
|
||||
provide: WorkspaceDomainsService,
|
||||
useValue: {},
|
||||
},
|
||||
{
|
||||
provide: DomainServerConfigService,
|
||||
useValue: {},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -50,19 +50,20 @@ import {
|
||||
type SignInUpBaseParams,
|
||||
type SignInUpNewUserPayload,
|
||||
} from 'src/engine/core-modules/auth/types/signInUp.type';
|
||||
import { type WorkspaceSubdomainCustomDomainAndIsCustomDomainEnabledType } from 'src/engine/core-modules/domain-manager/domain-manager.type';
|
||||
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 { WorkspaceDomainsService } from 'src/engine/core-modules/domain/workspace-domains/services/workspace-domains.service';
|
||||
import { WorkspaceDomainConfig } from 'src/engine/core-modules/domain/workspace-domains/types/workspace-domain-config.type';
|
||||
import { EmailService } from 'src/engine/core-modules/email/email.service';
|
||||
import { GuardRedirectService } from 'src/engine/core-modules/guard-redirect/services/guard-redirect.service';
|
||||
import { I18nService } from 'src/engine/core-modules/i18n/i18n.service';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/user-workspace.service';
|
||||
import { UserService } from 'src/engine/core-modules/user/services/user.service';
|
||||
import { User } from 'src/engine/core-modules/user/user.entity';
|
||||
import { WorkspaceInvitationService } from 'src/engine/core-modules/workspace-invitation/services/workspace-invitation.service';
|
||||
import { AuthProviderEnum } from 'src/engine/core-modules/workspace/types/workspace.type';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate';
|
||||
import { UserService } from 'src/engine/core-modules/user/services/user.service';
|
||||
|
||||
@Injectable()
|
||||
// eslint-disable-next-line @nx/workspace-inject-workspace-repository
|
||||
@@ -70,7 +71,8 @@ export class AuthService {
|
||||
constructor(
|
||||
private readonly accessTokenService: AccessTokenService,
|
||||
private readonly workspaceAgnosticTokenService: WorkspaceAgnosticTokenService,
|
||||
private readonly domainManagerService: DomainManagerService,
|
||||
private readonly workspaceDomainsService: WorkspaceDomainsService,
|
||||
private readonly domainServerConfigService: DomainServerConfigService,
|
||||
private readonly refreshTokenService: RefreshTokenService,
|
||||
private readonly loginTokenService: LoginTokenService,
|
||||
private readonly guardRedirectService: GuardRedirectService,
|
||||
@@ -535,7 +537,7 @@ export class AuthService {
|
||||
const emailTemplate = PasswordUpdateNotifyEmail({
|
||||
userName: `${user.firstName} ${user.lastName}`,
|
||||
email: user.email,
|
||||
link: this.domainManagerService.getBaseUrl().toString(),
|
||||
link: this.domainServerConfigService.getBaseUrl().toString(),
|
||||
locale: firstUserWorkspace.locale,
|
||||
});
|
||||
|
||||
@@ -582,10 +584,10 @@ export class AuthService {
|
||||
billingCheckoutSessionState,
|
||||
}: {
|
||||
loginToken: string;
|
||||
workspace: WorkspaceSubdomainCustomDomainAndIsCustomDomainEnabledType;
|
||||
workspace: WorkspaceDomainConfig;
|
||||
billingCheckoutSessionState?: string;
|
||||
}) {
|
||||
const url = this.domainManagerService.buildWorkspaceURL({
|
||||
const url = this.workspaceDomainsService.buildWorkspaceURL({
|
||||
workspace,
|
||||
pathname: AppPath.Verify,
|
||||
searchParams: {
|
||||
@@ -796,7 +798,7 @@ export class AuthService {
|
||||
},
|
||||
));
|
||||
|
||||
const url = this.domainManagerService.buildBaseUrl({
|
||||
const url = this.domainServerConfigService.buildBaseUrl({
|
||||
pathname: AppPath.SignInUp,
|
||||
searchParams: {
|
||||
tokenPair: JSON.stringify({
|
||||
@@ -882,7 +884,7 @@ export class AuthService {
|
||||
return this.guardRedirectService.getRedirectErrorUrlAndCaptureExceptions({
|
||||
error,
|
||||
workspace:
|
||||
this.domainManagerService.getSubdomainAndCustomDomainFromWorkspaceFallbackOnDefaultSubdomain(
|
||||
this.workspaceDomainsService.getSubdomainAndCustomDomainFromWorkspaceFallbackOnDefaultSubdomain(
|
||||
currentWorkspace,
|
||||
),
|
||||
pathname: AppPath.Verify,
|
||||
|
||||
+16
-8
@@ -12,13 +12,14 @@ import {
|
||||
AuthException,
|
||||
AuthExceptionCode,
|
||||
} from 'src/engine/core-modules/auth/auth.exception';
|
||||
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 { WorkspaceDomainsService } from 'src/engine/core-modules/domain/workspace-domains/services/workspace-domains.service';
|
||||
import { EmailService } from 'src/engine/core-modules/email/email.service';
|
||||
import { I18nService } from 'src/engine/core-modules/i18n/i18n.service';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
import { UserService } from 'src/engine/core-modules/user/services/user.service';
|
||||
import { type User } from 'src/engine/core-modules/user/user.entity';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { I18nService } from 'src/engine/core-modules/i18n/i18n.service';
|
||||
import { UserService } from 'src/engine/core-modules/user/services/user.service';
|
||||
|
||||
import { ResetPasswordService } from './reset-password.service';
|
||||
|
||||
@@ -40,7 +41,7 @@ describe('ResetPasswordService', () => {
|
||||
let appTokenRepository: Repository<AppToken>;
|
||||
let emailService: EmailService;
|
||||
let twentyConfigService: TwentyConfigService;
|
||||
let domainManagerService: DomainManagerService;
|
||||
let workspaceDomainsService: WorkspaceDomainsService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
@@ -72,11 +73,16 @@ describe('ResetPasswordService', () => {
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: DomainManagerService,
|
||||
provide: DomainServerConfigService,
|
||||
useValue: {
|
||||
getBaseUrl: jest
|
||||
.fn()
|
||||
.mockResolvedValue(new URL('http://localhost:3001')),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: WorkspaceDomainsService,
|
||||
useValue: {
|
||||
buildWorkspaceURL: jest.fn(),
|
||||
},
|
||||
},
|
||||
@@ -107,8 +113,10 @@ describe('ResetPasswordService', () => {
|
||||
);
|
||||
emailService = module.get<EmailService>(EmailService);
|
||||
twentyConfigService = module.get<TwentyConfigService>(TwentyConfigService);
|
||||
domainManagerService =
|
||||
module.get<DomainManagerService>(DomainManagerService);
|
||||
|
||||
workspaceDomainsService = module.get<WorkspaceDomainsService>(
|
||||
WorkspaceDomainsService,
|
||||
);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
@@ -197,7 +205,7 @@ describe('ResetPasswordService', () => {
|
||||
.spyOn(twentyConfigService, 'get')
|
||||
.mockReturnValue('http://localhost:3000');
|
||||
jest
|
||||
.spyOn(domainManagerService, 'buildWorkspaceURL')
|
||||
.spyOn(workspaceDomainsService, 'buildWorkspaceURL')
|
||||
.mockReturnValue(
|
||||
new URL(
|
||||
'https://subdomain.localhost.com:3000/reset-password/passwordResetToken',
|
||||
|
||||
+4
-4
@@ -25,19 +25,19 @@ import { type EmailPasswordResetLink } from 'src/engine/core-modules/auth/dto/em
|
||||
import { type InvalidatePassword } from 'src/engine/core-modules/auth/dto/invalidate-password.entity';
|
||||
import { type PasswordResetToken } from 'src/engine/core-modules/auth/dto/token.entity';
|
||||
import { type ValidatePasswordResetToken } from 'src/engine/core-modules/auth/dto/validate-password-reset-token.entity';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { EmailService } from 'src/engine/core-modules/email/email.service';
|
||||
import { I18nService } from 'src/engine/core-modules/i18n/i18n.service';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
import { UserService } from 'src/engine/core-modules/user/services/user.service';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { WorkspaceNotFoundDefaultError } from 'src/engine/core-modules/workspace/workspace.exception';
|
||||
import { UserService } from 'src/engine/core-modules/user/services/user.service';
|
||||
import { WorkspaceDomainsService } from 'src/engine/core-modules/domain/workspace-domains/services/workspace-domains.service';
|
||||
|
||||
@Injectable()
|
||||
export class ResetPasswordService {
|
||||
constructor(
|
||||
private readonly twentyConfigService: TwentyConfigService,
|
||||
private readonly domainManagerService: DomainManagerService,
|
||||
private readonly workspaceDomainsService: WorkspaceDomainsService,
|
||||
@InjectRepository(Workspace)
|
||||
private readonly workspaceRepository: Repository<Workspace>,
|
||||
@InjectRepository(AppToken)
|
||||
@@ -127,7 +127,7 @@ export class ResetPasswordService {
|
||||
|
||||
assertIsDefinedOrThrow(workspace, WorkspaceNotFoundDefaultError);
|
||||
|
||||
const link = this.domainManagerService.buildWorkspaceURL({
|
||||
const link = this.workspaceDomainsService.buildWorkspaceURL({
|
||||
workspace,
|
||||
pathname: getAppPath(AppPath.ResetPassword, {
|
||||
passwordResetToken: resetToken.passwordResetToken,
|
||||
|
||||
+7
-6
@@ -17,7 +17,7 @@ import {
|
||||
type ExistingUserOrPartialUserWithPicture,
|
||||
type SignInUpBaseParams,
|
||||
} from 'src/engine/core-modules/auth/types/signInUp.type';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { SubdomainManagerService } from 'src/engine/core-modules/domain/subdomain-manager/services/subdomain-manager.service';
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
import { FileUploadService } from 'src/engine/core-modules/file/file-upload/services/file-upload.service';
|
||||
import { OnboardingService } from 'src/engine/core-modules/onboarding/onboarding.service';
|
||||
@@ -45,7 +45,7 @@ describe('SignInUpService', () => {
|
||||
let workspaceInvitationService: WorkspaceInvitationService;
|
||||
let userWorkspaceService: UserWorkspaceService;
|
||||
let twentyConfigService: TwentyConfigService;
|
||||
let domainManagerService: DomainManagerService;
|
||||
let subdomainManagerService: SubdomainManagerService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
@@ -120,7 +120,7 @@ describe('SignInUpService', () => {
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: DomainManagerService,
|
||||
provide: SubdomainManagerService,
|
||||
useValue: {
|
||||
generateSubdomain: jest.fn(),
|
||||
},
|
||||
@@ -155,8 +155,9 @@ describe('SignInUpService', () => {
|
||||
userWorkspaceService =
|
||||
module.get<UserWorkspaceService>(UserWorkspaceService);
|
||||
twentyConfigService = module.get<TwentyConfigService>(TwentyConfigService);
|
||||
domainManagerService =
|
||||
module.get<DomainManagerService>(DomainManagerService);
|
||||
subdomainManagerService = module.get<SubdomainManagerService>(
|
||||
SubdomainManagerService,
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle signInUp with valid personal invitation', async () => {
|
||||
@@ -271,7 +272,7 @@ describe('SignInUpService', () => {
|
||||
} as Workspace);
|
||||
jest.spyOn(UserRepository, 'create').mockReturnValue({} as User);
|
||||
jest
|
||||
.spyOn(domainManagerService, 'generateSubdomain')
|
||||
.spyOn(subdomainManagerService, 'generateSubdomain')
|
||||
.mockResolvedValue('a-subdomain');
|
||||
jest
|
||||
.spyOn(UserRepository, 'save')
|
||||
|
||||
@@ -26,7 +26,7 @@ import {
|
||||
type SignInUpBaseParams,
|
||||
type SignInUpNewUserPayload,
|
||||
} from 'src/engine/core-modules/auth/types/signInUp.type';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { SubdomainManagerService } from 'src/engine/core-modules/domain/subdomain-manager/services/subdomain-manager.service';
|
||||
import { OnboardingService } from 'src/engine/core-modules/onboarding/onboarding.service';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/user-workspace.service';
|
||||
@@ -53,7 +53,7 @@ export class SignInUpService {
|
||||
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
|
||||
private readonly httpService: HttpService,
|
||||
private readonly twentyConfigService: TwentyConfigService,
|
||||
private readonly domainManagerService: DomainManagerService,
|
||||
private readonly subdomainManagerService: SubdomainManagerService,
|
||||
private readonly userService: UserService,
|
||||
) {}
|
||||
|
||||
@@ -423,8 +423,8 @@ export class SignInUpService {
|
||||
isWorkEmailFound && (await isLogoUrlValid()) ? logoUrl : undefined;
|
||||
|
||||
const workspaceToCreate = this.workspaceRepository.create({
|
||||
subdomain: await this.domainManagerService.generateSubdomain(
|
||||
isWorkEmailFound ? { email } : {},
|
||||
subdomain: await this.subdomainManagerService.generateSubdomain(
|
||||
isWorkEmailFound ? { userEmail: email } : {},
|
||||
),
|
||||
displayName: '',
|
||||
inviteHash: v4(),
|
||||
|
||||
+1
-3
@@ -8,8 +8,8 @@ import { BillingWebhookEntitlementService } from 'src/engine/core-modules/billin
|
||||
import { BillingWebhookInvoiceService } from 'src/engine/core-modules/billing-webhook/services/billing-webhook-invoice.service';
|
||||
import { BillingWebhookPriceService } from 'src/engine/core-modules/billing-webhook/services/billing-webhook-price.service';
|
||||
import { BillingWebhookProductService } from 'src/engine/core-modules/billing-webhook/services/billing-webhook-product.service';
|
||||
import { BillingWebhookSubscriptionService } from 'src/engine/core-modules/billing-webhook/services/billing-webhook-subscription.service';
|
||||
import { BillingWebhookSubscriptionScheduleService } from 'src/engine/core-modules/billing-webhook/services/billing-webhook-subscription-schedule.service';
|
||||
import { BillingWebhookSubscriptionService } from 'src/engine/core-modules/billing-webhook/services/billing-webhook-subscription.service';
|
||||
import { BillingModule } from 'src/engine/core-modules/billing/billing.module';
|
||||
import { BillingCustomer } from 'src/engine/core-modules/billing/entities/billing-customer.entity';
|
||||
import { BillingEntitlement } from 'src/engine/core-modules/billing/entities/billing-entitlement.entity';
|
||||
@@ -19,7 +19,6 @@ import { BillingProduct } from 'src/engine/core-modules/billing/entities/billing
|
||||
import { BillingSubscriptionItem } from 'src/engine/core-modules/billing/entities/billing-subscription-item.entity';
|
||||
import { BillingSubscription } from 'src/engine/core-modules/billing/entities/billing-subscription.entity';
|
||||
import { StripeModule } from 'src/engine/core-modules/billing/stripe/stripe.module';
|
||||
import { DomainManagerModule } from 'src/engine/core-modules/domain-manager/domain-manager.module';
|
||||
import { FeatureFlag } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
|
||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||
import { MessageQueueModule } from 'src/engine/core-modules/message-queue/message-queue.module';
|
||||
@@ -32,7 +31,6 @@ import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permi
|
||||
imports: [
|
||||
FeatureFlagModule,
|
||||
StripeModule,
|
||||
DomainManagerModule,
|
||||
MessageQueueModule,
|
||||
PermissionsModule,
|
||||
WorkspaceModule,
|
||||
|
||||
@@ -20,30 +20,30 @@ import { BillingFeatureUsedListener } from 'src/engine/core-modules/billing/list
|
||||
import { BillingWorkspaceMemberListener } from 'src/engine/core-modules/billing/listeners/billing-workspace-member.listener';
|
||||
import { BillingPlanService } from 'src/engine/core-modules/billing/services/billing-plan.service';
|
||||
import { BillingPortalWorkspaceService } from 'src/engine/core-modules/billing/services/billing-portal.workspace-service';
|
||||
import { BillingPriceService } from 'src/engine/core-modules/billing/services/billing-price.service';
|
||||
import { BillingProductService } from 'src/engine/core-modules/billing/services/billing-product.service';
|
||||
import { BillingSubscriptionItemService } from 'src/engine/core-modules/billing/services/billing-subscription-item.service';
|
||||
import { BillingSubscriptionPhaseService } from 'src/engine/core-modules/billing/services/billing-subscription-phase.service';
|
||||
import { BillingSubscriptionService } from 'src/engine/core-modules/billing/services/billing-subscription.service';
|
||||
import { BillingUsageService } from 'src/engine/core-modules/billing/services/billing-usage.service';
|
||||
import { BillingService } from 'src/engine/core-modules/billing/services/billing.service';
|
||||
import { StripeModule } from 'src/engine/core-modules/billing/stripe/stripe.module';
|
||||
import { DomainManagerModule } from 'src/engine/core-modules/domain-manager/domain-manager.module';
|
||||
import { WorkspaceDomainsModule } from 'src/engine/core-modules/domain/workspace-domains/workspace-domains.module';
|
||||
import { FeatureFlag } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
|
||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||
import { MessageQueueModule } from 'src/engine/core-modules/message-queue/message-queue.module';
|
||||
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
|
||||
import { BillingSubscriptionPhaseService } from 'src/engine/core-modules/billing/services/billing-subscription-phase.service';
|
||||
import { BillingPriceService } from 'src/engine/core-modules/billing/services/billing-price.service';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
FeatureFlagModule,
|
||||
StripeModule,
|
||||
DomainManagerModule,
|
||||
MessageQueueModule,
|
||||
PermissionsModule,
|
||||
AiModule,
|
||||
WorkspaceDomainsModule,
|
||||
TypeOrmModule.forFeature([
|
||||
BillingSubscription,
|
||||
BillingSubscriptionItem,
|
||||
|
||||
+8
-8
@@ -12,21 +12,21 @@ import {
|
||||
BillingException,
|
||||
BillingExceptionCode,
|
||||
} from 'src/engine/core-modules/billing/billing.exception';
|
||||
import { billingValidator } from 'src/engine/core-modules/billing/billing.validate';
|
||||
import { BillingCustomer } from 'src/engine/core-modules/billing/entities/billing-customer.entity';
|
||||
import { BillingSubscription } from 'src/engine/core-modules/billing/entities/billing-subscription.entity';
|
||||
import { BillingProductKey } from 'src/engine/core-modules/billing/enums/billing-product-key.enum';
|
||||
import { SubscriptionStatus } from 'src/engine/core-modules/billing/enums/billing-subscription-status.enum';
|
||||
import { BillingSubscriptionService } from 'src/engine/core-modules/billing/services/billing-subscription.service';
|
||||
import { StripeBillingPortalService } from 'src/engine/core-modules/billing/stripe/services/stripe-billing-portal.service';
|
||||
import { StripeCheckoutService } from 'src/engine/core-modules/billing/stripe/services/stripe-checkout.service';
|
||||
import { type BillingGetPricesPerPlanResult } from 'src/engine/core-modules/billing/types/billing-get-prices-per-plan-result.type';
|
||||
import { BillingMeterPrice } from 'src/engine/core-modules/billing/types/billing-meter-price.type';
|
||||
import { type BillingPortalCheckoutSessionParameters } from 'src/engine/core-modules/billing/types/billing-portal-checkout-session-parameters.type';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { WorkspaceDomainsService } from 'src/engine/core-modules/domain/workspace-domains/services/workspace-domains.service';
|
||||
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { assert } from 'src/utils/assert';
|
||||
import { billingValidator } from 'src/engine/core-modules/billing/billing.validate';
|
||||
import { BillingMeterPrice } from 'src/engine/core-modules/billing/types/billing-meter-price.type';
|
||||
import { BillingProductKey } from 'src/engine/core-modules/billing/enums/billing-product-key.enum';
|
||||
import { SubscriptionStatus } from 'src/engine/core-modules/billing/enums/billing-subscription-status.enum';
|
||||
|
||||
@Injectable()
|
||||
export class BillingPortalWorkspaceService {
|
||||
@@ -34,7 +34,7 @@ export class BillingPortalWorkspaceService {
|
||||
constructor(
|
||||
private readonly stripeCheckoutService: StripeCheckoutService,
|
||||
private readonly stripeBillingPortalService: StripeBillingPortalService,
|
||||
private readonly domainManagerService: DomainManagerService,
|
||||
private readonly workspaceDomainsService: WorkspaceDomainsService,
|
||||
private readonly billingSubscriptionService: BillingSubscriptionService,
|
||||
@InjectRepository(BillingSubscription)
|
||||
private readonly billingSubscriptionRepository: Repository<BillingSubscription>,
|
||||
@@ -127,7 +127,7 @@ export class BillingPortalWorkspaceService {
|
||||
billingPricesPerPlan: BillingGetPricesPerPlanResult;
|
||||
successUrlPath?: string;
|
||||
}) {
|
||||
const frontBaseUrl = this.domainManagerService.buildWorkspaceURL({
|
||||
const frontBaseUrl = this.workspaceDomainsService.buildWorkspaceURL({
|
||||
workspace,
|
||||
});
|
||||
const cancelUrl = frontBaseUrl.toString();
|
||||
@@ -182,7 +182,7 @@ export class BillingPortalWorkspaceService {
|
||||
throw new Error('Error: missing stripeCustomerId');
|
||||
}
|
||||
|
||||
const frontBaseUrl = this.domainManagerService.buildWorkspaceURL({
|
||||
const frontBaseUrl = this.workspaceDomainsService.buildWorkspaceURL({
|
||||
workspace,
|
||||
});
|
||||
|
||||
|
||||
+3
-3
@@ -5,7 +5,7 @@ import { Injectable, Logger } from '@nestjs/common';
|
||||
import type Stripe from 'stripe';
|
||||
|
||||
import { StripeSDKService } from 'src/engine/core-modules/billing/stripe/stripe-sdk/services/stripe-sdk.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 { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
|
||||
@Injectable()
|
||||
@@ -15,7 +15,7 @@ export class StripeBillingPortalService {
|
||||
|
||||
constructor(
|
||||
private readonly twentyConfigService: TwentyConfigService,
|
||||
private readonly domainManagerService: DomainManagerService,
|
||||
private readonly domainServerConfigService: DomainServerConfigService,
|
||||
private readonly stripeSDKService: StripeSDKService,
|
||||
) {
|
||||
if (!this.twentyConfigService.get('IS_BILLING_ENABLED')) {
|
||||
@@ -33,7 +33,7 @@ export class StripeBillingPortalService {
|
||||
return await this.stripe.billingPortal.sessions.create({
|
||||
customer: stripeCustomerId,
|
||||
return_url:
|
||||
returnUrl ?? this.domainManagerService.getBaseUrl().toString(),
|
||||
returnUrl ?? this.domainServerConfigService.getBaseUrl().toString(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { BillingCustomer } from 'src/engine/core-modules/billing/entities/billing-customer.entity';
|
||||
import { StripeBillingAlertService } from 'src/engine/core-modules/billing/stripe/services/stripe-billing-alert.service';
|
||||
import { StripeBillingMeterEventService } from 'src/engine/core-modules/billing/stripe/services/stripe-billing-meter-event.service';
|
||||
import { StripeBillingMeterService } from 'src/engine/core-modules/billing/stripe/services/stripe-billing-meter.service';
|
||||
import { StripeBillingPortalService } from 'src/engine/core-modules/billing/stripe/services/stripe-billing-portal.service';
|
||||
@@ -12,16 +13,15 @@ import { StripeCustomerService } from 'src/engine/core-modules/billing/stripe/se
|
||||
import { StripePriceService } from 'src/engine/core-modules/billing/stripe/services/stripe-price.service';
|
||||
import { StripeProductService } from 'src/engine/core-modules/billing/stripe/services/stripe-product.service';
|
||||
import { StripeSubscriptionItemService } from 'src/engine/core-modules/billing/stripe/services/stripe-subscription-item.service';
|
||||
import { StripeSubscriptionService } from 'src/engine/core-modules/billing/stripe/services/stripe-subscription.service';
|
||||
import { StripeSubscriptionScheduleService } from 'src/engine/core-modules/billing/stripe/services/stripe-subscription-schedule.service';
|
||||
import { StripeSubscriptionService } from 'src/engine/core-modules/billing/stripe/services/stripe-subscription.service';
|
||||
import { StripeWebhookService } from 'src/engine/core-modules/billing/stripe/services/stripe-webhook.service';
|
||||
import { StripeSDKModule } from 'src/engine/core-modules/billing/stripe/stripe-sdk/stripe-sdk.module';
|
||||
import { DomainManagerModule } from 'src/engine/core-modules/domain-manager/domain-manager.module';
|
||||
import { StripeBillingAlertService } from 'src/engine/core-modules/billing/stripe/services/stripe-billing-alert.service';
|
||||
import { DomainServerConfigModule } from 'src/engine/core-modules/domain/domain-server-config/domain-server-config.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
DomainManagerModule,
|
||||
DomainServerConfigModule,
|
||||
StripeSDKModule,
|
||||
TypeOrmModule.forFeature([BillingCustomer]),
|
||||
],
|
||||
|
||||
+2
-2
@@ -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],
|
||||
})
|
||||
|
||||
+7
-6
@@ -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);
|
||||
});
|
||||
|
||||
+3
-3
@@ -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,
|
||||
|
||||
@@ -2,17 +2,19 @@ import { Module } from '@nestjs/common';
|
||||
|
||||
import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm';
|
||||
|
||||
import { PublicDomain } from 'src/engine/core-modules/public-domain/public-domain.entity';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { PublicDomainModule } from 'src/engine/core-modules/public-domain/public-domain.module';
|
||||
import { WorkspaceModule } from 'src/engine/core-modules/workspace/workspace.module';
|
||||
import { DnsCloudflareController } from 'src/engine/core-modules/cloudflare/controllers/dns-cloudflare.controller';
|
||||
import { DnsCloudflareService } from 'src/engine/core-modules/cloudflare/services/dns-cloudflare.service';
|
||||
import { CustomDomainManagerModule } from 'src/engine/core-modules/domain/custom-domain-manager/custom-domain-manager.module';
|
||||
import { WorkspaceDomainsModule } from 'src/engine/core-modules/domain/workspace-domains/workspace-domains.module';
|
||||
import { PublicDomain } from 'src/engine/core-modules/public-domain/public-domain.entity';
|
||||
import { PublicDomainModule } from 'src/engine/core-modules/public-domain/public-domain.module';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
NestjsQueryTypeOrmModule.forFeature([PublicDomain, Workspace]),
|
||||
WorkspaceModule,
|
||||
WorkspaceDomainsModule,
|
||||
CustomDomainManagerModule,
|
||||
PublicDomainModule,
|
||||
],
|
||||
providers: [DnsCloudflareService],
|
||||
|
||||
+9
-5
@@ -2,22 +2,26 @@ import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { WorkspaceService } from 'src/engine/core-modules/workspace/services/workspace.service';
|
||||
import { CustomDomainManagerService } from 'src/engine/core-modules/domain/custom-domain-manager/services/custom-domain-manager.service';
|
||||
import { WorkspaceDomainsService } from 'src/engine/core-modules/domain/workspace-domains/services/workspace-domains.service';
|
||||
import { PublicDomainService } from 'src/engine/core-modules/public-domain/public-domain.service';
|
||||
|
||||
@Injectable()
|
||||
// eslint-disable-next-line @nx/workspace-inject-workspace-repository
|
||||
export class DnsCloudflareService {
|
||||
constructor(
|
||||
private readonly workspaceService: WorkspaceService,
|
||||
private readonly publicDomainService: PublicDomainService,
|
||||
private readonly customDomainManagerService: CustomDomainManagerService,
|
||||
private readonly workspaceDomainsService: WorkspaceDomainsService,
|
||||
) {}
|
||||
|
||||
async checkHostname(hostname: string) {
|
||||
const workspace = await this.workspaceService.findByCustomDomain(hostname);
|
||||
const workspace =
|
||||
await this.workspaceDomainsService.findByCustomDomain(hostname);
|
||||
|
||||
if (isDefined(workspace)) {
|
||||
await this.workspaceService.checkCustomDomainValidRecords(workspace);
|
||||
await this.customDomainManagerService.checkCustomDomainValidRecords(
|
||||
workspace,
|
||||
);
|
||||
}
|
||||
|
||||
const publicDomain = await this.publicDomainService.findByDomain(hostname);
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { DnsManagerService } from 'src/engine/core-modules/dns-manager/services/dns-manager.service';
|
||||
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';
|
||||
@Module({
|
||||
imports: [DomainManagerModule],
|
||||
imports: [DomainServerConfigModule],
|
||||
providers: [DnsManagerService],
|
||||
exports: [DnsManagerService],
|
||||
})
|
||||
|
||||
+12
-11
@@ -6,18 +6,18 @@ import { type CustomHostnameCreateResponse } from 'cloudflare/resources/custom-h
|
||||
import { AuditContextMock } from 'test/utils/audit-context.mock';
|
||||
|
||||
import { AuditService } from 'src/engine/core-modules/audit/services/audit.service';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { DnsManagerException } from 'src/engine/core-modules/dns-manager/exceptions/dns-manager.exception';
|
||||
import { DnsManagerService } from 'src/engine/core-modules/dns-manager/services/dns-manager.service';
|
||||
import { DomainServerConfigService } from 'src/engine/core-modules/domain/domain-server-config/services/domain-server-config.service';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DnsManagerService } from 'src/engine/core-modules/dns-manager/services/dns-manager.service';
|
||||
import { DnsManagerException } from 'src/engine/core-modules/dns-manager/exceptions/dns-manager.exception';
|
||||
|
||||
jest.mock('cloudflare');
|
||||
|
||||
describe('DnsManagerService', () => {
|
||||
let dnsManagerService: DnsManagerService;
|
||||
let twentyConfigService: TwentyConfigService;
|
||||
let domainManagerService: DomainManagerService;
|
||||
let domainServerConfigService: DomainServerConfigService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
@@ -36,7 +36,7 @@ describe('DnsManagerService', () => {
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: DomainManagerService,
|
||||
provide: DomainServerConfigService,
|
||||
useValue: {
|
||||
getBaseUrl: jest.fn(),
|
||||
getPublicDomainUrl: jest.fn(),
|
||||
@@ -53,8 +53,9 @@ describe('DnsManagerService', () => {
|
||||
|
||||
dnsManagerService = module.get<DnsManagerService>(DnsManagerService);
|
||||
twentyConfigService = module.get<TwentyConfigService>(TwentyConfigService);
|
||||
domainManagerService =
|
||||
module.get<DomainManagerService>(DomainManagerService);
|
||||
domainServerConfigService = module.get<DomainServerConfigService>(
|
||||
DomainServerConfigService,
|
||||
);
|
||||
|
||||
(dnsManagerService as any).cloudflareClient = {
|
||||
customHostnames: {
|
||||
@@ -165,7 +166,7 @@ describe('DnsManagerService', () => {
|
||||
jest.spyOn(twentyConfigService, 'get').mockReturnValue('test-zone-id');
|
||||
|
||||
jest
|
||||
.spyOn(domainManagerService, 'getBaseUrl')
|
||||
.spyOn(domainServerConfigService, 'getBaseUrl')
|
||||
.mockReturnValue(new URL('https://front.domain'));
|
||||
(dnsManagerService as any).cloudflareClient = cloudflareMock;
|
||||
|
||||
@@ -204,7 +205,7 @@ describe('DnsManagerService', () => {
|
||||
|
||||
jest.spyOn(twentyConfigService, 'get').mockReturnValue('test-zone-id');
|
||||
jest
|
||||
.spyOn(domainManagerService, 'getBaseUrl')
|
||||
.spyOn(domainServerConfigService, 'getBaseUrl')
|
||||
.mockReturnValue(new URL('https://front.domain'));
|
||||
(dnsManagerService as any).cloudflareClient = cloudflareMock;
|
||||
|
||||
@@ -245,11 +246,11 @@ describe('DnsManagerService', () => {
|
||||
|
||||
jest.spyOn(twentyConfigService, 'get').mockReturnValue('test-zone-id');
|
||||
jest
|
||||
.spyOn(domainManagerService, 'getBaseUrl')
|
||||
.spyOn(domainServerConfigService, 'getBaseUrl')
|
||||
.mockReturnValue(new URL('https://front.domain'));
|
||||
|
||||
jest
|
||||
.spyOn(domainManagerService, 'getPublicDomainUrl')
|
||||
.spyOn(domainServerConfigService, 'getPublicDomainUrl')
|
||||
.mockReturnValue(new URL('https://front.public-domain'));
|
||||
|
||||
(dnsManagerService as any).cloudflareClient = cloudflareMock;
|
||||
|
||||
+5
-5
@@ -15,7 +15,7 @@ import {
|
||||
DnsManagerExceptionCode,
|
||||
} from 'src/engine/core-modules/dns-manager/exceptions/dns-manager.exception';
|
||||
import { dnsManagerValidator } from 'src/engine/core-modules/dns-manager/validator/dns-manager.validate';
|
||||
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 { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
|
||||
type DnsManagerOptions = {
|
||||
@@ -28,7 +28,7 @@ export class DnsManagerService {
|
||||
|
||||
constructor(
|
||||
private readonly twentyConfigService: TwentyConfigService,
|
||||
private readonly domainManagerService: DomainManagerService,
|
||||
private readonly domainServerConfigService: DomainServerConfigService,
|
||||
) {
|
||||
if (this.twentyConfigService.get('CLOUDFLARE_API_KEY')) {
|
||||
this.cloudflareClient = new Cloudflare({
|
||||
@@ -61,7 +61,7 @@ export class DnsManagerService {
|
||||
): Promise<DomainValidRecords | undefined> {
|
||||
if (
|
||||
options?.isPublicDomain &&
|
||||
!isDefined(this.domainManagerService.getPublicDomainUrl().hostname)
|
||||
!isDefined(this.domainServerConfigService.getPublicDomainUrl().hostname)
|
||||
) {
|
||||
throw new DnsManagerException(
|
||||
'Missing public domain URL',
|
||||
@@ -95,8 +95,8 @@ export class DnsManagerService {
|
||||
status: statuses.redirection,
|
||||
key: hostname,
|
||||
value: options?.isPublicDomain
|
||||
? this.domainManagerService.getPublicDomainUrl().hostname
|
||||
: this.domainManagerService.getBaseUrl().hostname,
|
||||
? this.domainServerConfigService.getPublicDomainUrl().hostname
|
||||
: this.domainServerConfigService.getBaseUrl().hostname,
|
||||
},
|
||||
{
|
||||
validationType: 'ssl' as const,
|
||||
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { PublicDomain } from 'src/engine/core-modules/public-domain/public-domain.entity';
|
||||
import { AuditModule } from 'src/engine/core-modules/audit/audit.module';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([Workspace, PublicDomain]), AuditModule],
|
||||
providers: [DomainManagerService],
|
||||
exports: [DomainManagerService],
|
||||
})
|
||||
export class DomainManagerModule {}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { AuditModule } from 'src/engine/core-modules/audit/audit.module';
|
||||
import { BillingModule } from 'src/engine/core-modules/billing/billing.module';
|
||||
import { DnsManagerModule } from 'src/engine/core-modules/dns-manager/dns-manager.module';
|
||||
import { CustomDomainManagerService } from 'src/engine/core-modules/domain/custom-domain-manager/services/custom-domain-manager.service';
|
||||
import { PublicDomain } from 'src/engine/core-modules/public-domain/public-domain.entity';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([Workspace, PublicDomain]),
|
||||
DnsManagerModule,
|
||||
BillingModule,
|
||||
AuditModule,
|
||||
],
|
||||
providers: [CustomDomainManagerService],
|
||||
exports: [CustomDomainManagerService],
|
||||
})
|
||||
export class CustomDomainManagerModule {}
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { assertIsDefinedOrThrow, isDefined } from 'twenty-shared/utils';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { AuditService } from 'src/engine/core-modules/audit/services/audit.service';
|
||||
import { CUSTOM_DOMAIN_ACTIVATED_EVENT } from 'src/engine/core-modules/audit/utils/events/workspace-event/custom-domain/custom-domain-activated';
|
||||
import { CUSTOM_DOMAIN_DEACTIVATED_EVENT } from 'src/engine/core-modules/audit/utils/events/workspace-event/custom-domain/custom-domain-deactivated';
|
||||
import { BillingEntitlementKey } from 'src/engine/core-modules/billing/enums/billing-entitlement-key.enum';
|
||||
import { BillingService } from 'src/engine/core-modules/billing/services/billing.service';
|
||||
import { DomainValidRecords } from 'src/engine/core-modules/dns-manager/dtos/domain-valid-records';
|
||||
import { DnsManagerService } from 'src/engine/core-modules/dns-manager/services/dns-manager.service';
|
||||
import { PublicDomain } from 'src/engine/core-modules/public-domain/public-domain.entity';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import {
|
||||
WorkspaceException,
|
||||
WorkspaceExceptionCode,
|
||||
} from 'src/engine/core-modules/workspace/workspace.exception';
|
||||
|
||||
@Injectable()
|
||||
export class CustomDomainManagerService {
|
||||
constructor(
|
||||
@InjectRepository(Workspace)
|
||||
private readonly workspaceRepository: Repository<Workspace>,
|
||||
@InjectRepository(PublicDomain)
|
||||
private readonly publicDomainRepository: Repository<PublicDomain>,
|
||||
private readonly billingService: BillingService,
|
||||
private readonly dnsManagerService: DnsManagerService,
|
||||
private readonly auditService: AuditService,
|
||||
) {}
|
||||
|
||||
async isCustomDomainEnabled(workspaceId: string) {
|
||||
const isCustomDomainBillingEnabled =
|
||||
await this.billingService.hasEntitlement(
|
||||
workspaceId,
|
||||
BillingEntitlementKey.CUSTOM_DOMAIN,
|
||||
);
|
||||
|
||||
if (!isCustomDomainBillingEnabled) {
|
||||
throw new WorkspaceException(
|
||||
`No entitlement found for this workspace`,
|
||||
WorkspaceExceptionCode.WORKSPACE_CUSTOM_DOMAIN_DISABLED,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async setCustomDomain(workspace: Workspace, customDomain: string) {
|
||||
await this.isCustomDomainEnabled(workspace.id);
|
||||
|
||||
const existingWorkspace = await this.workspaceRepository.findOne({
|
||||
where: { customDomain },
|
||||
});
|
||||
|
||||
if (existingWorkspace && existingWorkspace.id !== workspace.id) {
|
||||
throw new WorkspaceException(
|
||||
'Domain already taken',
|
||||
WorkspaceExceptionCode.DOMAIN_ALREADY_TAKEN,
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
await this.publicDomainRepository.findOneBy({
|
||||
domain: customDomain,
|
||||
})
|
||||
) {
|
||||
throw new WorkspaceException(
|
||||
'Domain is already registered as public domain',
|
||||
WorkspaceExceptionCode.DOMAIN_ALREADY_TAKEN,
|
||||
{
|
||||
userFriendlyMessage: msg`Domain is already registered as public domain`,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if (!isDefined(customDomain) || workspace.customDomain === customDomain) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isDefined(workspace.customDomain)) {
|
||||
await this.dnsManagerService.updateHostname(
|
||||
workspace.customDomain,
|
||||
customDomain,
|
||||
);
|
||||
} else {
|
||||
await this.dnsManagerService.registerHostname(customDomain);
|
||||
}
|
||||
}
|
||||
|
||||
async checkCustomDomainValidRecords(
|
||||
workspace: Workspace,
|
||||
domainValidRecord?: DomainValidRecords,
|
||||
) {
|
||||
assertIsDefinedOrThrow(workspace.customDomain);
|
||||
|
||||
const customDomainWithRecords =
|
||||
domainValidRecord ??
|
||||
(await this.dnsManagerService.getHostnameWithRecords(
|
||||
workspace.customDomain,
|
||||
));
|
||||
|
||||
assertIsDefinedOrThrow(customDomainWithRecords);
|
||||
|
||||
const isCustomDomainWorking =
|
||||
await this.dnsManagerService.isHostnameWorking(workspace.customDomain);
|
||||
|
||||
if (workspace.isCustomDomainEnabled !== isCustomDomainWorking) {
|
||||
workspace.isCustomDomainEnabled = isCustomDomainWorking;
|
||||
|
||||
await this.workspaceRepository.save(workspace);
|
||||
|
||||
const analytics = this.auditService.createContext({
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
|
||||
analytics.insertWorkspaceEvent(
|
||||
workspace.isCustomDomainEnabled
|
||||
? CUSTOM_DOMAIN_ACTIVATED_EVENT
|
||||
: CUSTOM_DOMAIN_DEACTIVATED_EVENT,
|
||||
{},
|
||||
);
|
||||
}
|
||||
|
||||
return customDomainWithRecords;
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { DomainServerConfigService } from 'src/engine/core-modules/domain/domain-server-config/services/domain-server-config.service';
|
||||
|
||||
@Module({
|
||||
imports: [],
|
||||
providers: [DomainServerConfigService],
|
||||
exports: [DomainServerConfigService],
|
||||
})
|
||||
export class DomainServerConfigModule {}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
import { Test, type TestingModule } from '@nestjs/testing';
|
||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||
|
||||
import { DomainServerConfigService } from 'src/engine/core-modules/domain/domain-server-config/services/domain-server-config.service';
|
||||
import { PublicDomain } from 'src/engine/core-modules/public-domain/public-domain.entity';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
|
||||
describe('SubdomainManagerService', () => {
|
||||
let domainServerConfigService: DomainServerConfigService;
|
||||
let twentyConfigService: TwentyConfigService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
DomainServerConfigService,
|
||||
{
|
||||
provide: getRepositoryToken(Workspace),
|
||||
useValue: {
|
||||
find: jest.fn(),
|
||||
findOne: jest.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: getRepositoryToken(PublicDomain),
|
||||
useValue: {
|
||||
findOne: jest.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: TwentyConfigService,
|
||||
useValue: {
|
||||
get: jest.fn(),
|
||||
},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
domainServerConfigService = module.get<DomainServerConfigService>(
|
||||
DomainServerConfigService,
|
||||
);
|
||||
twentyConfigService = module.get<TwentyConfigService>(TwentyConfigService);
|
||||
});
|
||||
|
||||
describe('buildBaseUrl', () => {
|
||||
it('should build the base URL from environment variables', () => {
|
||||
jest
|
||||
.spyOn(twentyConfigService, 'get')
|
||||
.mockImplementation((key: string) => {
|
||||
const env = {
|
||||
FRONTEND_URL: 'https://example.com',
|
||||
};
|
||||
|
||||
// @ts-expect-error legacy noImplicitAny
|
||||
return env[key];
|
||||
});
|
||||
|
||||
const result = domainServerConfigService.getBaseUrl();
|
||||
|
||||
expect(result.toString()).toBe('https://example.com/');
|
||||
});
|
||||
|
||||
it('should append default subdomain if multiworkspace is enabled', () => {
|
||||
jest
|
||||
.spyOn(twentyConfigService, 'get')
|
||||
.mockImplementation((key: string) => {
|
||||
const env = {
|
||||
FRONTEND_URL: 'https://example.com',
|
||||
IS_MULTIWORKSPACE_ENABLED: true,
|
||||
DEFAULT_SUBDOMAIN: 'test',
|
||||
};
|
||||
|
||||
// @ts-expect-error legacy noImplicitAny
|
||||
return env[key];
|
||||
});
|
||||
|
||||
const result = domainServerConfigService.getBaseUrl();
|
||||
|
||||
expect(result.toString()).toBe('https://test.example.com/');
|
||||
});
|
||||
});
|
||||
});
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { buildUrlWithPathnameAndSearchParams } from 'src/engine/core-modules/domain/domain-server-config/utils/build-url-with-pathname-and-search-params.util';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
|
||||
@Injectable()
|
||||
export class DomainServerConfigService {
|
||||
constructor(private readonly twentyConfigService: TwentyConfigService) {}
|
||||
|
||||
getFrontUrl() {
|
||||
return new URL(
|
||||
this.twentyConfigService.get('FRONTEND_URL') ??
|
||||
this.twentyConfigService.get('SERVER_URL'),
|
||||
);
|
||||
}
|
||||
|
||||
getBaseUrl(): URL {
|
||||
const baseUrl = this.getFrontUrl();
|
||||
|
||||
if (
|
||||
this.twentyConfigService.get('IS_MULTIWORKSPACE_ENABLED') &&
|
||||
this.twentyConfigService.get('DEFAULT_SUBDOMAIN')
|
||||
) {
|
||||
baseUrl.hostname = `${this.twentyConfigService.get('DEFAULT_SUBDOMAIN')}.${baseUrl.hostname}`;
|
||||
}
|
||||
|
||||
return baseUrl;
|
||||
}
|
||||
|
||||
getPublicDomainUrl(): URL {
|
||||
return new URL(this.twentyConfigService.get('PUBLIC_DOMAIN_URL'));
|
||||
}
|
||||
|
||||
buildBaseUrl({
|
||||
pathname,
|
||||
searchParams,
|
||||
}: {
|
||||
pathname?: string;
|
||||
searchParams?: Record<string, string | number>;
|
||||
}) {
|
||||
return buildUrlWithPathnameAndSearchParams({
|
||||
baseUrl: this.getBaseUrl(),
|
||||
pathname,
|
||||
searchParams,
|
||||
});
|
||||
}
|
||||
|
||||
getSubdomainAndDomainFromUrl = (url: string) => {
|
||||
const { hostname: originHostname } = new URL(url);
|
||||
|
||||
const frontDomain = this.getFrontUrl().hostname;
|
||||
|
||||
const isFrontdomain = originHostname.endsWith(`.${frontDomain}`);
|
||||
|
||||
const subdomain = originHostname.replace(`.${frontDomain}`, '');
|
||||
|
||||
return {
|
||||
subdomain:
|
||||
isFrontdomain && !this.isDefaultSubdomain(subdomain)
|
||||
? subdomain
|
||||
: undefined,
|
||||
domain: isFrontdomain ? null : originHostname,
|
||||
};
|
||||
};
|
||||
|
||||
isDefaultSubdomain(subdomain: string) {
|
||||
return subdomain === this.twentyConfigService.get('DEFAULT_SUBDOMAIN');
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
export const appendSearchParamsToUrl = (
|
||||
url: URL,
|
||||
searchParams: Record<string, string | number | boolean>,
|
||||
) => {
|
||||
Object.entries(searchParams).forEach(([key, value]) => {
|
||||
url.searchParams.set(key, value.toString());
|
||||
});
|
||||
};
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
import { appendSearchParamsToUrl } from 'src/engine/core-modules/domain/domain-server-config/utils/append-search-params-to-url.util';
|
||||
|
||||
type BuildUrlWithPathnameAndSearchParamsProps = {
|
||||
baseUrl: URL;
|
||||
pathname?: string;
|
||||
searchParams?: Record<string, string | number | boolean>;
|
||||
};
|
||||
|
||||
export const buildUrlWithPathnameAndSearchParams = ({
|
||||
baseUrl,
|
||||
pathname,
|
||||
searchParams,
|
||||
}: BuildUrlWithPathnameAndSearchParamsProps) => {
|
||||
const url = baseUrl;
|
||||
|
||||
if (pathname) {
|
||||
url.pathname = pathname;
|
||||
}
|
||||
|
||||
if (searchParams) {
|
||||
appendSearchParamsToUrl(url, searchParams);
|
||||
}
|
||||
|
||||
return url;
|
||||
};
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { generateRandomSubdomain } from 'src/engine/core-modules/domain/subdomain-manager/utils/generate-random-subdomain.util';
|
||||
import { getSubdomainFromEmail } from 'src/engine/core-modules/domain/subdomain-manager/utils/get-subdomain-from-email.util';
|
||||
import { getSubdomainNameFromDisplayName } from 'src/engine/core-modules/domain/subdomain-manager/utils/get-subdomain-name-from-display-name.util';
|
||||
import { isSubdomainValid } from 'src/engine/core-modules/domain/subdomain-manager/utils/is-subdomain-valid.util';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import {
|
||||
WorkspaceException,
|
||||
WorkspaceExceptionCode,
|
||||
} from 'src/engine/core-modules/workspace/workspace.exception';
|
||||
|
||||
@Injectable()
|
||||
export class SubdomainManagerService {
|
||||
constructor(
|
||||
@InjectRepository(Workspace)
|
||||
private readonly workspaceRepository: Repository<Workspace>,
|
||||
private readonly twentyConfigService: TwentyConfigService,
|
||||
) {}
|
||||
|
||||
async generateSubdomain({
|
||||
userEmail,
|
||||
workspaceDisplayName,
|
||||
}: {
|
||||
userEmail?: string;
|
||||
workspaceDisplayName?: string;
|
||||
}) {
|
||||
const subdomainFromUserEmail = getSubdomainFromEmail(userEmail);
|
||||
const subdomainFromWorkspaceDisplayName =
|
||||
getSubdomainNameFromDisplayName(workspaceDisplayName);
|
||||
|
||||
const extractedSubdomain =
|
||||
subdomainFromUserEmail || subdomainFromWorkspaceDisplayName;
|
||||
|
||||
const isExtractedSubdomainValid = isDefined(extractedSubdomain)
|
||||
? isSubdomainValid(extractedSubdomain)
|
||||
: false;
|
||||
|
||||
const subdomain = isExtractedSubdomainValid
|
||||
? extractedSubdomain
|
||||
: generateRandomSubdomain();
|
||||
|
||||
const existingWorkspaceCount = await this.workspaceRepository.countBy({
|
||||
subdomain,
|
||||
});
|
||||
|
||||
return `${subdomain}${existingWorkspaceCount > 0 ? `-${Math.random().toString(36).substring(2, 10)}` : ''}`;
|
||||
}
|
||||
|
||||
async isSubdomainAvailable(subdomain: string) {
|
||||
const existingWorkspace = await this.workspaceRepository.findOne({
|
||||
where: { subdomain: subdomain },
|
||||
});
|
||||
|
||||
return !existingWorkspace;
|
||||
}
|
||||
|
||||
async validateSubdomainOrThrow(subdomain: string) {
|
||||
const isValid = isSubdomainValid(subdomain);
|
||||
|
||||
if (!isValid) {
|
||||
throw new WorkspaceException(
|
||||
'Subdomain is not valid',
|
||||
WorkspaceExceptionCode.SUBDOMAIN_NOT_VALID,
|
||||
);
|
||||
}
|
||||
|
||||
const isAvailable = await this.isSubdomainAvailable(subdomain);
|
||||
|
||||
if (
|
||||
!isAvailable ||
|
||||
this.twentyConfigService.get('DEFAULT_SUBDOMAIN') === subdomain
|
||||
) {
|
||||
throw new WorkspaceException(
|
||||
'Subdomain already taken',
|
||||
WorkspaceExceptionCode.SUBDOMAIN_ALREADY_TAKEN,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { SubdomainManagerService } from 'src/engine/core-modules/domain/subdomain-manager/services/subdomain-manager.service';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([Workspace])],
|
||||
providers: [SubdomainManagerService],
|
||||
exports: [SubdomainManagerService],
|
||||
})
|
||||
export class SubdomainManagerModule {}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { generateRandomSubdomain } from 'src/engine/core-modules/domain-manager/utils/generate-random-subdomain';
|
||||
import { generateRandomSubdomain } from 'src/engine/core-modules/domain/subdomain-manager/utils/generate-random-subdomain.util';
|
||||
|
||||
describe('generateRandomSubdomain', () => {
|
||||
it('should return a string in the format "prefix-color-suffix"', () => {
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { getSubdomainFromEmail } from 'src/engine/core-modules/domain-manager/utils/get-subdomain-from-email';
|
||||
import { getSubdomainFromEmail } from 'src/engine/core-modules/domain/subdomain-manager/utils/get-subdomain-from-email.util';
|
||||
|
||||
describe('getSubdomainFromEmail', () => {
|
||||
beforeEach(() => {
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { getSubdomainNameFromDisplayName } from 'src/engine/core-modules/domain-manager/utils/get-subdomain-name-from-display-name';
|
||||
import { getSubdomainNameFromDisplayName } from 'src/engine/core-modules/domain/subdomain-manager/utils/get-subdomain-name-from-display-name.util';
|
||||
|
||||
describe('getSubdomainNameFromDisplayName', () => {
|
||||
it('should return a hyphen-separated, lowercase subdomain name without spaces for a valid display name', () => {
|
||||
+384
@@ -0,0 +1,384 @@
|
||||
import { isSubdomainValid } from 'src/engine/core-modules/domain/subdomain-manager/utils/is-subdomain-valid.util';
|
||||
|
||||
describe('isSubdomainValid', () => {
|
||||
describe('valid subdomains', () => {
|
||||
it('should accept valid alphanumeric subdomains', () => {
|
||||
expect(isSubdomainValid('abc')).toBe(true);
|
||||
expect(isSubdomainValid('test123')).toBe(true);
|
||||
expect(isSubdomainValid('company1')).toBe(true);
|
||||
expect(isSubdomainValid('workspace2024')).toBe(true);
|
||||
});
|
||||
|
||||
it('should accept subdomains with hyphens in the middle', () => {
|
||||
expect(isSubdomainValid('my-company')).toBe(true);
|
||||
expect(isSubdomainValid('test-workspace')).toBe(true);
|
||||
expect(isSubdomainValid('multi-word-subdomain')).toBe(true);
|
||||
expect(isSubdomainValid('a-b-c-d-e')).toBe(true);
|
||||
});
|
||||
|
||||
it('should accept subdomains with mixed alphanumeric and hyphens', () => {
|
||||
expect(isSubdomainValid('test-123')).toBe(true);
|
||||
expect(isSubdomainValid('company-2024')).toBe(true);
|
||||
expect(isSubdomainValid('workspace-v2')).toBe(true);
|
||||
expect(isSubdomainValid('my-app-123')).toBe(true);
|
||||
});
|
||||
|
||||
it('should accept minimum length subdomains (3 characters)', () => {
|
||||
expect(isSubdomainValid('abc')).toBe(true);
|
||||
expect(isSubdomainValid('a1b')).toBe(true);
|
||||
expect(isSubdomainValid('x-y')).toBe(true);
|
||||
});
|
||||
|
||||
it('should accept maximum length subdomains (30 characters)', () => {
|
||||
const maxLengthSubdomain = 'a'.repeat(28) + 'bc'; // 30 characters total
|
||||
|
||||
expect(isSubdomainValid(maxLengthSubdomain)).toBe(true);
|
||||
|
||||
const maxLengthWithHyphens = 'a' + '-'.repeat(28) + 'b'; // 30 characters with hyphens
|
||||
|
||||
expect(isSubdomainValid(maxLengthWithHyphens)).toBe(true);
|
||||
});
|
||||
|
||||
it('should accept subdomains starting and ending with alphanumeric characters', () => {
|
||||
expect(isSubdomainValid('a-b')).toBe(true);
|
||||
expect(isSubdomainValid('1-test-2')).toBe(true);
|
||||
expect(isSubdomainValid('start-middle-end')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('invalid subdomain patterns', () => {
|
||||
it('should reject empty strings', () => {
|
||||
expect(isSubdomainValid('')).toBe(false);
|
||||
});
|
||||
|
||||
it('should reject subdomains that are too short (less than 3 characters)', () => {
|
||||
expect(isSubdomainValid('a')).toBe(false);
|
||||
expect(isSubdomainValid('ab')).toBe(false);
|
||||
expect(isSubdomainValid('1')).toBe(false);
|
||||
expect(isSubdomainValid('12')).toBe(false);
|
||||
});
|
||||
|
||||
it('should reject subdomains that are too long (more than 30 characters)', () => {
|
||||
const tooLongSubdomain = 'a'.repeat(31);
|
||||
|
||||
expect(isSubdomainValid(tooLongSubdomain)).toBe(false);
|
||||
|
||||
const wayTooLongSubdomain = 'a'.repeat(50);
|
||||
|
||||
expect(isSubdomainValid(wayTooLongSubdomain)).toBe(false);
|
||||
});
|
||||
|
||||
it('should reject subdomains starting with hyphens', () => {
|
||||
expect(isSubdomainValid('-test')).toBe(false);
|
||||
expect(isSubdomainValid('-abc')).toBe(false);
|
||||
expect(isSubdomainValid('-my-company')).toBe(false);
|
||||
});
|
||||
|
||||
it('should reject subdomains ending with hyphens', () => {
|
||||
expect(isSubdomainValid('test-')).toBe(false);
|
||||
expect(isSubdomainValid('abc-')).toBe(false);
|
||||
expect(isSubdomainValid('my-company-')).toBe(false);
|
||||
});
|
||||
|
||||
it('should reject subdomains with uppercase letters', () => {
|
||||
expect(isSubdomainValid('Test')).toBe(false);
|
||||
expect(isSubdomainValid('MyCompany')).toBe(false);
|
||||
expect(isSubdomainValid('WORKSPACE')).toBe(false);
|
||||
expect(isSubdomainValid('test-Company')).toBe(false);
|
||||
});
|
||||
|
||||
it('should reject subdomains with special characters', () => {
|
||||
expect(isSubdomainValid('test@company')).toBe(false);
|
||||
expect(isSubdomainValid('my_workspace')).toBe(false);
|
||||
expect(isSubdomainValid('test.company')).toBe(false);
|
||||
expect(isSubdomainValid('workspace#1')).toBe(false);
|
||||
expect(isSubdomainValid('test$company')).toBe(false);
|
||||
expect(isSubdomainValid('my%workspace')).toBe(false);
|
||||
expect(isSubdomainValid('test&company')).toBe(false);
|
||||
expect(isSubdomainValid('workspace*1')).toBe(false);
|
||||
expect(isSubdomainValid('test+company')).toBe(false);
|
||||
expect(isSubdomainValid('my=workspace')).toBe(false);
|
||||
});
|
||||
|
||||
it('should reject subdomains with spaces', () => {
|
||||
expect(isSubdomainValid('test company')).toBe(false);
|
||||
expect(isSubdomainValid('my workspace')).toBe(false);
|
||||
expect(isSubdomainValid(' test')).toBe(false);
|
||||
expect(isSubdomainValid('test ')).toBe(false);
|
||||
expect(isSubdomainValid(' ')).toBe(false);
|
||||
});
|
||||
|
||||
it('should reject subdomains starting with "api-"', () => {
|
||||
expect(isSubdomainValid('api-test')).toBe(false);
|
||||
expect(isSubdomainValid('api-company')).toBe(false);
|
||||
expect(isSubdomainValid('api-workspace')).toBe(false);
|
||||
expect(isSubdomainValid('api-123')).toBe(false);
|
||||
});
|
||||
|
||||
it('should reject subdomains with only hyphens', () => {
|
||||
expect(isSubdomainValid('---')).toBe(false);
|
||||
expect(isSubdomainValid('----')).toBe(false);
|
||||
});
|
||||
|
||||
it('should reject subdomains with numbers only at boundaries but invalid patterns', () => {
|
||||
expect(isSubdomainValid('1-')).toBe(false);
|
||||
expect(isSubdomainValid('-1')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('reserved subdomains', () => {
|
||||
it('should reject common reserved subdomains', () => {
|
||||
expect(isSubdomainValid('api')).toBe(false);
|
||||
expect(isSubdomainValid('www')).toBe(false);
|
||||
expect(isSubdomainValid('admin')).toBe(false);
|
||||
expect(isSubdomainValid('dashboard')).toBe(false);
|
||||
expect(isSubdomainValid('billing')).toBe(false);
|
||||
expect(isSubdomainValid('support')).toBe(false);
|
||||
});
|
||||
|
||||
it('should reject technical reserved subdomains', () => {
|
||||
expect(isSubdomainValid('db')).toBe(false);
|
||||
expect(isSubdomainValid('cdn')).toBe(false);
|
||||
expect(isSubdomainValid('storage')).toBe(false);
|
||||
expect(isSubdomainValid('files')).toBe(false);
|
||||
expect(isSubdomainValid('media')).toBe(false);
|
||||
expect(isSubdomainValid('assets')).toBe(false);
|
||||
});
|
||||
|
||||
it('should reject authentication related reserved subdomains', () => {
|
||||
expect(isSubdomainValid('auth')).toBe(false);
|
||||
expect(isSubdomainValid('login')).toBe(false);
|
||||
expect(isSubdomainValid('signin')).toBe(false);
|
||||
expect(isSubdomainValid('signup')).toBe(false);
|
||||
expect(isSubdomainValid('register')).toBe(false);
|
||||
});
|
||||
|
||||
it('should reject business related reserved subdomains', () => {
|
||||
expect(isSubdomainValid('about')).toBe(false);
|
||||
expect(isSubdomainValid('contact')).toBe(false);
|
||||
expect(isSubdomainValid('careers')).toBe(false);
|
||||
expect(isSubdomainValid('jobs')).toBe(false);
|
||||
expect(isSubdomainValid('blog')).toBe(false);
|
||||
expect(isSubdomainValid('news')).toBe(false);
|
||||
});
|
||||
|
||||
it('should reject country code reserved subdomains', () => {
|
||||
expect(isSubdomainValid('us')).toBe(false);
|
||||
expect(isSubdomainValid('uk')).toBe(false);
|
||||
expect(isSubdomainValid('ca')).toBe(false);
|
||||
expect(isSubdomainValid('au')).toBe(false);
|
||||
expect(isSubdomainValid('de')).toBe(false);
|
||||
expect(isSubdomainValid('fr')).toBe(false);
|
||||
expect(isSubdomainValid('it')).toBe(false);
|
||||
expect(isSubdomainValid('es')).toBe(false);
|
||||
});
|
||||
|
||||
it('should reject geographic reserved subdomains', () => {
|
||||
expect(isSubdomainValid('europe')).toBe(false);
|
||||
expect(isSubdomainValid('asia')).toBe(false);
|
||||
expect(isSubdomainValid('africa')).toBe(false);
|
||||
expect(isSubdomainValid('america')).toBe(false);
|
||||
expect(isSubdomainValid('oceania')).toBe(false);
|
||||
expect(isSubdomainValid('paris')).toBe(false);
|
||||
expect(isSubdomainValid('london')).toBe(false);
|
||||
expect(isSubdomainValid('new-york')).toBe(false);
|
||||
});
|
||||
|
||||
it('should reject environment related reserved subdomains', () => {
|
||||
expect(isSubdomainValid('dev')).toBe(false);
|
||||
expect(isSubdomainValid('test')).toBe(false);
|
||||
expect(isSubdomainValid('testing')).toBe(false);
|
||||
expect(isSubdomainValid('staging')).toBe(false);
|
||||
expect(isSubdomainValid('production')).toBe(false);
|
||||
});
|
||||
|
||||
it('should reject reserved subdomains case-insensitively', () => {
|
||||
expect(isSubdomainValid('API')).toBe(false);
|
||||
expect(isSubdomainValid('Api')).toBe(false);
|
||||
expect(isSubdomainValid('WWW')).toBe(false);
|
||||
expect(isSubdomainValid('Www')).toBe(false);
|
||||
expect(isSubdomainValid('ADMIN')).toBe(false);
|
||||
expect(isSubdomainValid('Admin')).toBe(false);
|
||||
expect(isSubdomainValid('TEST')).toBe(false);
|
||||
expect(isSubdomainValid('Test')).toBe(false);
|
||||
});
|
||||
|
||||
it('should reject all reserved subdomains from the constant', () => {
|
||||
const reservedSubdomains = [
|
||||
'trust',
|
||||
'demo',
|
||||
'api',
|
||||
't',
|
||||
'companies',
|
||||
'telemetry',
|
||||
'logs',
|
||||
'metrics',
|
||||
'next',
|
||||
'main',
|
||||
'admin',
|
||||
'dashboard',
|
||||
'dash',
|
||||
'billing',
|
||||
'db',
|
||||
'favicon',
|
||||
'www',
|
||||
'mail',
|
||||
'docs',
|
||||
'dev',
|
||||
'app',
|
||||
'staging',
|
||||
'production',
|
||||
'developer',
|
||||
'files',
|
||||
'cdn',
|
||||
'storage',
|
||||
'about',
|
||||
'help',
|
||||
'support',
|
||||
'contact',
|
||||
'privacy',
|
||||
'terms',
|
||||
'careers',
|
||||
'jobs',
|
||||
'blog',
|
||||
'news',
|
||||
'events',
|
||||
'community',
|
||||
'forum',
|
||||
'chat',
|
||||
'test',
|
||||
'testing',
|
||||
'feedback',
|
||||
'config',
|
||||
'settings',
|
||||
'media',
|
||||
'image',
|
||||
'audio',
|
||||
'video',
|
||||
'images',
|
||||
'partners',
|
||||
'partnership',
|
||||
'partnerships',
|
||||
'assets',
|
||||
'login',
|
||||
'signin',
|
||||
'signup',
|
||||
'legal',
|
||||
'shop',
|
||||
'merch',
|
||||
'store',
|
||||
'auth',
|
||||
'register',
|
||||
'payment',
|
||||
];
|
||||
|
||||
reservedSubdomains.forEach((subdomain) => {
|
||||
expect(isSubdomainValid(subdomain)).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('edge cases', () => {
|
||||
it('should handle whitespace-only strings', () => {
|
||||
expect(isSubdomainValid(' ')).toBe(false);
|
||||
expect(isSubdomainValid('\t')).toBe(false);
|
||||
expect(isSubdomainValid('\n')).toBe(false);
|
||||
expect(isSubdomainValid('\r')).toBe(false);
|
||||
});
|
||||
|
||||
it('should handle strings with leading/trailing whitespace', () => {
|
||||
expect(isSubdomainValid(' test')).toBe(false);
|
||||
expect(isSubdomainValid('test ')).toBe(false);
|
||||
expect(isSubdomainValid(' test ')).toBe(false);
|
||||
expect(isSubdomainValid('\ttest\t')).toBe(false);
|
||||
});
|
||||
|
||||
it('should handle boundary length cases precisely', () => {
|
||||
// Exactly 3 characters (minimum valid)
|
||||
expect(isSubdomainValid('abc')).toBe(true);
|
||||
|
||||
// Exactly 30 characters (maximum valid)
|
||||
const exactly30Chars = 'a'.repeat(28) + 'bc';
|
||||
|
||||
expect(exactly30Chars.length).toBe(30);
|
||||
expect(isSubdomainValid(exactly30Chars)).toBe(true);
|
||||
|
||||
// Exactly 31 characters (first invalid length)
|
||||
const exactly31Chars = 'a'.repeat(29) + 'bc';
|
||||
|
||||
expect(exactly31Chars.length).toBe(31);
|
||||
expect(isSubdomainValid(exactly31Chars)).toBe(false);
|
||||
});
|
||||
|
||||
it('should validate that reserved subdomains check is case insensitive', () => {
|
||||
// Test mixed case variations of reserved subdomains
|
||||
expect(isSubdomainValid('Trust')).toBe(false);
|
||||
expect(isSubdomainValid('TRUST')).toBe(false);
|
||||
expect(isSubdomainValid('tRuSt')).toBe(false);
|
||||
expect(isSubdomainValid('Demo')).toBe(false);
|
||||
expect(isSubdomainValid('DEMO')).toBe(false);
|
||||
expect(isSubdomainValid('dEmO')).toBe(false);
|
||||
});
|
||||
|
||||
it('should accept valid subdomains that are similar to reserved ones but not exact matches', () => {
|
||||
// 'testing' is reserved, but 'testing123' is not
|
||||
expect(isSubdomainValid('testing123')).toBe(true);
|
||||
// 'api' is reserved, but 'myapi' is not
|
||||
expect(isSubdomainValid('myapi')).toBe(true);
|
||||
// 'admin' is reserved, but 'adminpanel' is not
|
||||
expect(isSubdomainValid('adminpanel')).toBe(true);
|
||||
// 'test' is reserved, but 'testapp' is not
|
||||
expect(isSubdomainValid('testapp')).toBe(true);
|
||||
});
|
||||
|
||||
it('should handle Unicode characters', () => {
|
||||
expect(isSubdomainValid('tëst')).toBe(false);
|
||||
expect(isSubdomainValid('tést')).toBe(false);
|
||||
expect(isSubdomainValid('tèst')).toBe(false);
|
||||
expect(isSubdomainValid('café')).toBe(false);
|
||||
expect(isSubdomainValid('naïve')).toBe(false);
|
||||
});
|
||||
|
||||
it('should handle numeric strings', () => {
|
||||
expect(isSubdomainValid('123')).toBe(true);
|
||||
expect(isSubdomainValid('456789')).toBe(true);
|
||||
expect(isSubdomainValid('1-2-3')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('pattern validation specifics', () => {
|
||||
it('should enforce the exact regex pattern requirements', () => {
|
||||
// Test that the pattern requires alphanumeric start and end
|
||||
expect(isSubdomainValid('a-b')).toBe(true);
|
||||
expect(isSubdomainValid('1-2')).toBe(true);
|
||||
expect(isSubdomainValid('test-123')).toBe(true);
|
||||
|
||||
// Test that it rejects patterns not matching the regex
|
||||
expect(isSubdomainValid('-ab')).toBe(false);
|
||||
expect(isSubdomainValid('ab-')).toBe(false);
|
||||
});
|
||||
|
||||
it('should reject api- prefix specifically', () => {
|
||||
expect(isSubdomainValid('api-anything')).toBe(false);
|
||||
expect(isSubdomainValid('api-test')).toBe(false);
|
||||
expect(isSubdomainValid('api-123')).toBe(false);
|
||||
|
||||
// But allow 'api' in other positions
|
||||
expect(isSubdomainValid('myapi')).toBe(true);
|
||||
});
|
||||
|
||||
it('should validate length constraints from regex', () => {
|
||||
// The regex pattern is: /^(?!api-).*^[a-z0-9][a-z0-9-]{1,28}[a-z0-9]$/
|
||||
// This means: start char + 1-28 middle chars + end char = 3-30 total chars
|
||||
|
||||
// 3 chars: start + 1 middle + end
|
||||
expect(isSubdomainValid('abc')).toBe(true);
|
||||
|
||||
// 30 chars: start + 28 middle + end
|
||||
const thirtyChars = 'a' + 'b'.repeat(28) + 'c';
|
||||
|
||||
expect(thirtyChars.length).toBe(30);
|
||||
expect(isSubdomainValid(thirtyChars)).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
+1
-1
@@ -8,5 +8,5 @@ export const getSubdomainFromEmail = (email?: string) => {
|
||||
|
||||
const domain = getDomainNameByEmail(email);
|
||||
|
||||
return domain.split('.')[0];
|
||||
return domain.split('.')[0].toLowerCase();
|
||||
};
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
import { RESERVED_SUBDOMAINS } from 'src/engine/core-modules/workspace/constants/reserved-subdomains.constant';
|
||||
import { VALID_SUBDOMAIN_PATTERN } from 'src/engine/core-modules/workspace/constants/valid-subdomain-pattern.constant';
|
||||
|
||||
export const isSubdomainValid = (subdomain: string) => {
|
||||
return (
|
||||
VALID_SUBDOMAIN_PATTERN.test(subdomain) &&
|
||||
!RESERVED_SUBDOMAINS.includes(subdomain.toLowerCase())
|
||||
);
|
||||
};
|
||||
+41
-75
@@ -3,64 +3,14 @@ import { getRepositoryToken } from '@nestjs/typeorm';
|
||||
|
||||
import { type Repository } from 'typeorm';
|
||||
|
||||
import { DomainServerConfigService } from 'src/engine/core-modules/domain/domain-server-config/services/domain-server-config.service';
|
||||
import { WorkspaceDomainsService } from 'src/engine/core-modules/domain/workspace-domains/services/workspace-domains.service';
|
||||
import { PublicDomain } from 'src/engine/core-modules/public-domain/public-domain.entity';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { PublicDomain } from 'src/engine/core-modules/public-domain/public-domain.entity';
|
||||
|
||||
import { DomainManagerService } from './domain-manager.service';
|
||||
|
||||
describe('DomainManagerService', () => {
|
||||
describe('getWorkspaceUrls', () => {
|
||||
it('should return a URL containing the correct customDomain if customDomain is provided', () => {
|
||||
jest
|
||||
.spyOn(twentyConfigService, 'get')
|
||||
.mockImplementation((key: string) => {
|
||||
const env = {
|
||||
FRONTEND_URL: 'https://example.com',
|
||||
};
|
||||
|
||||
// @ts-expect-error legacy noImplicitAny
|
||||
return env[key];
|
||||
});
|
||||
|
||||
const result = domainManagerService.getWorkspaceUrls({
|
||||
subdomain: 'subdomain',
|
||||
customDomain: 'custom-host.com',
|
||||
isCustomDomainEnabled: true,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
customUrl: 'https://custom-host.com/',
|
||||
subdomainUrl: 'https://example.com/',
|
||||
});
|
||||
});
|
||||
|
||||
it('should return a URL containing the correct subdomain if customDomain is not provided but subdomain is', () => {
|
||||
jest
|
||||
.spyOn(twentyConfigService, 'get')
|
||||
.mockImplementation((key: string) => {
|
||||
const env = {
|
||||
FRONTEND_URL: 'https://example.com',
|
||||
IS_MULTIWORKSPACE_ENABLED: true,
|
||||
};
|
||||
|
||||
// @ts-expect-error legacy noImplicitAny
|
||||
return env[key];
|
||||
});
|
||||
|
||||
const result = domainManagerService.getWorkspaceUrls({
|
||||
subdomain: 'subdomain',
|
||||
customDomain: null,
|
||||
isCustomDomainEnabled: false,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
customUrl: undefined,
|
||||
subdomainUrl: 'https://subdomain.example.com/',
|
||||
});
|
||||
});
|
||||
});
|
||||
let domainManagerService: DomainManagerService;
|
||||
describe('WorkspaceDomainsService', () => {
|
||||
let workspaceDomainsService: WorkspaceDomainsService;
|
||||
let twentyConfigService: TwentyConfigService;
|
||||
let workspaceRepository: Repository<Workspace>;
|
||||
let publicDomainRepository: Repository<PublicDomain>;
|
||||
@@ -68,7 +18,8 @@ describe('DomainManagerService', () => {
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
DomainManagerService,
|
||||
DomainServerConfigService,
|
||||
WorkspaceDomainsService,
|
||||
{
|
||||
provide: getRepositoryToken(Workspace),
|
||||
useValue: {
|
||||
@@ -97,13 +48,15 @@ describe('DomainManagerService', () => {
|
||||
publicDomainRepository = module.get<Repository<PublicDomain>>(
|
||||
getRepositoryToken(PublicDomain),
|
||||
);
|
||||
domainManagerService =
|
||||
module.get<DomainManagerService>(DomainManagerService);
|
||||
workspaceDomainsService = module.get<WorkspaceDomainsService>(
|
||||
WorkspaceDomainsService,
|
||||
);
|
||||
|
||||
twentyConfigService = module.get<TwentyConfigService>(TwentyConfigService);
|
||||
});
|
||||
|
||||
describe('buildBaseUrl', () => {
|
||||
it('should build the base URL from environment variables', () => {
|
||||
describe('getWorkspaceUrls', () => {
|
||||
it('should return a URL containing the correct customDomain if customDomain is provided', () => {
|
||||
jest
|
||||
.spyOn(twentyConfigService, 'get')
|
||||
.mockImplementation((key: string) => {
|
||||
@@ -115,28 +68,41 @@ describe('DomainManagerService', () => {
|
||||
return env[key];
|
||||
});
|
||||
|
||||
const result = domainManagerService.getBaseUrl();
|
||||
const result = workspaceDomainsService.getWorkspaceUrls({
|
||||
subdomain: 'subdomain',
|
||||
customDomain: 'custom-host.com',
|
||||
isCustomDomainEnabled: true,
|
||||
});
|
||||
|
||||
expect(result.toString()).toBe('https://example.com/');
|
||||
expect(result).toEqual({
|
||||
customUrl: 'https://custom-host.com/',
|
||||
subdomainUrl: 'https://example.com/',
|
||||
});
|
||||
});
|
||||
|
||||
it('should append default subdomain if multiworkspace is enabled', () => {
|
||||
it('should return a URL containing the correct subdomain if customDomain is not provided but subdomain is', () => {
|
||||
jest
|
||||
.spyOn(twentyConfigService, 'get')
|
||||
.mockImplementation((key: string) => {
|
||||
const env = {
|
||||
FRONTEND_URL: 'https://example.com',
|
||||
IS_MULTIWORKSPACE_ENABLED: true,
|
||||
DEFAULT_SUBDOMAIN: 'test',
|
||||
};
|
||||
|
||||
// @ts-expect-error legacy noImplicitAny
|
||||
return env[key];
|
||||
});
|
||||
|
||||
const result = domainManagerService.getBaseUrl();
|
||||
const result = workspaceDomainsService.getWorkspaceUrls({
|
||||
subdomain: 'subdomain',
|
||||
customDomain: null,
|
||||
isCustomDomainEnabled: false,
|
||||
});
|
||||
|
||||
expect(result.toString()).toBe('https://test.example.com/');
|
||||
expect(result).toEqual({
|
||||
customUrl: undefined,
|
||||
subdomainUrl: 'https://subdomain.example.com/',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -155,7 +121,7 @@ describe('DomainManagerService', () => {
|
||||
return env[key];
|
||||
});
|
||||
|
||||
const result = domainManagerService.buildWorkspaceURL({
|
||||
const result = workspaceDomainsService.buildWorkspaceURL({
|
||||
workspace: {
|
||||
subdomain: 'test',
|
||||
customDomain: null,
|
||||
@@ -178,7 +144,7 @@ describe('DomainManagerService', () => {
|
||||
return env[key];
|
||||
});
|
||||
|
||||
const result = domainManagerService.buildWorkspaceURL({
|
||||
const result = workspaceDomainsService.buildWorkspaceURL({
|
||||
workspace: {
|
||||
subdomain: 'test',
|
||||
customDomain: null,
|
||||
@@ -202,7 +168,7 @@ describe('DomainManagerService', () => {
|
||||
return env[key];
|
||||
});
|
||||
|
||||
const result = domainManagerService.buildWorkspaceURL({
|
||||
const result = workspaceDomainsService.buildWorkspaceURL({
|
||||
workspace: {
|
||||
subdomain: 'test',
|
||||
customDomain: null,
|
||||
@@ -240,7 +206,7 @@ describe('DomainManagerService', () => {
|
||||
] as unknown as Workspace[]);
|
||||
|
||||
const result =
|
||||
await domainManagerService.getWorkspaceByOriginOrDefaultWorkspace(
|
||||
await workspaceDomainsService.getWorkspaceByOriginOrDefaultWorkspace(
|
||||
'https://example.com',
|
||||
);
|
||||
|
||||
@@ -270,7 +236,7 @@ describe('DomainManagerService', () => {
|
||||
] as unknown as Workspace[]);
|
||||
|
||||
const result =
|
||||
await domainManagerService.getWorkspaceByOriginOrDefaultWorkspace(
|
||||
await workspaceDomainsService.getWorkspaceByOriginOrDefaultWorkspace(
|
||||
'https://example.com',
|
||||
);
|
||||
|
||||
@@ -296,7 +262,7 @@ describe('DomainManagerService', () => {
|
||||
} as unknown as Promise<Workspace>);
|
||||
|
||||
const result =
|
||||
await domainManagerService.getWorkspaceByOriginOrDefaultWorkspace(
|
||||
await workspaceDomainsService.getWorkspaceByOriginOrDefaultWorkspace(
|
||||
'https://123.example.com',
|
||||
);
|
||||
|
||||
@@ -322,7 +288,7 @@ describe('DomainManagerService', () => {
|
||||
} as unknown as Promise<Workspace>);
|
||||
|
||||
const result =
|
||||
await domainManagerService.getWorkspaceByOriginOrDefaultWorkspace(
|
||||
await workspaceDomainsService.getWorkspaceByOriginOrDefaultWorkspace(
|
||||
'https://123.custom.com',
|
||||
);
|
||||
|
||||
@@ -352,7 +318,7 @@ describe('DomainManagerService', () => {
|
||||
} as unknown as Promise<PublicDomain>);
|
||||
|
||||
const result =
|
||||
await domainManagerService.getWorkspaceByOriginOrDefaultWorkspace(
|
||||
await workspaceDomainsService.getWorkspaceByOriginOrDefaultWorkspace(
|
||||
'https://123.custom.com',
|
||||
);
|
||||
|
||||
@@ -377,7 +343,7 @@ describe('DomainManagerService', () => {
|
||||
jest.spyOn(publicDomainRepository, 'findOne').mockResolvedValueOnce(null);
|
||||
|
||||
const result =
|
||||
await domainManagerService.getWorkspaceByOriginOrDefaultWorkspace(
|
||||
await workspaceDomainsService.getWorkspaceByOriginOrDefaultWorkspace(
|
||||
'https://123.custom.com',
|
||||
);
|
||||
|
||||
+25
-120
@@ -4,127 +4,48 @@ import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { assertIsDefinedOrThrow, isDefined } from 'twenty-shared/utils';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { type WorkspaceSubdomainCustomDomainAndIsCustomDomainEnabledType } from 'src/engine/core-modules/domain-manager/domain-manager.type';
|
||||
import { generateRandomSubdomain } from 'src/engine/core-modules/domain-manager/utils/generate-random-subdomain';
|
||||
import { getSubdomainFromEmail } from 'src/engine/core-modules/domain-manager/utils/get-subdomain-from-email';
|
||||
import { getSubdomainNameFromDisplayName } from 'src/engine/core-modules/domain-manager/utils/get-subdomain-name-from-display-name';
|
||||
import { DomainServerConfigService } from 'src/engine/core-modules/domain/domain-server-config/services/domain-server-config.service';
|
||||
import { buildUrlWithPathnameAndSearchParams } from 'src/engine/core-modules/domain/domain-server-config/utils/build-url-with-pathname-and-search-params.util';
|
||||
import { WorkspaceDomainConfig } from 'src/engine/core-modules/domain/workspace-domains/types/workspace-domain-config.type';
|
||||
import { PublicDomain } from 'src/engine/core-modules/public-domain/public-domain.entity';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { PublicDomain } from 'src/engine/core-modules/public-domain/public-domain.entity';
|
||||
import { WorkspaceNotFoundDefaultError } from 'src/engine/core-modules/workspace/workspace.exception';
|
||||
|
||||
@Injectable()
|
||||
export class DomainManagerService {
|
||||
export class WorkspaceDomainsService {
|
||||
constructor(
|
||||
private readonly domainServerConfigService: DomainServerConfigService,
|
||||
private readonly twentyConfigService: TwentyConfigService,
|
||||
@InjectRepository(Workspace)
|
||||
private readonly workspaceRepository: Repository<Workspace>,
|
||||
@InjectRepository(PublicDomain)
|
||||
private readonly publicDomainRepository: Repository<PublicDomain>,
|
||||
private readonly twentyConfigService: TwentyConfigService,
|
||||
) {}
|
||||
|
||||
getFrontUrl() {
|
||||
return new URL(
|
||||
this.twentyConfigService.get('FRONTEND_URL') ??
|
||||
this.twentyConfigService.get('SERVER_URL'),
|
||||
);
|
||||
}
|
||||
|
||||
getBaseUrl(): URL {
|
||||
const baseUrl = this.getFrontUrl();
|
||||
|
||||
if (
|
||||
this.twentyConfigService.get('IS_MULTIWORKSPACE_ENABLED') &&
|
||||
this.twentyConfigService.get('DEFAULT_SUBDOMAIN')
|
||||
) {
|
||||
baseUrl.hostname = `${this.twentyConfigService.get('DEFAULT_SUBDOMAIN')}.${baseUrl.hostname}`;
|
||||
}
|
||||
|
||||
return baseUrl;
|
||||
}
|
||||
|
||||
getPublicDomainUrl(): URL {
|
||||
return new URL(this.twentyConfigService.get('PUBLIC_DOMAIN_URL'));
|
||||
}
|
||||
|
||||
private appendSearchParams(
|
||||
url: URL,
|
||||
searchParams: Record<string, string | number | boolean>,
|
||||
) {
|
||||
Object.entries(searchParams).forEach(([key, value]) => {
|
||||
url.searchParams.set(key, value.toString());
|
||||
});
|
||||
}
|
||||
|
||||
buildBaseUrl({
|
||||
pathname,
|
||||
searchParams,
|
||||
}: {
|
||||
pathname?: string;
|
||||
searchParams?: Record<string, string | number>;
|
||||
}) {
|
||||
const url = this.getBaseUrl();
|
||||
|
||||
if (pathname) {
|
||||
url.pathname = pathname;
|
||||
}
|
||||
|
||||
if (searchParams) {
|
||||
this.appendSearchParams(url, searchParams);
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
buildWorkspaceURL({
|
||||
workspace,
|
||||
pathname,
|
||||
searchParams,
|
||||
}: {
|
||||
workspace: WorkspaceSubdomainCustomDomainAndIsCustomDomainEnabledType;
|
||||
workspace: WorkspaceDomainConfig;
|
||||
pathname?: string;
|
||||
searchParams?: Record<string, string | number | boolean>;
|
||||
}) {
|
||||
const workspaceUrls = this.getWorkspaceUrls(workspace);
|
||||
|
||||
const url = new URL(workspaceUrls.customUrl ?? workspaceUrls.subdomainUrl);
|
||||
|
||||
if (pathname) {
|
||||
url.pathname = pathname;
|
||||
}
|
||||
|
||||
if (searchParams) {
|
||||
this.appendSearchParams(url, searchParams);
|
||||
}
|
||||
const url = buildUrlWithPathnameAndSearchParams({
|
||||
baseUrl: new URL(workspaceUrls.customUrl ?? workspaceUrls.subdomainUrl),
|
||||
pathname,
|
||||
searchParams,
|
||||
});
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
getSubdomainAndDomainFromUrl = (url: string) => {
|
||||
const { hostname: originHostname } = new URL(url);
|
||||
|
||||
const frontDomain = this.getFrontUrl().hostname;
|
||||
|
||||
const isFrontdomain = originHostname.endsWith(`.${frontDomain}`);
|
||||
|
||||
const subdomain = originHostname.replace(`.${frontDomain}`, '');
|
||||
|
||||
return {
|
||||
subdomain:
|
||||
isFrontdomain && !this.isDefaultSubdomain(subdomain)
|
||||
? subdomain
|
||||
: undefined,
|
||||
domain: isFrontdomain ? null : originHostname,
|
||||
};
|
||||
};
|
||||
|
||||
isDefaultSubdomain(subdomain: string) {
|
||||
return subdomain === this.twentyConfigService.get('DEFAULT_SUBDOMAIN');
|
||||
}
|
||||
|
||||
computeRedirectErrorUrl(
|
||||
computeWorkspaceRedirectErrorUrl(
|
||||
errorMessage: string,
|
||||
workspace: WorkspaceSubdomainCustomDomainAndIsCustomDomainEnabledType,
|
||||
workspace: WorkspaceDomainConfig,
|
||||
pathname: string,
|
||||
) {
|
||||
const url = this.buildWorkspaceURL({
|
||||
@@ -168,7 +89,8 @@ export class DomainManagerService {
|
||||
return this.getDefaultWorkspace();
|
||||
}
|
||||
|
||||
const { subdomain, domain } = this.getSubdomainAndDomainFromUrl(origin);
|
||||
const { subdomain, domain } =
|
||||
this.domainServerConfigService.getSubdomainAndDomainFromUrl(origin);
|
||||
|
||||
if (!domain && !subdomain) return;
|
||||
|
||||
@@ -195,29 +117,8 @@ export class DomainManagerService {
|
||||
return publicDomainFromCustomDomain?.workspace;
|
||||
}
|
||||
|
||||
private extractSubdomain(params?: { email?: string; displayName?: string }) {
|
||||
if (params?.email) {
|
||||
return getSubdomainFromEmail(params.email);
|
||||
}
|
||||
|
||||
if (params?.displayName) {
|
||||
return getSubdomainNameFromDisplayName(params.displayName);
|
||||
}
|
||||
}
|
||||
|
||||
async generateSubdomain(params?: { email?: string; displayName?: string }) {
|
||||
const subdomain =
|
||||
this.extractSubdomain(params) ?? generateRandomSubdomain();
|
||||
|
||||
const existingWorkspaceCount = await this.workspaceRepository.countBy({
|
||||
subdomain,
|
||||
});
|
||||
|
||||
return `${subdomain}${existingWorkspaceCount > 0 ? `-${Math.random().toString(36).substring(2, 10)}` : ''}`;
|
||||
}
|
||||
|
||||
private getCustomWorkspaceUrl(customDomain: string) {
|
||||
const url = this.getFrontUrl();
|
||||
const url = this.domainServerConfigService.getFrontUrl();
|
||||
|
||||
url.hostname = customDomain;
|
||||
|
||||
@@ -225,7 +126,7 @@ export class DomainManagerService {
|
||||
}
|
||||
|
||||
private getTwentyWorkspaceUrl(subdomain: string) {
|
||||
const url = this.getFrontUrl();
|
||||
const url = this.domainServerConfigService.getFrontUrl();
|
||||
|
||||
url.hostname = this.twentyConfigService.get('IS_MULTIWORKSPACE_ENABLED')
|
||||
? `${subdomain}.${url.hostname}`
|
||||
@@ -235,7 +136,7 @@ export class DomainManagerService {
|
||||
}
|
||||
|
||||
getSubdomainAndCustomDomainFromWorkspaceFallbackOnDefaultSubdomain(
|
||||
workspace?: WorkspaceSubdomainCustomDomainAndIsCustomDomainEnabledType | null,
|
||||
workspace?: WorkspaceDomainConfig | null,
|
||||
) {
|
||||
if (!workspace) {
|
||||
return {
|
||||
@@ -258,7 +159,7 @@ export class DomainManagerService {
|
||||
subdomain,
|
||||
customDomain,
|
||||
isCustomDomainEnabled,
|
||||
}: WorkspaceSubdomainCustomDomainAndIsCustomDomainEnabledType) {
|
||||
}: WorkspaceDomainConfig) {
|
||||
return {
|
||||
customUrl:
|
||||
isCustomDomainEnabled && customDomain
|
||||
@@ -267,4 +168,8 @@ export class DomainManagerService {
|
||||
subdomainUrl: this.getTwentyWorkspaceUrl(subdomain),
|
||||
};
|
||||
}
|
||||
|
||||
async findByCustomDomain(customDomain: string) {
|
||||
return this.workspaceRepository.findOne({ where: { customDomain } });
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import { type Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
|
||||
export type WorkspaceSubdomainCustomDomainAndIsCustomDomainEnabledType = Pick<
|
||||
export type WorkspaceDomainConfig = Pick<
|
||||
Workspace,
|
||||
'subdomain' | 'customDomain' | 'isCustomDomainEnabled'
|
||||
>;
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { DomainServerConfigModule } from 'src/engine/core-modules/domain/domain-server-config/domain-server-config.module';
|
||||
import { WorkspaceDomainsService } from 'src/engine/core-modules/domain/workspace-domains/services/workspace-domains.service';
|
||||
import { PublicDomain } from 'src/engine/core-modules/public-domain/public-domain.entity';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
DomainServerConfigModule,
|
||||
TypeOrmModule.forFeature([Workspace, PublicDomain]),
|
||||
],
|
||||
providers: [WorkspaceDomainsService],
|
||||
exports: [WorkspaceDomainsService],
|
||||
})
|
||||
export class WorkspaceDomainsModule {}
|
||||
+5
-3
@@ -3,23 +3,25 @@ import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { AppToken } from 'src/engine/core-modules/app-token/app-token.entity';
|
||||
import { EmailVerificationTokenService } from 'src/engine/core-modules/auth/token/services/email-verification-token.service';
|
||||
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 { WorkspaceDomainsModule } from 'src/engine/core-modules/domain/workspace-domains/workspace-domains.module';
|
||||
import { EmailVerificationResolver } from 'src/engine/core-modules/email-verification/email-verification.resolver';
|
||||
import { EmailVerificationService } from 'src/engine/core-modules/email-verification/services/email-verification.service';
|
||||
import { EmailModule } from 'src/engine/core-modules/email/email.module';
|
||||
import { TwentyConfigModule } from 'src/engine/core-modules/twenty-config/twenty-config.module';
|
||||
import { UserWorkspaceModule } from 'src/engine/core-modules/user-workspace/user-workspace.module';
|
||||
import { UserModule } from 'src/engine/core-modules/user/user.module';
|
||||
import { User } from 'src/engine/core-modules/user/user.entity';
|
||||
import { UserModule } from 'src/engine/core-modules/user/user.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([AppToken, User]),
|
||||
EmailModule,
|
||||
TwentyConfigModule,
|
||||
DomainManagerModule,
|
||||
UserModule,
|
||||
UserWorkspaceModule,
|
||||
WorkspaceDomainsModule,
|
||||
DomainServerConfigModule,
|
||||
],
|
||||
providers: [
|
||||
EmailVerificationService,
|
||||
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
import { UseFilters, UseGuards, UsePipes } from '@nestjs/common';
|
||||
import { Args, Context, Mutation, Resolver } from '@nestjs/graphql';
|
||||
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { WorkspaceDomainsService } from 'src/engine/core-modules/domain/workspace-domains/services/workspace-domains.service';
|
||||
import { ResendEmailVerificationTokenInput } from 'src/engine/core-modules/email-verification/dtos/resend-email-verification-token.input';
|
||||
import { ResendEmailVerificationTokenOutput } from 'src/engine/core-modules/email-verification/dtos/resend-email-verification-token.output';
|
||||
import { EmailVerificationExceptionFilter } from 'src/engine/core-modules/email-verification/email-verification-exception-filter.util';
|
||||
@@ -20,7 +20,7 @@ import { PublicEndpointGuard } from 'src/engine/guards/public-endpoint.guard';
|
||||
export class EmailVerificationResolver {
|
||||
constructor(
|
||||
private readonly emailVerificationService: EmailVerificationService,
|
||||
private readonly domainManagerService: DomainManagerService,
|
||||
private readonly workspaceDomainsService: WorkspaceDomainsService,
|
||||
) {}
|
||||
|
||||
// TODO: this should be an authenticated endpoint
|
||||
@@ -33,7 +33,7 @@ export class EmailVerificationResolver {
|
||||
@Context() context: I18nContext,
|
||||
): Promise<ResendEmailVerificationTokenOutput> {
|
||||
const workspace =
|
||||
await this.domainManagerService.getWorkspaceByOriginOrDefaultWorkspace(
|
||||
await this.workspaceDomainsService.getWorkspaceByOriginOrDefaultWorkspace(
|
||||
origin,
|
||||
);
|
||||
|
||||
|
||||
+11
-11
@@ -16,8 +16,9 @@ import {
|
||||
AppTokenType,
|
||||
} from 'src/engine/core-modules/app-token/app-token.entity';
|
||||
import { EmailVerificationTokenService } from 'src/engine/core-modules/auth/token/services/email-verification-token.service';
|
||||
import { type WorkspaceSubdomainCustomDomainAndIsCustomDomainEnabledType } from 'src/engine/core-modules/domain-manager/domain-manager.type';
|
||||
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 { WorkspaceDomainsService } from 'src/engine/core-modules/domain/workspace-domains/services/workspace-domains.service';
|
||||
import { WorkspaceDomainConfig } from 'src/engine/core-modules/domain/workspace-domains/types/workspace-domain-config.type';
|
||||
import {
|
||||
EmailVerificationException,
|
||||
EmailVerificationExceptionCode,
|
||||
@@ -32,7 +33,8 @@ export class EmailVerificationService {
|
||||
constructor(
|
||||
@InjectRepository(AppToken)
|
||||
private readonly appTokenRepository: Repository<AppToken>,
|
||||
private readonly domainManagerService: DomainManagerService,
|
||||
private readonly workspaceDomainsService: WorkspaceDomainsService,
|
||||
private readonly domainsServerConfigService: DomainServerConfigService,
|
||||
private readonly emailService: EmailService,
|
||||
private readonly twentyConfigService: TwentyConfigService,
|
||||
private readonly userService: UserService,
|
||||
@@ -43,9 +45,7 @@ export class EmailVerificationService {
|
||||
async sendVerificationEmail(
|
||||
userId: string,
|
||||
email: string,
|
||||
workspace:
|
||||
| WorkspaceSubdomainCustomDomainAndIsCustomDomainEnabledType
|
||||
| undefined,
|
||||
workspace: WorkspaceDomainConfig | undefined,
|
||||
locale: keyof typeof APP_LOCALES,
|
||||
verifyEmailRedirectPath?: string,
|
||||
) {
|
||||
@@ -67,11 +67,13 @@ export class EmailVerificationService {
|
||||
},
|
||||
};
|
||||
const verificationLink = workspace
|
||||
? this.domainManagerService.buildWorkspaceURL({
|
||||
? this.workspaceDomainsService.buildWorkspaceURL({
|
||||
workspace,
|
||||
...linkPathnameAndSearchParams,
|
||||
})
|
||||
: this.domainManagerService.buildBaseUrl(linkPathnameAndSearchParams);
|
||||
: this.domainsServerConfigService.buildBaseUrl(
|
||||
linkPathnameAndSearchParams,
|
||||
);
|
||||
|
||||
const emailData = {
|
||||
link: verificationLink.toString(),
|
||||
@@ -104,9 +106,7 @@ export class EmailVerificationService {
|
||||
|
||||
async resendEmailVerificationToken(
|
||||
email: string,
|
||||
workspace:
|
||||
| WorkspaceSubdomainCustomDomainAndIsCustomDomainEnabledType
|
||||
| undefined,
|
||||
workspace: WorkspaceDomainConfig | undefined,
|
||||
locale: keyof typeof APP_LOCALES,
|
||||
) {
|
||||
if (!this.twentyConfigService.get('IS_EMAIL_VERIFICATION_REQUIRED')) {
|
||||
|
||||
+3
-2
@@ -1,10 +1,11 @@
|
||||
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 { WorkspaceDomainsModule } from 'src/engine/core-modules/domain/workspace-domains/workspace-domains.module';
|
||||
import { GuardRedirectService } from 'src/engine/core-modules/guard-redirect/services/guard-redirect.service';
|
||||
|
||||
@Module({
|
||||
imports: [DomainManagerModule],
|
||||
imports: [WorkspaceDomainsModule, DomainServerConfigModule],
|
||||
providers: [GuardRedirectService],
|
||||
exports: [GuardRedirectService],
|
||||
})
|
||||
|
||||
+7
-5
@@ -1,4 +1,4 @@
|
||||
import { type ExecutionContext, Injectable } from '@nestjs/common';
|
||||
import { ExecutionContext, Injectable } from '@nestjs/common';
|
||||
|
||||
import { type Request } from 'express';
|
||||
import { AppPath } from 'twenty-shared/types';
|
||||
@@ -7,7 +7,8 @@ import {
|
||||
AuthException,
|
||||
AuthExceptionCode,
|
||||
} from 'src/engine/core-modules/auth/auth.exception';
|
||||
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 { WorkspaceDomainsService } from 'src/engine/core-modules/domain/workspace-domains/services/workspace-domains.service';
|
||||
import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
import { type CustomException } from 'src/utils/custom-exception';
|
||||
@@ -15,9 +16,10 @@ import { type CustomException } from 'src/utils/custom-exception';
|
||||
@Injectable()
|
||||
export class GuardRedirectService {
|
||||
constructor(
|
||||
private readonly domainManagerService: DomainManagerService,
|
||||
private readonly twentyConfigService: TwentyConfigService,
|
||||
private readonly exceptionHandlerService: ExceptionHandlerService,
|
||||
private readonly domainsServerConfigService: DomainServerConfigService,
|
||||
private readonly workspaceDomainsService: WorkspaceDomainsService,
|
||||
) {}
|
||||
|
||||
dispatchErrorFromGuard(
|
||||
@@ -48,7 +50,7 @@ export class GuardRedirectService {
|
||||
const request = context.switchToHttp().getRequest<Request>();
|
||||
|
||||
const subdomainAndDomainFromReferer = request.headers.referer
|
||||
? this.domainManagerService.getSubdomainAndDomainFromUrl(
|
||||
? this.domainsServerConfigService.getSubdomainAndDomainFromUrl(
|
||||
request.headers.referer,
|
||||
)
|
||||
: null;
|
||||
@@ -95,7 +97,7 @@ export class GuardRedirectService {
|
||||
}) {
|
||||
this.captureException(error, workspace.id);
|
||||
|
||||
return this.domainManagerService.computeRedirectErrorUrl(
|
||||
return this.workspaceDomainsService.computeWorkspaceRedirectErrorUrl(
|
||||
error instanceof AuthException ? error.message : 'Unknown error',
|
||||
{
|
||||
subdomain: workspace.subdomain,
|
||||
|
||||
+2
-2
@@ -9,7 +9,7 @@ import {
|
||||
AuthExceptionCode,
|
||||
} from 'src/engine/core-modules/auth/auth.exception';
|
||||
import { LoginTokenService } from 'src/engine/core-modules/auth/token/services/login-token.service';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { WorkspaceDomainsService } from 'src/engine/core-modules/domain/workspace-domains/services/workspace-domains.service';
|
||||
import { ImpersonationService } from 'src/engine/core-modules/impersonation/services/impersonation.service';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
import { OTPStatus } from 'src/engine/core-modules/two-factor-authentication/strategies/otp/otp.constants';
|
||||
@@ -61,7 +61,7 @@ describe('ImpersonationService', () => {
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: DomainManagerService,
|
||||
provide: WorkspaceDomainsService,
|
||||
useValue: {
|
||||
getWorkspaceUrls: jest.fn().mockReturnValue({
|
||||
customUrl: undefined,
|
||||
|
||||
+2
-2
@@ -3,7 +3,7 @@ import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { AuditModule } from 'src/engine/core-modules/audit/audit.module';
|
||||
import { AuthModule } from 'src/engine/core-modules/auth/auth.module';
|
||||
import { DomainManagerModule } from 'src/engine/core-modules/domain-manager/domain-manager.module';
|
||||
import { WorkspaceDomainsModule } from 'src/engine/core-modules/domain/workspace-domains/workspace-domains.module';
|
||||
import { ImpersonationResolver } from 'src/engine/core-modules/impersonation/impersonation.resolver';
|
||||
import { ImpersonationService } from 'src/engine/core-modules/impersonation/services/impersonation.service';
|
||||
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||
@@ -23,7 +23,7 @@ import { UserRoleModule } from 'src/engine/metadata-modules/user-role/user-role.
|
||||
UserRoleModule,
|
||||
AuditModule,
|
||||
TypeOrmModule.forFeature([UserWorkspace, Workspace, User]),
|
||||
DomainManagerModule,
|
||||
WorkspaceDomainsModule,
|
||||
PermissionsModule,
|
||||
],
|
||||
providers: [ImpersonationService, ImpersonationResolver],
|
||||
|
||||
+3
-3
@@ -13,7 +13,7 @@ import {
|
||||
AuthExceptionCode,
|
||||
} from 'src/engine/core-modules/auth/auth.exception';
|
||||
import { LoginTokenService } from 'src/engine/core-modules/auth/token/services/login-token.service';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { WorkspaceDomainsService } from 'src/engine/core-modules/domain/workspace-domains/services/workspace-domains.service';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
import { twoFactorAuthenticationMethodsValidator } from 'src/engine/core-modules/two-factor-authentication/two-factor-authentication.validation';
|
||||
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||
@@ -25,7 +25,7 @@ import { PermissionsService } from 'src/engine/metadata-modules/permissions/perm
|
||||
export class ImpersonationService {
|
||||
constructor(
|
||||
private readonly auditService: AuditService,
|
||||
private readonly domainManagerService: DomainManagerService,
|
||||
private readonly workspaceDomainsService: WorkspaceDomainsService,
|
||||
private readonly loginTokenService: LoginTokenService,
|
||||
private readonly twentyConfigService: TwentyConfigService,
|
||||
@InjectRepository(UserWorkspace)
|
||||
@@ -172,7 +172,7 @@ export class ImpersonationService {
|
||||
return {
|
||||
workspace: {
|
||||
id: toImpersonateUserWorkspace.workspace.id,
|
||||
workspaceUrls: this.domainManagerService.getWorkspaceUrls(
|
||||
workspaceUrls: this.workspaceDomainsService.getWorkspaceUrls(
|
||||
toImpersonateUserWorkspace.workspace,
|
||||
),
|
||||
},
|
||||
|
||||
@@ -6,7 +6,6 @@ import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm';
|
||||
|
||||
import { AppToken } from 'src/engine/core-modules/app-token/app-token.entity';
|
||||
import { BillingModule } from 'src/engine/core-modules/billing/billing.module';
|
||||
import { DomainManagerModule } from 'src/engine/core-modules/domain-manager/domain-manager.module';
|
||||
import { FeatureFlag } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
|
||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||
import { GuardRedirectModule } from 'src/engine/core-modules/guard-redirect/guard-redirect.module';
|
||||
@@ -24,7 +23,6 @@ import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permi
|
||||
FeatureFlag,
|
||||
]),
|
||||
BillingModule,
|
||||
DomainManagerModule,
|
||||
GuardRedirectModule,
|
||||
PermissionsModule,
|
||||
FeatureFlagModule,
|
||||
|
||||
+3
-3
@@ -2,7 +2,7 @@ import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { TokenModule } from 'src/engine/core-modules/auth/token/token.module';
|
||||
import { DomainManagerModule } from 'src/engine/core-modules/domain-manager/domain-manager.module';
|
||||
import { WorkspaceDomainsModule } from 'src/engine/core-modules/domain/workspace-domains/workspace-domains.module';
|
||||
import { JwtModule } from 'src/engine/core-modules/jwt/jwt.module';
|
||||
import { MetricsModule } from 'src/engine/core-modules/metrics/metrics.module';
|
||||
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||
@@ -13,13 +13,13 @@ import { UserModule } from 'src/engine/core-modules/user/user.module';
|
||||
import { TwoFactorAuthenticationResolver } from './two-factor-authentication.resolver';
|
||||
import { TwoFactorAuthenticationService } from './two-factor-authentication.service';
|
||||
|
||||
import { SimpleSecretEncryptionUtil } from './utils/simple-secret-encryption.util';
|
||||
import { TwoFactorAuthenticationMethod } from './entities/two-factor-authentication-method.entity';
|
||||
import { SimpleSecretEncryptionUtil } from './utils/simple-secret-encryption.util';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
UserWorkspaceModule,
|
||||
DomainManagerModule,
|
||||
WorkspaceDomainsModule,
|
||||
MetricsModule,
|
||||
TokenModule,
|
||||
JwtModule,
|
||||
|
||||
+11
-9
@@ -6,7 +6,7 @@ import {
|
||||
AuthExceptionCode,
|
||||
} from 'src/engine/core-modules/auth/auth.exception';
|
||||
import { LoginTokenService } from 'src/engine/core-modules/auth/token/services/login-token.service';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { WorkspaceDomainsService } from 'src/engine/core-modules/domain/workspace-domains/services/workspace-domains.service';
|
||||
import { UserService } from 'src/engine/core-modules/user/services/user.service';
|
||||
import { type User } from 'src/engine/core-modules/user/user.entity';
|
||||
import { type Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
@@ -37,7 +37,7 @@ const createMockUserService = () => ({
|
||||
findUserByEmailOrThrow: jest.fn(),
|
||||
});
|
||||
|
||||
const createMockDomainManagerService = () => ({
|
||||
const createMockWorkspaceDomainsService = () => ({
|
||||
getWorkspaceByOriginOrDefaultWorkspace: jest.fn(),
|
||||
});
|
||||
|
||||
@@ -48,7 +48,9 @@ describe('TwoFactorAuthenticationResolver', () => {
|
||||
>;
|
||||
let loginTokenService: ReturnType<typeof createMockLoginTokenService>;
|
||||
let userService: ReturnType<typeof createMockUserService>;
|
||||
let domainManagerService: ReturnType<typeof createMockDomainManagerService>;
|
||||
let workspaceDomainsService: ReturnType<
|
||||
typeof createMockWorkspaceDomainsService
|
||||
>;
|
||||
let repository: ReturnType<typeof createMockRepository>;
|
||||
|
||||
const mockUser: User = {
|
||||
@@ -86,8 +88,8 @@ describe('TwoFactorAuthenticationResolver', () => {
|
||||
useFactory: createMockUserService,
|
||||
},
|
||||
{
|
||||
provide: DomainManagerService,
|
||||
useFactory: createMockDomainManagerService,
|
||||
provide: WorkspaceDomainsService,
|
||||
useFactory: createMockWorkspaceDomainsService,
|
||||
},
|
||||
{
|
||||
provide: getRepositoryToken(TwoFactorAuthenticationMethod),
|
||||
@@ -102,7 +104,7 @@ describe('TwoFactorAuthenticationResolver', () => {
|
||||
twoFactorAuthenticationService = module.get(TwoFactorAuthenticationService);
|
||||
loginTokenService = module.get(LoginTokenService);
|
||||
userService = module.get(UserService);
|
||||
domainManagerService = module.get(DomainManagerService);
|
||||
workspaceDomainsService = module.get(WorkspaceDomainsService);
|
||||
repository = module.get(getRepositoryToken(TwoFactorAuthenticationMethod));
|
||||
});
|
||||
|
||||
@@ -125,7 +127,7 @@ describe('TwoFactorAuthenticationResolver', () => {
|
||||
sub: mockUser.email,
|
||||
workspaceId: mockWorkspace.id,
|
||||
});
|
||||
domainManagerService.getWorkspaceByOriginOrDefaultWorkspace.mockResolvedValue(
|
||||
workspaceDomainsService.getWorkspaceByOriginOrDefaultWorkspace.mockResolvedValue(
|
||||
mockWorkspace,
|
||||
);
|
||||
userService.findUserByEmailOrThrow.mockResolvedValue(mockUser);
|
||||
@@ -144,7 +146,7 @@ describe('TwoFactorAuthenticationResolver', () => {
|
||||
mockInput.loginToken,
|
||||
);
|
||||
expect(
|
||||
domainManagerService.getWorkspaceByOriginOrDefaultWorkspace,
|
||||
workspaceDomainsService.getWorkspaceByOriginOrDefaultWorkspace,
|
||||
).toHaveBeenCalledWith(origin);
|
||||
expect(userService.findUserByEmailOrThrow).toHaveBeenCalledWith(
|
||||
mockUser.email,
|
||||
@@ -160,7 +162,7 @@ describe('TwoFactorAuthenticationResolver', () => {
|
||||
});
|
||||
|
||||
it('should throw WORKSPACE_NOT_FOUND when workspace is not found', async () => {
|
||||
domainManagerService.getWorkspaceByOriginOrDefaultWorkspace.mockResolvedValue(
|
||||
workspaceDomainsService.getWorkspaceByOriginOrDefaultWorkspace.mockResolvedValue(
|
||||
null,
|
||||
);
|
||||
|
||||
|
||||
+3
-3
@@ -11,7 +11,7 @@ import {
|
||||
} from 'src/engine/core-modules/auth/auth.exception';
|
||||
import { AuthGraphqlApiExceptionFilter } from 'src/engine/core-modules/auth/filters/auth-graphql-api-exception.filter';
|
||||
import { LoginTokenService } from 'src/engine/core-modules/auth/token/services/login-token.service';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { WorkspaceDomainsService } from 'src/engine/core-modules/domain/workspace-domains/services/workspace-domains.service';
|
||||
import { UserService } from 'src/engine/core-modules/user/services/user.service';
|
||||
import { User } from 'src/engine/core-modules/user/user.entity';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
@@ -39,7 +39,7 @@ export class TwoFactorAuthenticationResolver {
|
||||
private readonly twoFactorAuthenticationService: TwoFactorAuthenticationService,
|
||||
private readonly loginTokenService: LoginTokenService,
|
||||
private readonly userService: UserService,
|
||||
private readonly domainManagerService: DomainManagerService,
|
||||
private readonly workspaceDomainsService: WorkspaceDomainsService,
|
||||
@InjectRepository(TwoFactorAuthenticationMethod)
|
||||
private readonly twoFactorAuthenticationMethodRepository: Repository<TwoFactorAuthenticationMethod>,
|
||||
) {}
|
||||
@@ -57,7 +57,7 @@ export class TwoFactorAuthenticationResolver {
|
||||
);
|
||||
|
||||
const workspace =
|
||||
await this.domainManagerService.getWorkspaceByOriginOrDefaultWorkspace(
|
||||
await this.workspaceDomainsService.getWorkspaceByOriginOrDefaultWorkspace(
|
||||
origin,
|
||||
);
|
||||
|
||||
|
||||
+2
-2
@@ -6,7 +6,7 @@ import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm';
|
||||
import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
|
||||
import { ApprovedAccessDomainModule } from 'src/engine/core-modules/approved-access-domain/approved-access-domain.module';
|
||||
import { TokenModule } from 'src/engine/core-modules/auth/token/token.module';
|
||||
import { DomainManagerModule } from 'src/engine/core-modules/domain-manager/domain-manager.module';
|
||||
import { WorkspaceDomainsModule } from 'src/engine/core-modules/domain/workspace-domains/workspace-domains.module';
|
||||
import { FileUploadModule } from 'src/engine/core-modules/file/file-upload/file-upload.module';
|
||||
import { FileModule } from 'src/engine/core-modules/file/file.module';
|
||||
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||
@@ -31,7 +31,7 @@ import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/works
|
||||
WorkspaceDataSourceModule,
|
||||
ApprovedAccessDomainModule,
|
||||
WorkspaceInvitationModule,
|
||||
DomainManagerModule,
|
||||
WorkspaceDomainsModule,
|
||||
TwentyORMModule,
|
||||
UserRoleModule,
|
||||
FileUploadModule,
|
||||
|
||||
+2
-2
@@ -9,7 +9,7 @@ import { type ApprovedAccessDomain } from 'src/engine/core-modules/approved-acce
|
||||
import { ApprovedAccessDomainService } from 'src/engine/core-modules/approved-access-domain/services/approved-access-domain.service';
|
||||
import { AuthException } from 'src/engine/core-modules/auth/auth.exception';
|
||||
import { LoginTokenService } from 'src/engine/core-modules/auth/token/services/login-token.service';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { WorkspaceDomainsService } from 'src/engine/core-modules/domain/workspace-domains/services/workspace-domains.service';
|
||||
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
|
||||
import {
|
||||
FileUploadService,
|
||||
@@ -81,7 +81,7 @@ describe('UserWorkspaceService', () => {
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: DomainManagerService,
|
||||
provide: WorkspaceDomainsService,
|
||||
useValue: {
|
||||
getWorkspaceUrls: jest.fn(),
|
||||
},
|
||||
|
||||
+3
-3
@@ -16,7 +16,7 @@ import {
|
||||
} from 'src/engine/core-modules/auth/auth.exception';
|
||||
import { type AvailableWorkspace } from 'src/engine/core-modules/auth/dto/available-workspaces.output';
|
||||
import { LoginTokenService } from 'src/engine/core-modules/auth/token/services/login-token.service';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { WorkspaceDomainsService } from 'src/engine/core-modules/domain/workspace-domains/services/workspace-domains.service';
|
||||
import { FileUploadService } from 'src/engine/core-modules/file/file-upload/services/file-upload.service';
|
||||
import { FileService } from 'src/engine/core-modules/file/services/file.service';
|
||||
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||
@@ -43,7 +43,7 @@ export class UserWorkspaceService extends TypeOrmQueryService<UserWorkspace> {
|
||||
@InjectRepository(User)
|
||||
private readonly userRepository: Repository<User>,
|
||||
private readonly workspaceInvitationService: WorkspaceInvitationService,
|
||||
private readonly domainManagerService: DomainManagerService,
|
||||
private readonly workspaceDomainsService: WorkspaceDomainsService,
|
||||
private readonly loginTokenService: LoginTokenService,
|
||||
private readonly approvedAccessDomainService: ApprovedAccessDomainService,
|
||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
@@ -384,7 +384,7 @@ export class UserWorkspaceService extends TypeOrmQueryService<UserWorkspace> {
|
||||
return {
|
||||
id: workspace.id,
|
||||
displayName: workspace.displayName,
|
||||
workspaceUrls: this.domainManagerService.getWorkspaceUrls(workspace),
|
||||
workspaceUrls: this.workspaceDomainsService.getWorkspaceUrls(workspace),
|
||||
logo: workspace.logo
|
||||
? this.fileService.signFileUrl({
|
||||
url: workspace.logo,
|
||||
|
||||
+2
-2
@@ -7,7 +7,7 @@ import {
|
||||
AppToken,
|
||||
AppTokenType,
|
||||
} from 'src/engine/core-modules/app-token/app-token.entity';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { WorkspaceDomainsService } from 'src/engine/core-modules/domain/workspace-domains/services/workspace-domains.service';
|
||||
import { EmailService } from 'src/engine/core-modules/email/email.service';
|
||||
import { FileService } from 'src/engine/core-modules/file/services/file.service';
|
||||
import { I18nService } from 'src/engine/core-modules/i18n/i18n.service';
|
||||
@@ -60,7 +60,7 @@ describe('WorkspaceInvitationService', () => {
|
||||
useClass: Repository,
|
||||
},
|
||||
{
|
||||
provide: DomainManagerService,
|
||||
provide: WorkspaceDomainsService,
|
||||
useValue: {
|
||||
buildWorkspaceURL: jest
|
||||
.fn()
|
||||
|
||||
+3
-3
@@ -20,7 +20,7 @@ import {
|
||||
AuthException,
|
||||
AuthExceptionCode,
|
||||
} from 'src/engine/core-modules/auth/auth.exception';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { WorkspaceDomainsService } from 'src/engine/core-modules/domain/workspace-domains/services/workspace-domains.service';
|
||||
import { EmailService } from 'src/engine/core-modules/email/email.service';
|
||||
import { FileService } from 'src/engine/core-modules/file/services/file.service';
|
||||
import { I18nService } from 'src/engine/core-modules/i18n/i18n.service';
|
||||
@@ -46,7 +46,7 @@ export class WorkspaceInvitationService {
|
||||
private readonly twentyConfigService: TwentyConfigService,
|
||||
private readonly emailService: EmailService,
|
||||
private readonly onboardingService: OnboardingService,
|
||||
private readonly domainManagerService: DomainManagerService,
|
||||
private readonly workspaceDomainsService: WorkspaceDomainsService,
|
||||
private readonly i18nService: I18nService,
|
||||
private readonly fileService: FileService,
|
||||
) {}
|
||||
@@ -281,7 +281,7 @@ export class WorkspaceInvitationService {
|
||||
|
||||
for (const invitation of invitationsPr) {
|
||||
if (invitation.status === 'fulfilled') {
|
||||
const link = this.domainManagerService.buildWorkspaceURL({
|
||||
const link = this.workspaceDomainsService.buildWorkspaceURL({
|
||||
workspace,
|
||||
pathname: getAppPath(AppPath.Invite, {
|
||||
workspaceInviteHash: workspace?.inviteHash,
|
||||
|
||||
+2
-2
@@ -3,7 +3,7 @@ import { Module } from '@nestjs/common';
|
||||
import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm';
|
||||
|
||||
import { AppToken } from 'src/engine/core-modules/app-token/app-token.entity';
|
||||
import { DomainManagerModule } from 'src/engine/core-modules/domain-manager/domain-manager.module';
|
||||
import { WorkspaceDomainsModule } from 'src/engine/core-modules/domain/workspace-domains/workspace-domains.module';
|
||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||
import { FileModule } from 'src/engine/core-modules/file/file.module';
|
||||
import { OnboardingModule } from 'src/engine/core-modules/onboarding/onboarding.module';
|
||||
@@ -15,7 +15,7 @@ import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permi
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
DomainManagerModule,
|
||||
WorkspaceDomainsModule,
|
||||
NestjsQueryTypeOrmModule.forFeature([AppToken, UserWorkspace, Workspace]),
|
||||
FileModule,
|
||||
OnboardingModule,
|
||||
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
export const RESERVED_SUBDOMAINS = [
|
||||
'trust',
|
||||
'demo',
|
||||
'api',
|
||||
't',
|
||||
'companies',
|
||||
'telemetry',
|
||||
'logs',
|
||||
'metrics',
|
||||
'next',
|
||||
'main',
|
||||
'admin',
|
||||
'dashboard',
|
||||
'dash',
|
||||
'billing',
|
||||
'db',
|
||||
'favicon',
|
||||
'www',
|
||||
'mail',
|
||||
'docs',
|
||||
'dev',
|
||||
'app',
|
||||
'staging',
|
||||
'production',
|
||||
'developer',
|
||||
'files',
|
||||
'cdn',
|
||||
'storage',
|
||||
'about',
|
||||
'help',
|
||||
'support',
|
||||
'contact',
|
||||
'privacy',
|
||||
'terms',
|
||||
'careers',
|
||||
'jobs',
|
||||
'blog',
|
||||
'news',
|
||||
'events',
|
||||
'community',
|
||||
'forum',
|
||||
'chat',
|
||||
'test',
|
||||
'testing',
|
||||
'feedback',
|
||||
'config',
|
||||
'settings',
|
||||
'media',
|
||||
'image',
|
||||
'audio',
|
||||
'video',
|
||||
'images',
|
||||
'partners',
|
||||
'partnership',
|
||||
'partnerships',
|
||||
'assets',
|
||||
'login',
|
||||
'signin',
|
||||
'signup',
|
||||
'legal',
|
||||
'shop',
|
||||
'merch',
|
||||
'store',
|
||||
'auth',
|
||||
'register',
|
||||
'payment',
|
||||
'fr',
|
||||
'de',
|
||||
'it',
|
||||
'es',
|
||||
'pt',
|
||||
'nl',
|
||||
'be',
|
||||
'ch',
|
||||
'us',
|
||||
'ca',
|
||||
'au',
|
||||
'nz',
|
||||
'za',
|
||||
'eu',
|
||||
'uk',
|
||||
'ru',
|
||||
'ua',
|
||||
'pl',
|
||||
'ro',
|
||||
'bg',
|
||||
'gr',
|
||||
'cz',
|
||||
'sk',
|
||||
'hu',
|
||||
'hr',
|
||||
'si',
|
||||
'rs',
|
||||
'me',
|
||||
'ba',
|
||||
'mk',
|
||||
'al',
|
||||
'az',
|
||||
'tr',
|
||||
'cy',
|
||||
'lv',
|
||||
'lt',
|
||||
'ee',
|
||||
'fi',
|
||||
'is',
|
||||
'no',
|
||||
'se',
|
||||
'dk',
|
||||
'asia',
|
||||
'africa',
|
||||
'america',
|
||||
'europe',
|
||||
'north-america',
|
||||
'south-africa',
|
||||
'north-africa',
|
||||
'south-america',
|
||||
'oceania',
|
||||
'paris',
|
||||
'london',
|
||||
'new-york',
|
||||
'san-francisco',
|
||||
];
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
export const VALID_SUBDOMAIN_PATTERN =
|
||||
/^(?!api-).*^[a-z0-9][a-z0-9-]{1,28}[a-z0-9]$/;
|
||||
+7
-5
@@ -1,14 +1,14 @@
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { IsNull, Not, Repository, Raw } from 'typeorm';
|
||||
import { WorkspaceActivationStatus } from 'twenty-shared/workspace';
|
||||
import { IsNull, Not, Raw, Repository } from 'typeorm';
|
||||
|
||||
import { SentryCronMonitor } from 'src/engine/core-modules/cron/sentry-cron-monitor.decorator';
|
||||
import { CustomDomainManagerService } from 'src/engine/core-modules/domain/custom-domain-manager/services/custom-domain-manager.service';
|
||||
import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator';
|
||||
import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator';
|
||||
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { SentryCronMonitor } from 'src/engine/core-modules/cron/sentry-cron-monitor.decorator';
|
||||
import { WorkspaceService } from 'src/engine/core-modules/workspace/services/workspace.service';
|
||||
|
||||
export const CHECK_CUSTOM_DOMAIN_VALID_RECORDS_CRON_PATTERN = '0 * * * *';
|
||||
|
||||
@@ -17,7 +17,7 @@ export class CheckCustomDomainValidRecordsCronJob {
|
||||
constructor(
|
||||
@InjectRepository(Workspace)
|
||||
private readonly workspaceRepository: Repository<Workspace>,
|
||||
private readonly workspaceService: WorkspaceService,
|
||||
private readonly customDomainManagerService: CustomDomainManagerService,
|
||||
) {}
|
||||
|
||||
@Process(CheckCustomDomainValidRecordsCronJob.name)
|
||||
@@ -39,7 +39,9 @@ export class CheckCustomDomainValidRecordsCronJob {
|
||||
|
||||
for (const workspace of workspaces) {
|
||||
try {
|
||||
await this.workspaceService.checkCustomDomainValidRecords(workspace);
|
||||
await this.customDomainManagerService.checkCustomDomainValidRecords(
|
||||
workspace,
|
||||
);
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`[${CheckCustomDomainValidRecordsCronJob.name}] Cannot check custom domain for workspaces: ${error.message}`,
|
||||
|
||||
-124
@@ -3,7 +3,6 @@ import { Field, InputType } from '@nestjs/graphql';
|
||||
import {
|
||||
IsBoolean,
|
||||
IsInt,
|
||||
IsNotIn,
|
||||
IsOptional,
|
||||
IsString,
|
||||
IsUUID,
|
||||
@@ -18,129 +17,6 @@ export class UpdateWorkspaceInput {
|
||||
@Field({ nullable: true })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@Matches(/^(?!api-).*^[a-z0-9][a-z0-9-]{1,28}[a-z0-9]$/)
|
||||
@IsNotIn([
|
||||
'trust',
|
||||
'demo',
|
||||
'api',
|
||||
't',
|
||||
'companies',
|
||||
'telemetry',
|
||||
'logs',
|
||||
'metrics',
|
||||
'next',
|
||||
'main',
|
||||
'admin',
|
||||
'dashboard',
|
||||
'dash',
|
||||
'billing',
|
||||
'db',
|
||||
'favicon',
|
||||
'www',
|
||||
'mail',
|
||||
'docs',
|
||||
'dev',
|
||||
'app',
|
||||
'staging',
|
||||
'production',
|
||||
'developer',
|
||||
'files',
|
||||
'cdn',
|
||||
'storage',
|
||||
'about',
|
||||
'help',
|
||||
'support',
|
||||
'contact',
|
||||
'privacy',
|
||||
'terms',
|
||||
'careers',
|
||||
'jobs',
|
||||
'blog',
|
||||
'news',
|
||||
'events',
|
||||
'community',
|
||||
'forum',
|
||||
'chat',
|
||||
'test',
|
||||
'testing',
|
||||
'feedback',
|
||||
'config',
|
||||
'settings',
|
||||
'media',
|
||||
'image',
|
||||
'audio',
|
||||
'video',
|
||||
'images',
|
||||
'partners',
|
||||
'partnership',
|
||||
'partnerships',
|
||||
'assets',
|
||||
'login',
|
||||
'signin',
|
||||
'signup',
|
||||
'legal',
|
||||
'shop',
|
||||
'merch',
|
||||
'store',
|
||||
'auth',
|
||||
'register',
|
||||
'payment',
|
||||
'fr',
|
||||
'de',
|
||||
'it',
|
||||
'es',
|
||||
'pt',
|
||||
'nl',
|
||||
'be',
|
||||
'ch',
|
||||
'us',
|
||||
'ca',
|
||||
'au',
|
||||
'nz',
|
||||
'za',
|
||||
'eu',
|
||||
'uk',
|
||||
'ru',
|
||||
'ua',
|
||||
'pl',
|
||||
'ro',
|
||||
'bg',
|
||||
'gr',
|
||||
'cz',
|
||||
'sk',
|
||||
'hu',
|
||||
'hr',
|
||||
'si',
|
||||
'rs',
|
||||
'me',
|
||||
'ba',
|
||||
'mk',
|
||||
'al',
|
||||
'az',
|
||||
'tr',
|
||||
'cy',
|
||||
'lv',
|
||||
'lt',
|
||||
'ee',
|
||||
'fi',
|
||||
'is',
|
||||
'no',
|
||||
'se',
|
||||
'dk',
|
||||
'asia',
|
||||
'africa',
|
||||
'america',
|
||||
'europe',
|
||||
'north-america',
|
||||
'south-africa',
|
||||
'north-africa',
|
||||
'south-america',
|
||||
'oceania',
|
||||
'paris',
|
||||
'london',
|
||||
'new-york',
|
||||
'san-francisco',
|
||||
])
|
||||
subdomain?: string;
|
||||
|
||||
@Field({ nullable: true })
|
||||
|
||||
+4
-2
@@ -7,7 +7,8 @@ import { AuditService } from 'src/engine/core-modules/audit/services/audit.servi
|
||||
import { BillingSubscriptionService } from 'src/engine/core-modules/billing/services/billing-subscription.service';
|
||||
import { BillingService } from 'src/engine/core-modules/billing/services/billing.service';
|
||||
import { DnsManagerService } from 'src/engine/core-modules/dns-manager/services/dns-manager.service';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { CustomDomainManagerService } from 'src/engine/core-modules/domain/custom-domain-manager/services/custom-domain-manager.service';
|
||||
import { SubdomainManagerService } from 'src/engine/core-modules/domain/subdomain-manager/services/subdomain-manager.service';
|
||||
import { EmailService } from 'src/engine/core-modules/email/email.service';
|
||||
import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service';
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
@@ -93,8 +94,9 @@ describe('WorkspaceService', () => {
|
||||
WorkspaceManagerService,
|
||||
UserWorkspaceService,
|
||||
UserService,
|
||||
DomainManagerService,
|
||||
DnsManagerService,
|
||||
CustomDomainManagerService,
|
||||
SubdomainManagerService,
|
||||
TwentyConfigService,
|
||||
EmailService,
|
||||
OnboardingService,
|
||||
+11
-130
@@ -9,14 +9,12 @@ import { assertIsDefinedOrThrow, isDefined } from 'twenty-shared/utils';
|
||||
import { WorkspaceActivationStatus } from 'twenty-shared/workspace';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { AuditService } from 'src/engine/core-modules/audit/services/audit.service';
|
||||
import { CUSTOM_DOMAIN_ACTIVATED_EVENT } from 'src/engine/core-modules/audit/utils/events/workspace-event/custom-domain/custom-domain-activated';
|
||||
import { CUSTOM_DOMAIN_DEACTIVATED_EVENT } from 'src/engine/core-modules/audit/utils/events/workspace-event/custom-domain/custom-domain-deactivated';
|
||||
import { BillingEntitlementKey } from 'src/engine/core-modules/billing/enums/billing-entitlement-key.enum';
|
||||
import { BillingSubscriptionService } from 'src/engine/core-modules/billing/services/billing-subscription.service';
|
||||
import { BillingService } from 'src/engine/core-modules/billing/services/billing.service';
|
||||
import { DomainValidRecords } from 'src/engine/core-modules/dns-manager/dtos/domain-valid-records';
|
||||
import { DnsManagerService } from 'src/engine/core-modules/dns-manager/services/dns-manager.service';
|
||||
import { CustomDomainManagerService } from 'src/engine/core-modules/domain/custom-domain-manager/services/custom-domain-manager.service';
|
||||
import { SubdomainManagerService } from 'src/engine/core-modules/domain/subdomain-manager/services/subdomain-manager.service';
|
||||
import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service';
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
import {
|
||||
@@ -26,7 +24,6 @@ import {
|
||||
import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator';
|
||||
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
|
||||
import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service';
|
||||
import { PublicDomain } from 'src/engine/core-modules/public-domain/public-domain.entity';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||
import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/user-workspace.service';
|
||||
@@ -60,8 +57,6 @@ export class WorkspaceService extends TypeOrmQueryService<Workspace> {
|
||||
constructor(
|
||||
@InjectRepository(Workspace)
|
||||
private readonly workspaceRepository: Repository<Workspace>,
|
||||
@InjectRepository(PublicDomain)
|
||||
private readonly publicDomainRepository: Repository<PublicDomain>,
|
||||
@InjectRepository(User)
|
||||
private readonly userRepository: Repository<User>,
|
||||
@InjectRepository(UserWorkspace)
|
||||
@@ -77,84 +72,14 @@ export class WorkspaceService extends TypeOrmQueryService<Workspace> {
|
||||
private readonly dnsManagerService: DnsManagerService,
|
||||
private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
|
||||
private readonly workspaceCacheStorageService: WorkspaceCacheStorageService,
|
||||
private readonly auditService: AuditService,
|
||||
private readonly subdomainManagerService: SubdomainManagerService,
|
||||
private readonly customDomainManagerService: CustomDomainManagerService,
|
||||
@InjectMessageQueue(MessageQueue.deleteCascadeQueue)
|
||||
private readonly messageQueueService: MessageQueueService,
|
||||
) {
|
||||
super(workspaceRepository);
|
||||
}
|
||||
|
||||
private async isCustomDomainEnabled(workspaceId: string) {
|
||||
const isCustomDomainBillingEnabled =
|
||||
await this.billingService.hasEntitlement(
|
||||
workspaceId,
|
||||
this.featureLookUpKey,
|
||||
);
|
||||
|
||||
if (!isCustomDomainBillingEnabled) {
|
||||
throw new WorkspaceException(
|
||||
`No entitlement found for this workspace`,
|
||||
WorkspaceExceptionCode.WORKSPACE_CUSTOM_DOMAIN_DISABLED,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async validateSubdomainUpdate(newSubdomain: string) {
|
||||
const subdomainAvailable = await this.isSubdomainAvailable(newSubdomain);
|
||||
|
||||
if (
|
||||
!subdomainAvailable ||
|
||||
this.twentyConfigService.get('DEFAULT_SUBDOMAIN') === newSubdomain
|
||||
) {
|
||||
throw new WorkspaceException(
|
||||
'Subdomain already taken',
|
||||
WorkspaceExceptionCode.SUBDOMAIN_ALREADY_TAKEN,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async setCustomDomain(workspace: Workspace, customDomain: string) {
|
||||
await this.isCustomDomainEnabled(workspace.id);
|
||||
|
||||
const existingWorkspace = await this.workspaceRepository.findOne({
|
||||
where: { customDomain },
|
||||
});
|
||||
|
||||
if (existingWorkspace && existingWorkspace.id !== workspace.id) {
|
||||
throw new WorkspaceException(
|
||||
'Domain already taken',
|
||||
WorkspaceExceptionCode.DOMAIN_ALREADY_TAKEN,
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
await this.publicDomainRepository.findOneBy({
|
||||
domain: customDomain,
|
||||
})
|
||||
) {
|
||||
throw new WorkspaceException(
|
||||
'Domain is already registered as public domain',
|
||||
WorkspaceExceptionCode.DOMAIN_ALREADY_TAKEN,
|
||||
{
|
||||
userFriendlyMessage: msg`Domain is already registered as public domain`,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if (!isDefined(customDomain) || workspace.customDomain === customDomain) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isDefined(workspace.customDomain)) {
|
||||
await this.dnsManagerService.updateHostname(
|
||||
workspace.customDomain,
|
||||
customDomain,
|
||||
);
|
||||
} else {
|
||||
await this.dnsManagerService.registerHostname(customDomain);
|
||||
}
|
||||
}
|
||||
|
||||
async updateWorkspaceById({
|
||||
payload,
|
||||
userWorkspaceId,
|
||||
@@ -186,7 +111,9 @@ export class WorkspaceService extends TypeOrmQueryService<Workspace> {
|
||||
});
|
||||
|
||||
if (payload.subdomain && workspace.subdomain !== payload.subdomain) {
|
||||
await this.validateSubdomainUpdate(payload.subdomain);
|
||||
await this.subdomainManagerService.validateSubdomainOrThrow(
|
||||
payload.subdomain,
|
||||
);
|
||||
}
|
||||
|
||||
let customDomainRegistered = false;
|
||||
@@ -202,7 +129,10 @@ export class WorkspaceService extends TypeOrmQueryService<Workspace> {
|
||||
payload.customDomain &&
|
||||
workspace.customDomain !== payload.customDomain
|
||||
) {
|
||||
await this.setCustomDomain(workspace, payload.customDomain);
|
||||
await this.customDomainManagerService.setCustomDomain(
|
||||
workspace,
|
||||
payload.customDomain,
|
||||
);
|
||||
customDomainRegistered = true;
|
||||
}
|
||||
|
||||
@@ -410,14 +340,6 @@ export class WorkspaceService extends TypeOrmQueryService<Workspace> {
|
||||
}
|
||||
}
|
||||
|
||||
async isSubdomainAvailable(subdomain: string) {
|
||||
const existingWorkspace = await this.workspaceRepository.findOne({
|
||||
where: { subdomain: subdomain },
|
||||
});
|
||||
|
||||
return !existingWorkspace;
|
||||
}
|
||||
|
||||
private async validateSecurityPermissions({
|
||||
payload,
|
||||
userWorkspaceId,
|
||||
@@ -508,45 +430,4 @@ export class WorkspaceService extends TypeOrmQueryService<Workspace> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async checkCustomDomainValidRecords(
|
||||
workspace: Workspace,
|
||||
domainValidRecord?: DomainValidRecords,
|
||||
) {
|
||||
assertIsDefinedOrThrow(workspace.customDomain);
|
||||
|
||||
const customDomainWithRecords =
|
||||
domainValidRecord ??
|
||||
(await this.dnsManagerService.getHostnameWithRecords(
|
||||
workspace.customDomain,
|
||||
));
|
||||
|
||||
assertIsDefinedOrThrow(customDomainWithRecords);
|
||||
|
||||
const isCustomDomainWorking =
|
||||
await this.dnsManagerService.isHostnameWorking(workspace.customDomain);
|
||||
|
||||
if (workspace.isCustomDomainEnabled !== isCustomDomainWorking) {
|
||||
workspace.isCustomDomainEnabled = isCustomDomainWorking;
|
||||
|
||||
await this.workspaceRepository.save(workspace);
|
||||
|
||||
const analytics = this.auditService.createContext({
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
|
||||
analytics.insertWorkspaceEvent(
|
||||
workspace.isCustomDomainEnabled
|
||||
? CUSTOM_DOMAIN_ACTIVATED_EVENT
|
||||
: CUSTOM_DOMAIN_DEACTIVATED_EVENT,
|
||||
{},
|
||||
);
|
||||
}
|
||||
|
||||
return customDomainWithRecords;
|
||||
}
|
||||
|
||||
async findByCustomDomain(customDomain: string) {
|
||||
return this.workspaceRepository.findOne({ where: { customDomain } });
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -19,6 +19,7 @@ export const workspaceGraphqlApiExceptionHandler = (error: Error) => {
|
||||
throw new NotFoundError(error);
|
||||
case WorkspaceExceptionCode.DOMAIN_ALREADY_TAKEN:
|
||||
case WorkspaceExceptionCode.SUBDOMAIN_ALREADY_TAKEN:
|
||||
case WorkspaceExceptionCode.SUBDOMAIN_NOT_VALID:
|
||||
throw new ConflictError(error);
|
||||
case WorkspaceExceptionCode.ENVIRONMENT_VAR_NOT_ENABLED:
|
||||
case WorkspaceExceptionCode.WORKSPACE_CUSTOM_DOMAIN_DISABLED:
|
||||
|
||||
@@ -5,6 +5,7 @@ export class WorkspaceException extends CustomException<WorkspaceExceptionCode>
|
||||
export enum WorkspaceExceptionCode {
|
||||
SUBDOMAIN_NOT_FOUND = 'SUBDOMAIN_NOT_FOUND',
|
||||
SUBDOMAIN_ALREADY_TAKEN = 'SUBDOMAIN_ALREADY_TAKEN',
|
||||
SUBDOMAIN_NOT_VALID = 'SUBDOMAIN_NOT_VALID',
|
||||
DOMAIN_ALREADY_TAKEN = 'DOMAIN_ALREADY_TAKEN',
|
||||
WORKSPACE_NOT_FOUND = 'WORKSPACE_NOT_FOUND',
|
||||
WORKSPACE_CUSTOM_DOMAIN_DISABLED = 'WORKSPACE_CUSTOM_DOMAIN_DISABLED',
|
||||
|
||||
@@ -10,7 +10,9 @@ import { TokenModule } from 'src/engine/core-modules/auth/token/token.module';
|
||||
import { BillingModule } from 'src/engine/core-modules/billing/billing.module';
|
||||
import { BillingSubscription } from 'src/engine/core-modules/billing/entities/billing-subscription.entity';
|
||||
import { DnsManagerModule } from 'src/engine/core-modules/dns-manager/dns-manager.module';
|
||||
import { DomainManagerModule } from 'src/engine/core-modules/domain-manager/domain-manager.module';
|
||||
import { CustomDomainManagerModule } from 'src/engine/core-modules/domain/custom-domain-manager/custom-domain-manager.module';
|
||||
import { SubdomainManagerModule } from 'src/engine/core-modules/domain/subdomain-manager/subdomain-manager.module';
|
||||
import { WorkspaceDomainsModule } from 'src/engine/core-modules/domain/workspace-domains/workspace-domains.module';
|
||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||
import { FileUploadModule } from 'src/engine/core-modules/file/file-upload/file-upload.module';
|
||||
import { FileModule } from 'src/engine/core-modules/file/file.module';
|
||||
@@ -65,7 +67,9 @@ import { WorkspaceManagerModule } from 'src/engine/workspace-manager/workspace-m
|
||||
RoleModule,
|
||||
AgentModule,
|
||||
DnsManagerModule,
|
||||
DomainManagerModule,
|
||||
WorkspaceDomainsModule,
|
||||
SubdomainManagerModule,
|
||||
CustomDomainManagerModule,
|
||||
ViewModule,
|
||||
WorkspaceManyOrAllFlatEntityMapsCacheModule,
|
||||
],
|
||||
|
||||
@@ -25,7 +25,8 @@ import { BillingSubscription } from 'src/engine/core-modules/billing/entities/bi
|
||||
import { BillingSubscriptionService } from 'src/engine/core-modules/billing/services/billing-subscription.service';
|
||||
import { DomainValidRecords } from 'src/engine/core-modules/dns-manager/dtos/domain-valid-records';
|
||||
import { DnsManagerService } from 'src/engine/core-modules/dns-manager/services/dns-manager.service';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { CustomDomainManagerService } from 'src/engine/core-modules/domain/custom-domain-manager/services/custom-domain-manager.service';
|
||||
import { WorkspaceDomainsService } from 'src/engine/core-modules/domain/workspace-domains/services/workspace-domains.service';
|
||||
import { FeatureFlagDTO } from 'src/engine/core-modules/feature-flag/dtos/feature-flag-dto';
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
@@ -89,7 +90,7 @@ const OriginHeader = createParamDecorator(
|
||||
export class WorkspaceResolver {
|
||||
constructor(
|
||||
private readonly workspaceService: WorkspaceService,
|
||||
private readonly domainManagerService: DomainManagerService,
|
||||
private readonly workspaceDomainsService: WorkspaceDomainsService,
|
||||
private readonly userWorkspaceService: UserWorkspaceService,
|
||||
private readonly twentyConfigService: TwentyConfigService,
|
||||
private readonly fileUploadService: FileUploadService,
|
||||
@@ -100,6 +101,7 @@ export class WorkspaceResolver {
|
||||
private readonly agentService: AgentService,
|
||||
private readonly viewService: ViewService,
|
||||
private readonly dnsManagerService: DnsManagerService,
|
||||
private readonly customDomainManagerService: CustomDomainManagerService,
|
||||
) {}
|
||||
|
||||
@Query(() => Workspace)
|
||||
@@ -295,7 +297,7 @@ export class WorkspaceResolver {
|
||||
|
||||
@ResolveField(() => WorkspaceUrlsDTO)
|
||||
workspaceUrls(@Parent() workspace: Workspace) {
|
||||
return this.domainManagerService.getWorkspaceUrls(workspace);
|
||||
return this.workspaceDomainsService.getWorkspaceUrls(workspace);
|
||||
}
|
||||
|
||||
@ResolveField(() => Boolean)
|
||||
@@ -356,7 +358,7 @@ export class WorkspaceResolver {
|
||||
}
|
||||
|
||||
const workspace =
|
||||
await this.domainManagerService.getWorkspaceByOriginOrDefaultWorkspace(
|
||||
await this.workspaceDomainsService.getWorkspaceByOriginOrDefaultWorkspace(
|
||||
origin,
|
||||
);
|
||||
|
||||
@@ -379,7 +381,7 @@ export class WorkspaceResolver {
|
||||
id: workspace.id,
|
||||
logo: workspaceLogoWithToken,
|
||||
displayName: workspace.displayName,
|
||||
workspaceUrls: this.domainManagerService.getWorkspaceUrls(workspace),
|
||||
workspaceUrls: this.workspaceDomainsService.getWorkspaceUrls(workspace),
|
||||
authProviders: getAuthProvidersByWorkspace({
|
||||
workspace,
|
||||
systemEnabledProviders,
|
||||
@@ -407,7 +409,7 @@ export class WorkspaceResolver {
|
||||
workspace.customDomain,
|
||||
);
|
||||
|
||||
return this.workspaceService.checkCustomDomainValidRecords(
|
||||
return this.customDomainManagerService.checkCustomDomainValidRecords(
|
||||
workspace,
|
||||
domainValidRecords,
|
||||
);
|
||||
|
||||
@@ -17,7 +17,7 @@ import { In } from 'typeorm';
|
||||
import { getAllSelectableFields } from 'src/engine/api/utils/get-all-selectable-fields.utils';
|
||||
import { AIBillingService } from 'src/engine/core-modules/ai/services/ai-billing.service';
|
||||
import { AiModelRegistryService } from 'src/engine/core-modules/ai/services/ai-model-registry.service';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { WorkspaceDomainsService } from 'src/engine/core-modules/domain/workspace-domains/services/workspace-domains.service';
|
||||
import { type Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AgentHandoffToolService } from 'src/engine/metadata-modules/agent/agent-handoff-tool.service';
|
||||
import { AgentService } from 'src/engine/metadata-modules/agent/agent.service';
|
||||
@@ -47,7 +47,7 @@ export class AgentExecutionService implements AgentExecutionContext {
|
||||
|
||||
constructor(
|
||||
private readonly agentHandoffToolService: AgentHandoffToolService,
|
||||
private readonly domainManagerService: DomainManagerService,
|
||||
private readonly workspaceDomainsService: WorkspaceDomainsService,
|
||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
private readonly workspacePermissionsCacheService: WorkspacePermissionsCacheService,
|
||||
private readonly aiModelRegistryService: AiModelRegistryService,
|
||||
@@ -218,7 +218,7 @@ export class AgentExecutionService implements AgentExecutionContext {
|
||||
).map((record) => {
|
||||
return {
|
||||
...record,
|
||||
resourceUrl: this.domainManagerService.buildWorkspaceURL({
|
||||
resourceUrl: this.workspaceDomainsService.buildWorkspaceURL({
|
||||
workspace,
|
||||
pathname: getAppPath(AppPath.RecordShowPage, {
|
||||
objectNameSingular:
|
||||
|
||||
@@ -4,7 +4,7 @@ import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { AiModule } from 'src/engine/core-modules/ai/ai.module';
|
||||
import { AuditModule } from 'src/engine/core-modules/audit/audit.module';
|
||||
import { TokenModule } from 'src/engine/core-modules/auth/token/token.module';
|
||||
import { DomainManagerModule } from 'src/engine/core-modules/domain-manager/domain-manager.module';
|
||||
import { WorkspaceDomainsModule } from 'src/engine/core-modules/domain/workspace-domains/workspace-domains.module';
|
||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||
import { FileEntity } from 'src/engine/core-modules/file/entities/file.entity';
|
||||
import { FileUploadModule } from 'src/engine/core-modules/file/file-upload/file-upload.module';
|
||||
@@ -68,7 +68,7 @@ import { AgentActorContextService } from './services/agent-actor-context.service
|
||||
WorkspacePermissionsCacheModule,
|
||||
WorkspaceCacheStorageModule,
|
||||
TokenModule,
|
||||
DomainManagerModule,
|
||||
WorkspaceDomainsModule,
|
||||
WorkflowToolsModule,
|
||||
UserWorkspaceModule,
|
||||
UserRoleModule,
|
||||
|
||||
+2
-2
@@ -2,7 +2,7 @@ import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { AuthModule } from 'src/engine/core-modules/auth/auth.module';
|
||||
import { DomainManagerModule } from 'src/engine/core-modules/domain-manager/domain-manager.module';
|
||||
import { WorkspaceDomainsModule } from 'src/engine/core-modules/domain/workspace-domains/workspace-domains.module';
|
||||
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
|
||||
import { RouteTriggerResolver } from 'src/engine/metadata-modules/route-trigger/resolvers/route-trigger.resolver';
|
||||
import { RouteTriggerController } from 'src/engine/metadata-modules/route-trigger/route-trigger.controller';
|
||||
@@ -17,7 +17,7 @@ import { WorkspaceMigrationV2Module } from 'src/engine/workspace-manager/workspa
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([RouteTrigger]),
|
||||
AuthModule,
|
||||
DomainManagerModule,
|
||||
WorkspaceDomainsModule,
|
||||
ServerlessFunctionModule,
|
||||
WorkspaceManyOrAllFlatEntityMapsCacheModule,
|
||||
WorkspaceMigrationV2Module,
|
||||
|
||||
+9
-9
@@ -1,29 +1,29 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Repository } from 'typeorm';
|
||||
import { Request } from 'express';
|
||||
import { match } from 'path-to-regexp';
|
||||
import { assertIsDefinedOrThrow, isDefined } from 'twenty-shared/utils';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import {
|
||||
HTTPMethod,
|
||||
RouteTrigger,
|
||||
} from 'src/engine/metadata-modules/route-trigger/route-trigger.entity';
|
||||
import { AccessTokenService } from 'src/engine/core-modules/auth/token/services/access-token.service';
|
||||
import { ServerlessFunctionService } from 'src/engine/metadata-modules/serverless-function/serverless-function.service';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { WorkspaceDomainsService } from 'src/engine/core-modules/domain/workspace-domains/services/workspace-domains.service';
|
||||
import {
|
||||
RouteTriggerException,
|
||||
RouteTriggerExceptionCode,
|
||||
} from 'src/engine/metadata-modules/route-trigger/exceptions/route-trigger.exception';
|
||||
import {
|
||||
HTTPMethod,
|
||||
RouteTrigger,
|
||||
} from 'src/engine/metadata-modules/route-trigger/route-trigger.entity';
|
||||
import { ServerlessFunctionService } from 'src/engine/metadata-modules/serverless-function/serverless-function.service';
|
||||
|
||||
@Injectable()
|
||||
export class RouteTriggerService {
|
||||
constructor(
|
||||
private readonly accessTokenService: AccessTokenService,
|
||||
private readonly serverlessFunctionService: ServerlessFunctionService,
|
||||
private readonly domainManagerService: DomainManagerService,
|
||||
private readonly workspaceDomainsService: WorkspaceDomainsService,
|
||||
@InjectRepository(RouteTrigger)
|
||||
private readonly routeTriggerRepository: Repository<RouteTrigger>,
|
||||
) {}
|
||||
@@ -41,7 +41,7 @@ export class RouteTriggerService {
|
||||
const host = `${request.protocol}://${request.get('host')}`;
|
||||
|
||||
const workspace =
|
||||
await this.domainManagerService.getWorkspaceByOriginOrDefaultWorkspace(
|
||||
await this.workspaceDomainsService.getWorkspaceByOriginOrDefaultWorkspace(
|
||||
host,
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user