From b1da1d97a2ffbc7785a7318435ed0a266fb68525 Mon Sep 17 00:00:00 2001 From: Chuck Driesler Date: Mon, 31 Mar 2025 13:36:22 +0100 Subject: [PATCH] fix(workspaces): domain compliance by slug (#4295) --- .../typedefs/workspaces.graphql | 2 +- .../modules/core/graph/generated/graphql.ts | 2 +- .../graph/generated/graphql.ts | 2 +- .../workspaces/graph/resolvers/workspaces.ts | 14 ++-- .../modules/workspaces/services/domains.ts | 24 +++---- .../tests/unit/services/domains.spec.ts | 66 ++++++++----------- .../server/test/graphql/generated/graphql.ts | 2 +- 7 files changed, 52 insertions(+), 60 deletions(-) diff --git a/packages/server/assets/workspacesCore/typedefs/workspaces.graphql b/packages/server/assets/workspacesCore/typedefs/workspaces.graphql index 61be3df63..b65c0efa7 100644 --- a/packages/server/assets/workspacesCore/typedefs/workspaces.graphql +++ b/packages/server/assets/workspacesCore/typedefs/workspaces.graphql @@ -571,7 +571,7 @@ extend type Project { # case of using userSearch, and we alway expose this extend type LimitedUser { - workspaceDomainPolicyCompliant(workspaceId: String): Boolean + workspaceDomainPolicyCompliant(workspaceSlug: String): Boolean workspaceRole(workspaceId: String): String # if workspaceId is undefined | null, just return undefined # this can be implemented by the workspaceCore resolver too, to avoid frontend component duplication diff --git a/packages/server/modules/core/graph/generated/graphql.ts b/packages/server/modules/core/graph/generated/graphql.ts index 568341857..d3340f710 100644 --- a/packages/server/modules/core/graph/generated/graphql.ts +++ b/packages/server/modules/core/graph/generated/graphql.ts @@ -1183,7 +1183,7 @@ export type LimitedUserTimelineArgs = { * to another user */ export type LimitedUserWorkspaceDomainPolicyCompliantArgs = { - workspaceId?: InputMaybe; + workspaceSlug?: InputMaybe; }; diff --git a/packages/server/modules/cross-server-sync/graph/generated/graphql.ts b/packages/server/modules/cross-server-sync/graph/generated/graphql.ts index 47df08982..467eef02a 100644 --- a/packages/server/modules/cross-server-sync/graph/generated/graphql.ts +++ b/packages/server/modules/cross-server-sync/graph/generated/graphql.ts @@ -1163,7 +1163,7 @@ export type LimitedUserTimelineArgs = { * to another user */ export type LimitedUserWorkspaceDomainPolicyCompliantArgs = { - workspaceId?: InputMaybe; + workspaceSlug?: InputMaybe; }; diff --git a/packages/server/modules/workspaces/graph/resolvers/workspaces.ts b/packages/server/modules/workspaces/graph/resolvers/workspaces.ts index 7cc801ab7..131607323 100644 --- a/packages/server/modules/workspaces/graph/resolvers/workspaces.ts +++ b/packages/server/modules/workspaces/graph/resolvers/workspaces.ts @@ -1430,15 +1430,15 @@ export = FF_WORKSPACES_MODULE_ENABLED }, LimitedUser: { workspaceDomainPolicyCompliant: async (parent, args) => { - const workspaceId = args.workspaceId - if (!workspaceId) return null - - const userId = parent.id + const { id: userId } = parent + const { workspaceSlug } = args + if (!workspaceSlug) return null return await isUserWorkspaceDomainPolicyCompliantFactory({ - findEmailsByUserId: findEmailsByUserIdFactory({ db }), - getWorkspaceWithDomains: getWorkspaceWithDomainsFactory({ db }) - })({ workspaceId, userId }) + getWorkspaceBySlug: getWorkspaceBySlugFactory({ db }), + getWorkspaceDomains: getWorkspaceDomainsFactory({ db }), + findEmailsByUserId: findEmailsByUserIdFactory({ db }) + })({ workspaceSlug, userId }) }, workspaceRole: async (parent, args, context) => { const workspaceId = args.workspaceId diff --git a/packages/server/modules/workspaces/services/domains.ts b/packages/server/modules/workspaces/services/domains.ts index 78c7560c2..e7e0046df 100644 --- a/packages/server/modules/workspaces/services/domains.ts +++ b/packages/server/modules/workspaces/services/domains.ts @@ -1,42 +1,44 @@ import { FindEmailsByUserId } from '@/modules/core/domain/userEmails/operations' import { userEmailsCompliantWithWorkspaceDomains } from '@/modules/workspaces/domain/logic' import { - GetWorkspaceWithDomains, DeleteWorkspaceDomain, CountDomainsByWorkspaceId, - UpdateWorkspace + UpdateWorkspace, + GetWorkspaceBySlug, + GetWorkspaceDomains } from '@/modules/workspaces/domain/operations' import { WorkspaceNotFoundError } from '@/modules/workspaces/errors/workspace' export const isUserWorkspaceDomainPolicyCompliantFactory = ({ - getWorkspaceWithDomains, + getWorkspaceBySlug, + getWorkspaceDomains, findEmailsByUserId }: { - getWorkspaceWithDomains: GetWorkspaceWithDomains + getWorkspaceBySlug: GetWorkspaceBySlug + getWorkspaceDomains: GetWorkspaceDomains findEmailsByUserId: FindEmailsByUserId }) => async ({ - workspaceId, + workspaceSlug, userId }: { - workspaceId: string + workspaceSlug: string userId: string }): Promise => { - const workspace = await getWorkspaceWithDomains({ - id: workspaceId - }) - // maybe we should throw + const workspace = await getWorkspaceBySlug({ workspaceSlug }) if (!workspace) throw new WorkspaceNotFoundError() // if workspace is not protected, the value is not true, its an empty response if (!workspace.domainBasedMembershipProtectionEnabled) return null + const workspaceDomains = await getWorkspaceDomains({ workspaceIds: [workspace.id] }) + const userEmails = await findEmailsByUserId({ userId }) return userEmailsCompliantWithWorkspaceDomains({ userEmails, - workspaceDomains: workspace.domains + workspaceDomains }) } diff --git a/packages/server/modules/workspaces/tests/unit/services/domains.spec.ts b/packages/server/modules/workspaces/tests/unit/services/domains.spec.ts index 6d6d4fd1a..5c1cd2b5e 100644 --- a/packages/server/modules/workspaces/tests/unit/services/domains.spec.ts +++ b/packages/server/modules/workspaces/tests/unit/services/domains.spec.ts @@ -1,5 +1,6 @@ import { WorkspaceNotFoundError } from '@/modules/workspaces/errors/workspace' import { isUserWorkspaceDomainPolicyCompliantFactory } from '@/modules/workspaces/services/domains' +import { Workspace } from '@/modules/workspacesCore/domain/types' import { expectToThrow } from '@/test/assertionHelper' import { expect } from 'chai' import cryptoRandomString from 'crypto-random-string' @@ -9,10 +10,11 @@ describe('workspace domain services', () => { it('throws WorkspaceNotFoundError', async () => { const error = await expectToThrow(async () => { await isUserWorkspaceDomainPolicyCompliantFactory({ - getWorkspaceWithDomains: async () => null, + getWorkspaceBySlug: async () => null, + getWorkspaceDomains: async () => [], findEmailsByUserId: async () => [] })({ - workspaceId: cryptoRandomString({ length: 10 }), + workspaceSlug: cryptoRandomString({ length: 10 }), userId: cryptoRandomString({ length: 10 }) }) }) @@ -20,21 +22,15 @@ describe('workspace domain services', () => { }) it('returns null if the workspace is not domain protected', async () => { const isCompliant = await isUserWorkspaceDomainPolicyCompliantFactory({ - getWorkspaceWithDomains: async () => ({ - name: cryptoRandomString({ length: 10 }), - logo: null, - slug: cryptoRandomString({ length: 10 }), - createdAt: new Date(), - updatedAt: new Date(), - description: '', - discoverabilityEnabled: false, - domainBasedMembershipProtectionEnabled: false, - domains: [], - id: cryptoRandomString({ length: 10 }) - }), + getWorkspaceBySlug: async () => + ({ + id: cryptoRandomString({ length: 10 }), + domainBasedMembershipProtectionEnabled: false + } as Workspace), + getWorkspaceDomains: async () => [], findEmailsByUserId: async () => [] })({ - workspaceId: cryptoRandomString({ length: 10 }), + workspaceSlug: cryptoRandomString({ length: 10 }), userId: cryptoRandomString({ length: 10 }) }) expect(isCompliant).to.be.null @@ -42,28 +38,22 @@ describe('workspace domain services', () => { it('returns validation result from compliance check', async () => { const domain = 'example.com' const isCompliant = await isUserWorkspaceDomainPolicyCompliantFactory({ - getWorkspaceWithDomains: async () => ({ - name: cryptoRandomString({ length: 10 }), - logo: null, - slug: cryptoRandomString({ length: 10 }), - createdAt: new Date(), - updatedAt: new Date(), - description: '', - discoverabilityEnabled: false, - domainBasedMembershipProtectionEnabled: true, - domains: [ - { - createdAt: new Date(), - createdByUserId: cryptoRandomString({ length: 10 }), - domain, - id: cryptoRandomString({ length: 10 }), - updatedAt: new Date(), - verified: true, - workspaceId: cryptoRandomString({ length: 10 }) - } - ], - id: cryptoRandomString({ length: 10 }) - }), + getWorkspaceBySlug: async () => + ({ + id: cryptoRandomString({ length: 10 }), + domainBasedMembershipProtectionEnabled: true + } as Workspace), + getWorkspaceDomains: async () => [ + { + createdAt: new Date(), + createdByUserId: cryptoRandomString({ length: 10 }), + domain, + id: cryptoRandomString({ length: 10 }), + updatedAt: new Date(), + verified: true, + workspaceId: cryptoRandomString({ length: 10 }) + } + ], findEmailsByUserId: async () => [ { createdAt: new Date(), @@ -77,7 +67,7 @@ describe('workspace domain services', () => { ] })({ userId: cryptoRandomString({ length: 10 }), - workspaceId: cryptoRandomString({ length: 10 }) + workspaceSlug: cryptoRandomString({ length: 10 }) }) expect(isCompliant).to.be.true }) diff --git a/packages/server/test/graphql/generated/graphql.ts b/packages/server/test/graphql/generated/graphql.ts index 2971d32c3..f985771f6 100644 --- a/packages/server/test/graphql/generated/graphql.ts +++ b/packages/server/test/graphql/generated/graphql.ts @@ -1164,7 +1164,7 @@ export type LimitedUserTimelineArgs = { * to another user */ export type LimitedUserWorkspaceDomainPolicyCompliantArgs = { - workspaceId?: InputMaybe; + workspaceSlug?: InputMaybe; };