From 2b0b62235e640fe0b3b9e544373e2e997701fa94 Mon Sep 17 00:00:00 2001 From: Brahm Lower Date: Mon, 13 Jul 2026 07:55:38 -0700 Subject: [PATCH] fix: validation link for access-domains deeplinks to Invite tab (#22845) The "validate domain" link sent in the email when adding an Access Domain wasn't working because the link didn't deep link to the Invite tab URLs are now built to include the that hash property to deep link to the target tab. Before: ``` https://example.com/settings/members?wtdId=&validationToken= ``` After: ``` https://example.com/settings/members?wtdId=&validationToken=#invite ``` Review in cubic --- .../approved-access-domain.service.ts | 1 + .../services/approved-access-domain.spec.ts | 1 + ...rl-with-pathname-and-search-params.util.ts | 6 ++++ .../workspace-domains.service.spec.ts | 32 +++++++++++++++++++ .../services/workspace-domains.service.ts | 3 ++ 5 files changed, 43 insertions(+) diff --git a/packages/twenty-server/src/engine/core-modules/approved-access-domain/services/approved-access-domain.service.ts b/packages/twenty-server/src/engine/core-modules/approved-access-domain/services/approved-access-domain.service.ts index fee9853109..94b0f35068 100644 --- a/packages/twenty-server/src/engine/core-modules/approved-access-domain/services/approved-access-domain.service.ts +++ b/packages/twenty-server/src/engine/core-modules/approved-access-domain/services/approved-access-domain.service.ts @@ -86,6 +86,7 @@ export class ApprovedAccessDomainService { workspaceId: workspace.id, }), }, + hash: 'invite', }); if (!isDefined(sender.userEmail)) { diff --git a/packages/twenty-server/src/engine/core-modules/approved-access-domain/services/approved-access-domain.spec.ts b/packages/twenty-server/src/engine/core-modules/approved-access-domain/services/approved-access-domain.spec.ts index ebb79bc87b..359227144c 100644 --- a/packages/twenty-server/src/engine/core-modules/approved-access-domain/services/approved-access-domain.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/approved-access-domain/services/approved-access-domain.spec.ts @@ -344,6 +344,7 @@ describe('ApprovedAccessDomainService', () => { wtdId: approvedAccessDomain.id, validationToken: 'signed.jwt.token', }, + hash: 'invite', }); expect(emailService.send).toHaveBeenCalledWith({ diff --git a/packages/twenty-server/src/engine/core-modules/domain/domain-server-config/utils/build-url-with-pathname-and-search-params.util.ts b/packages/twenty-server/src/engine/core-modules/domain/domain-server-config/utils/build-url-with-pathname-and-search-params.util.ts index bea0a0b890..f8053deb71 100644 --- a/packages/twenty-server/src/engine/core-modules/domain/domain-server-config/utils/build-url-with-pathname-and-search-params.util.ts +++ b/packages/twenty-server/src/engine/core-modules/domain/domain-server-config/utils/build-url-with-pathname-and-search-params.util.ts @@ -4,12 +4,14 @@ type BuildUrlWithPathnameAndSearchParamsProps = { baseUrl: URL; pathname?: string; searchParams?: Record; + hash?: string; }; export const buildUrlWithPathnameAndSearchParams = ({ baseUrl, pathname, searchParams, + hash, }: BuildUrlWithPathnameAndSearchParamsProps) => { const url = baseUrl; @@ -21,5 +23,9 @@ export const buildUrlWithPathnameAndSearchParams = ({ appendSearchParamsToUrl(url, searchParams); } + if (hash) { + url.hash = hash; + } + return url; }; diff --git a/packages/twenty-server/src/engine/core-modules/domain/workspace-domains/services/__test__/workspace-domains.service.spec.ts b/packages/twenty-server/src/engine/core-modules/domain/workspace-domains/services/__test__/workspace-domains.service.spec.ts index 81edd7578c..49cc3e6523 100644 --- a/packages/twenty-server/src/engine/core-modules/domain/workspace-domains/services/__test__/workspace-domains.service.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/domain/workspace-domains/services/__test__/workspace-domains.service.spec.ts @@ -183,6 +183,38 @@ describe('WorkspaceDomainsService', () => { expect(result.searchParams.get('foo')).toBe('bar'); expect(result.searchParams.get('baz')).toBe('123'); }); + + it('should set the hash if 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 = workspaceDomainsService.buildWorkspaceURL({ + workspace: { + subdomain: 'test', + customDomain: null, + isCustomDomainEnabled: false, + }, + pathname: '/settings/members', + searchParams: { + wtdId: 'domain-id', + }, + hash: 'invite', + }); + + expect(result.hash).toBe('#invite'); + expect(result.searchParams.get('wtdId')).toBe('domain-id'); + expect(result.toString()).toBe( + 'https://example.com/settings/members?wtdId=domain-id#invite', + ); + }); }); describe('getWorkspaceByOriginOrDefaultWorkspace', () => { diff --git a/packages/twenty-server/src/engine/core-modules/domain/workspace-domains/services/workspace-domains.service.ts b/packages/twenty-server/src/engine/core-modules/domain/workspace-domains/services/workspace-domains.service.ts index fb6ea4d892..cfcc76461f 100644 --- a/packages/twenty-server/src/engine/core-modules/domain/workspace-domains/services/workspace-domains.service.ts +++ b/packages/twenty-server/src/engine/core-modules/domain/workspace-domains/services/workspace-domains.service.ts @@ -31,10 +31,12 @@ export class WorkspaceDomainsService { workspace, pathname, searchParams, + hash, }: { workspace: WorkspaceDomainConfig; pathname?: string; searchParams?: Record; + hash?: string; }) { const workspaceUrls = this.getWorkspaceUrls(workspace); @@ -42,6 +44,7 @@ export class WorkspaceDomainsService { baseUrl: new URL(workspaceUrls.customUrl ?? workspaceUrls.subdomainUrl), pathname, searchParams, + hash, }); return url;