fix(dns-manager): throw proper DnsManagerException when hostname not found in Cloudflare
https://sonarly.com/issue/33567?type=bug When a user checks DNS records for their custom domain, and the hostname is not registered in Cloudflare's custom hostnames despite being set in the workspace database, `refreshHostname()` crashes with a generic "Value not defined" error instead of a proper domain-specific exception.
This commit is contained in:
+11
-4
@@ -6,19 +6,26 @@ import {
|
||||
DnsManagerException,
|
||||
DnsManagerExceptionCode,
|
||||
} from 'src/engine/core-modules/dns-manager/exceptions/dns-manager.exception';
|
||||
import {
|
||||
BaseGraphQLError,
|
||||
ConflictError,
|
||||
NotFoundError,
|
||||
} from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
|
||||
|
||||
@Catch(DnsManagerException)
|
||||
export class DnsManagerExceptionFilter implements ExceptionFilter {
|
||||
catch(exception: DnsManagerException) {
|
||||
switch (exception.code) {
|
||||
case DnsManagerExceptionCode.INTERNAL_SERVER_ERROR:
|
||||
case DnsManagerExceptionCode.HOSTNAME_ALREADY_REGISTERED:
|
||||
case DnsManagerExceptionCode.HOSTNAME_NOT_REGISTERED:
|
||||
case DnsManagerExceptionCode.MISSING_PUBLIC_DOMAIN_URL:
|
||||
throw new NotFoundError(exception);
|
||||
case DnsManagerExceptionCode.HOSTNAME_ALREADY_REGISTERED:
|
||||
throw new ConflictError(exception);
|
||||
case DnsManagerExceptionCode.INVALID_INPUT_DATA:
|
||||
case DnsManagerExceptionCode.CLOUDFLARE_CLIENT_NOT_INITIALIZED:
|
||||
case DnsManagerExceptionCode.MULTIPLE_HOSTNAMES_FOUND:
|
||||
case DnsManagerExceptionCode.MISSING_PUBLIC_DOMAIN_URL:
|
||||
throw exception;
|
||||
case DnsManagerExceptionCode.INTERNAL_SERVER_ERROR:
|
||||
throw new BaseGraphQLError(exception);
|
||||
default: {
|
||||
assertUnreachable(exception.code);
|
||||
}
|
||||
|
||||
+57
@@ -288,6 +288,63 @@ describe('DnsManagerService', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('refreshHostname', () => {
|
||||
it('should throw DnsManagerException when hostname is not found in Cloudflare', async () => {
|
||||
const hostname = 'example.com';
|
||||
const cloudflareMock = {
|
||||
customHostnames: {
|
||||
list: jest.fn().mockResolvedValueOnce({ result: [] }),
|
||||
},
|
||||
};
|
||||
|
||||
jest.spyOn(twentyConfigService, 'get').mockReturnValue('test-zone-id');
|
||||
(dnsManagerService as any).cloudflareClient = cloudflareMock;
|
||||
|
||||
await expect(
|
||||
dnsManagerService.refreshHostname(hostname),
|
||||
).rejects.toThrow(DnsManagerException);
|
||||
});
|
||||
|
||||
it('should refresh and return hostname records when hostname exists', async () => {
|
||||
const hostname = 'example.com';
|
||||
const mockResult = {
|
||||
id: 'custom-id',
|
||||
hostname,
|
||||
verification_errors: [],
|
||||
ssl: {
|
||||
dcv_delegation_records: [],
|
||||
},
|
||||
};
|
||||
const cloudflareMock = {
|
||||
customHostnames: {
|
||||
list: jest.fn().mockResolvedValueOnce({ result: [mockResult] }),
|
||||
edit: jest.fn().mockResolvedValueOnce({}),
|
||||
},
|
||||
};
|
||||
|
||||
jest.spyOn(twentyConfigService, 'get').mockReturnValue('test-zone-id');
|
||||
jest
|
||||
.spyOn(domainServerConfigService, 'getBaseUrl')
|
||||
.mockReturnValue(new URL('https://front.domain'));
|
||||
(dnsManagerService as any).cloudflareClient = cloudflareMock;
|
||||
|
||||
const result = await dnsManagerService.refreshHostname(hostname);
|
||||
|
||||
expect(result).toEqual({
|
||||
id: 'custom-id',
|
||||
domain: hostname,
|
||||
records: expect.any(Array),
|
||||
});
|
||||
expect(cloudflareMock.customHostnames.edit).toHaveBeenCalledWith(
|
||||
'custom-id',
|
||||
{
|
||||
zone_id: 'test-zone-id',
|
||||
ssl: expect.any(Object),
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateHostname', () => {
|
||||
it('should update a custom domain and register a new one', async () => {
|
||||
const fromHostname = 'old.com';
|
||||
|
||||
+10
-1
@@ -138,7 +138,16 @@ export class DnsManagerService {
|
||||
options,
|
||||
);
|
||||
|
||||
assertIsDefinedOrThrow(publicDomainWithRecords);
|
||||
assertIsDefinedOrThrow(
|
||||
publicDomainWithRecords,
|
||||
new DnsManagerException(
|
||||
'Hostname not found in Cloudflare',
|
||||
DnsManagerExceptionCode.HOSTNAME_NOT_REGISTERED,
|
||||
{
|
||||
userFriendlyMessage: msg`Domain is not registered in Cloudflare`,
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
await this.cloudflareClient.customHostnames.edit(
|
||||
publicDomainWithRecords.id,
|
||||
|
||||
@@ -8,6 +8,7 @@ import { PermissionFlagType } from 'twenty-shared/constants';
|
||||
|
||||
import { MetadataResolver } from 'src/engine/api/graphql/graphql-config/decorators/metadata-resolver.decorator';
|
||||
import { DomainValidRecords } from 'src/engine/core-modules/dns-manager/dtos/domain-valid-records';
|
||||
import { DnsManagerExceptionFilter } from 'src/engine/core-modules/dns-manager/exceptions/dns-manager-exception-filter';
|
||||
import { DnsManagerService } from 'src/engine/core-modules/dns-manager/services/dns-manager.service';
|
||||
import { PreventNestToAutoLogGraphqlErrorsFilter } from 'src/engine/core-modules/graphql/filters/prevent-nest-to-auto-log-graphql-errors.filter';
|
||||
import { ResolverValidationPipe } from 'src/engine/core-modules/graphql/pipes/resolver-validation.pipe';
|
||||
@@ -31,6 +32,7 @@ import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
)
|
||||
@UsePipes(ResolverValidationPipe)
|
||||
@UseFilters(
|
||||
DnsManagerExceptionFilter,
|
||||
PublicDomainExceptionFilter,
|
||||
PreventNestToAutoLogGraphqlErrorsFilter,
|
||||
)
|
||||
|
||||
@@ -23,6 +23,7 @@ import { BillingEntitlementDTO } from 'src/engine/core-modules/billing/dtos/bill
|
||||
import { BillingSubscriptionEntity } from 'src/engine/core-modules/billing/entities/billing-subscription.entity';
|
||||
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 { DnsManagerExceptionFilter } from 'src/engine/core-modules/dns-manager/exceptions/dns-manager-exception-filter';
|
||||
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 { WorkspaceDomainsService } from 'src/engine/core-modules/domain/workspace-domains/services/workspace-domains.service';
|
||||
@@ -82,6 +83,7 @@ const OriginHeader = createParamDecorator(
|
||||
@MetadataResolver(() => WorkspaceEntity)
|
||||
@UsePipes(ResolverValidationPipe)
|
||||
@UseFilters(
|
||||
DnsManagerExceptionFilter,
|
||||
PreventNestToAutoLogGraphqlErrorsFilter,
|
||||
PermissionsGraphqlApiExceptionFilter,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user