feat(workspaces): do not allow discoverablity if no verified domains (#2847)
* feat(workspaces): do not allow discoverablity if no verified domains * test(workspaces): fix tests
This commit is contained in:
@@ -9,16 +9,25 @@ export class WorkspaceAdminRequiredError extends BaseError {
|
||||
export class WorkspaceInvalidRoleError extends BaseError {
|
||||
static defaultMessage = 'Invalid workspace role provided'
|
||||
static code = 'WORKSPACE_INVALID_ROLE_ERROR'
|
||||
static statusCode = 400
|
||||
}
|
||||
|
||||
export class WorkspaceInvalidLogoError extends BaseError {
|
||||
static defaultMessage = 'Provided logo is not valid'
|
||||
static code = 'WORKSPACE_INVALID_LOGO_ERROR'
|
||||
static statusCode = 400
|
||||
}
|
||||
|
||||
export class WorkspaceInvalidDescriptionError extends BaseError {
|
||||
static defaultMessage = 'Provided description is too long'
|
||||
static code = 'WORKSPACE_INVALID_DESCRIPTION_ERROR'
|
||||
static statusCode = 400
|
||||
}
|
||||
|
||||
export class WorkspaceNoVerifiedDomainsError extends BaseError {
|
||||
static defaultMessage = 'Invalid operation, the workspace has no verified domains'
|
||||
static code = 'WORKSPACE_NO_VERIFIED_DOMAINS'
|
||||
static statusCode = 400
|
||||
}
|
||||
|
||||
export class WorkspaceQueryError extends BaseError {
|
||||
|
||||
@@ -315,7 +315,7 @@ export = FF_WORKSPACES_MODULE_ENABLED
|
||||
)
|
||||
|
||||
const updateWorkspace = updateWorkspaceFactory({
|
||||
getWorkspace: getWorkspaceFactory({ db }),
|
||||
getWorkspace: getWorkspaceWithDomainsFactory({ db }),
|
||||
upsertWorkspace: upsertWorkspaceFactory({ db }),
|
||||
emitWorkspaceEvent: getEventBus().emit
|
||||
})
|
||||
|
||||
@@ -30,7 +30,8 @@ import {
|
||||
WorkspaceNotFoundError,
|
||||
WorkspaceProtectedError,
|
||||
WorkspaceUnverifiedDomainError,
|
||||
WorkspaceInvalidDescriptionError
|
||||
WorkspaceInvalidDescriptionError,
|
||||
WorkspaceNoVerifiedDomainsError
|
||||
} from '@/modules/workspaces/errors/workspace'
|
||||
import { isUserLastWorkspaceAdmin } from '@/modules/workspaces/helpers/roles'
|
||||
import { EventBus } from '@/modules/shared/services/eventBus'
|
||||
@@ -50,7 +51,7 @@ import { blockedDomains } from '@/modules/workspaces/helpers/blockedDomains'
|
||||
import { DeleteAllResourceInvites } from '@/modules/serverinvites/domain/operations'
|
||||
import { WorkspaceInviteResourceType } from '@/modules/workspaces/domain/constants'
|
||||
import { ProjectInviteResourceType } from '@/modules/serverinvites/domain/constants'
|
||||
import { chunk, isEmpty } from 'lodash'
|
||||
import { chunk, isEmpty, omit } from 'lodash'
|
||||
import {
|
||||
DeleteProjectRole,
|
||||
UpsertProjectRole
|
||||
@@ -137,13 +138,13 @@ export const updateWorkspaceFactory =
|
||||
upsertWorkspace,
|
||||
emitWorkspaceEvent
|
||||
}: {
|
||||
getWorkspace: GetWorkspace
|
||||
getWorkspace: GetWorkspaceWithDomains
|
||||
upsertWorkspace: UpsertWorkspace
|
||||
emitWorkspaceEvent: EventBus['emit']
|
||||
}) =>
|
||||
async ({ workspaceId, workspaceInput }: WorkspaceUpdateArgs): Promise<Workspace> => {
|
||||
// Get existing workspace to merge with incoming changes
|
||||
const currentWorkspace = await getWorkspace({ workspaceId })
|
||||
const currentWorkspace = await getWorkspace({ id: workspaceId })
|
||||
if (!currentWorkspace) {
|
||||
throw new WorkspaceNotFoundError()
|
||||
}
|
||||
@@ -160,8 +161,15 @@ export const updateWorkspaceFactory =
|
||||
throw new WorkspaceInvalidDescriptionError()
|
||||
}
|
||||
|
||||
if (
|
||||
workspaceInput.discoverabilityEnabled &&
|
||||
!currentWorkspace.discoverabilityEnabled &&
|
||||
!currentWorkspace.domains.find((domain) => domain.verified)
|
||||
)
|
||||
throw new WorkspaceNoVerifiedDomainsError()
|
||||
|
||||
const workspace = {
|
||||
...currentWorkspace,
|
||||
...omit(currentWorkspace, 'domains'),
|
||||
...removeNullOrUndefinedKeys(workspaceInput),
|
||||
updatedAt: new Date()
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ export const createTestWorkspace = async (
|
||||
workspace.id = newWorkspace.id
|
||||
if (workspace.discoverabilityEnabled) {
|
||||
const updateWorkspace = updateWorkspaceFactory({
|
||||
getWorkspace: getWorkspaceFactory({ db }),
|
||||
getWorkspace: getWorkspaceWithDomainsFactory({ db }),
|
||||
upsertWorkspace: upsertWorkspaceFactory({ db }),
|
||||
emitWorkspaceEvent: (...args) => getEventBus().emit(...args)
|
||||
})
|
||||
@@ -93,7 +93,7 @@ export const createTestWorkspace = async (
|
||||
|
||||
if (workspace.domainBasedMembershipProtectionEnabled) {
|
||||
await updateWorkspaceFactory({
|
||||
getWorkspace: getWorkspaceFactory({ db }),
|
||||
getWorkspace: getWorkspaceWithDomainsFactory({ db }),
|
||||
upsertWorkspace: upsertWorkspaceFactory({ db }),
|
||||
emitWorkspaceEvent: getEventBus().emit
|
||||
})({
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import {
|
||||
Workspace,
|
||||
WorkspaceAcl,
|
||||
WorkspaceDomain
|
||||
WorkspaceDomain,
|
||||
WorkspaceWithDomains
|
||||
} from '@/modules/workspacesCore/domain/types'
|
||||
import {
|
||||
addDomainToWorkspaceFactory,
|
||||
createWorkspaceFactory,
|
||||
deleteWorkspaceRoleFactory,
|
||||
updateWorkspaceFactory,
|
||||
updateWorkspaceRoleFactory
|
||||
} from '@/modules/workspaces/services/management'
|
||||
import { Roles } from '@speckle/shared'
|
||||
@@ -19,11 +21,13 @@ import { createRandomPassword } from '@/modules/core/helpers/testHelpers'
|
||||
import {
|
||||
WorkspaceAdminRequiredError,
|
||||
WorkspaceDomainBlockedError,
|
||||
WorkspaceNotFoundError,
|
||||
WorkspaceNoVerifiedDomainsError,
|
||||
WorkspaceProtectedError,
|
||||
WorkspaceUnverifiedDomainError
|
||||
} from '@/modules/workspaces/errors/workspace'
|
||||
import { UserEmail } from '@/modules/core/domain/userEmails/types'
|
||||
import { omit } from 'lodash'
|
||||
import { merge, omit } from 'lodash'
|
||||
import { GetWorkspaceWithDomains } from '@/modules/workspaces/domain/operations'
|
||||
import { FindVerifiedEmailsByUserId } from '@/modules/core/domain/userEmails/operations'
|
||||
import { EventNames } from '@/modules/shared/services/eventBus'
|
||||
@@ -137,6 +141,163 @@ describe('Workspace services', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
describe('updateWorkspaceFactory creates a function, that', () => {
|
||||
const createTestWorkspaceWithDomainsData = (
|
||||
input: Partial<WorkspaceWithDomains> = {}
|
||||
): WorkspaceWithDomains => {
|
||||
const workspaceId = cryptoRandomString({ length: 10 })
|
||||
const workspace: WorkspaceWithDomains = {
|
||||
id: workspaceId,
|
||||
name: cryptoRandomString({ length: 10 }),
|
||||
description: cryptoRandomString({ length: 20 }),
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
logo: null,
|
||||
defaultLogoIndex: 0,
|
||||
discoverabilityEnabled: false,
|
||||
domainBasedMembershipProtectionEnabled: false,
|
||||
domains: []
|
||||
}
|
||||
return merge(workspace, input)
|
||||
}
|
||||
it('throws WorkspaceNotFoundError if the workspace is not found', async () => {
|
||||
const err = await expectToThrow(async () => {
|
||||
await updateWorkspaceFactory({
|
||||
getWorkspace: async () => null,
|
||||
emitWorkspaceEvent: async () => {
|
||||
expect.fail()
|
||||
},
|
||||
upsertWorkspace: async () => {
|
||||
expect.fail()
|
||||
}
|
||||
})({
|
||||
workspaceId: cryptoRandomString({ length: 10 }),
|
||||
workspaceInput: {}
|
||||
})
|
||||
})
|
||||
expect(err.message).to.be.equal(new WorkspaceNotFoundError().message)
|
||||
})
|
||||
it('throws from image validator if the workspace logo is invalid', async () => {
|
||||
const workspace = createTestWorkspaceWithDomainsData()
|
||||
const err = await expectToThrow(async () => {
|
||||
await updateWorkspaceFactory({
|
||||
getWorkspace: async () => workspace,
|
||||
emitWorkspaceEvent: async () => {
|
||||
expect.fail()
|
||||
},
|
||||
upsertWorkspace: async () => {
|
||||
expect.fail()
|
||||
}
|
||||
})({
|
||||
workspaceId: workspace.id,
|
||||
workspaceInput: {
|
||||
logo: 'a broken logo'
|
||||
}
|
||||
})
|
||||
})
|
||||
expect(err.message).to.be.equal('Provided logo is malformed')
|
||||
})
|
||||
it('validates description length', async () => {
|
||||
const workspace = createTestWorkspaceWithDomainsData()
|
||||
const err = await expectToThrow(async () => {
|
||||
await updateWorkspaceFactory({
|
||||
getWorkspace: async () => workspace,
|
||||
emitWorkspaceEvent: async () => {
|
||||
expect.fail()
|
||||
},
|
||||
upsertWorkspace: async () => {
|
||||
expect.fail()
|
||||
}
|
||||
})({
|
||||
workspaceId: workspace.id,
|
||||
workspaceInput: {
|
||||
logo: 'a broken logo'
|
||||
}
|
||||
})
|
||||
})
|
||||
expect(err.message).to.be.equal('Provided logo is malformed')
|
||||
})
|
||||
it('does not allow turning on discoverability if the workspace has no verified domains', async () => {
|
||||
const workspace = createTestWorkspaceWithDomainsData()
|
||||
const err = await expectToThrow(async () => {
|
||||
await updateWorkspaceFactory({
|
||||
getWorkspace: async () => workspace,
|
||||
emitWorkspaceEvent: async () => {
|
||||
expect.fail()
|
||||
},
|
||||
upsertWorkspace: async () => {
|
||||
expect.fail()
|
||||
}
|
||||
})({
|
||||
workspaceId: workspace.id,
|
||||
workspaceInput: {
|
||||
discoverabilityEnabled: true
|
||||
}
|
||||
})
|
||||
})
|
||||
expect(err.message).to.be.equal(new WorkspaceNoVerifiedDomainsError().message)
|
||||
})
|
||||
|
||||
it('does not allow setting the workspace name to an empty string', async () => {
|
||||
const workspace = createTestWorkspaceWithDomainsData()
|
||||
|
||||
let newWorkspaceName
|
||||
await updateWorkspaceFactory({
|
||||
getWorkspace: async () => workspace,
|
||||
emitWorkspaceEvent: async () => {
|
||||
return []
|
||||
},
|
||||
upsertWorkspace: async ({ workspace }) => {
|
||||
newWorkspaceName = workspace.name
|
||||
}
|
||||
})({
|
||||
workspaceId: workspace.id,
|
||||
workspaceInput: { name: '' }
|
||||
})
|
||||
expect(newWorkspaceName).to.be.equal(workspace.name)
|
||||
})
|
||||
it('updates the workspace and emits the correct event payload', async () => {
|
||||
const workspaceId = cryptoRandomString({ length: 10 })
|
||||
const workspace = createTestWorkspaceWithDomainsData({
|
||||
id: workspaceId,
|
||||
domains: [
|
||||
{
|
||||
createdAt: new Date(),
|
||||
createdByUserId: cryptoRandomString({ length: 10 }),
|
||||
domain: 'example.com',
|
||||
updatedAt: new Date(),
|
||||
id: cryptoRandomString({ length: 10 }),
|
||||
verified: true,
|
||||
workspaceId
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
let updatedWorkspace
|
||||
|
||||
const workspaceInput = {
|
||||
name: cryptoRandomString({ length: 10 }),
|
||||
discoverabilityEnabled: true
|
||||
}
|
||||
|
||||
await updateWorkspaceFactory({
|
||||
getWorkspace: async () => workspace,
|
||||
emitWorkspaceEvent: async () => {
|
||||
return []
|
||||
},
|
||||
upsertWorkspace: async ({ workspace }) => {
|
||||
updatedWorkspace = workspace
|
||||
}
|
||||
})({
|
||||
workspaceId,
|
||||
workspaceInput
|
||||
})
|
||||
expect(updatedWorkspace!.name).to.be.equal(workspaceInput.name)
|
||||
expect(updatedWorkspace!.discoverabilityEnabled).to.be.equal(
|
||||
workspaceInput.discoverabilityEnabled
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
type WorkspaceRoleTestContext = {
|
||||
|
||||
Reference in New Issue
Block a user