chore(workspaces): check user email verified and add user to workspace

This commit is contained in:
Alessandro Magionami
2025-01-22 09:57:38 +01:00
parent adaad0d027
commit abf0ccf835
3 changed files with 128 additions and 46 deletions
@@ -799,33 +799,34 @@ export = FF_WORKSPACES_MODULE_ENABLED
})({ userId: ctx.userId!, workspaceId: args.input.workspaceId })
},
requestToJoin: async (_parent, args, ctx) => {
const transaction = await db.transaction()
const createWorkspaceJoinRequest = createWorkspaceJoinRequestFactory({
db: transaction
const requestToJoin = commandFactory({
db,
operationFactory: ({ db }) => {
const createWorkspaceJoinRequest = createWorkspaceJoinRequestFactory({
db
})
const sendWorkspaceJoinRequestReceivedEmail =
sendWorkspaceJoinRequestReceivedEmailFactory({
renderEmail,
sendEmail,
getServerInfo,
getWorkspaceCollaborators: getWorkspaceCollaboratorsFactory({
db
}),
getUserEmails: findEmailsByUserIdFactory({ db })
})
return requestToJoinWorkspaceFactory({
createWorkspaceJoinRequest,
sendWorkspaceJoinRequestReceivedEmail,
getUserById: getUserFactory({ db }),
getWorkspace: getWorkspaceFactory({ db })
})
}
})
return await requestToJoin({
userId: ctx.userId!,
workspaceId: args.input.workspaceId
})
const sendWorkspaceJoinRequestReceivedEmail =
sendWorkspaceJoinRequestReceivedEmailFactory({
renderEmail,
sendEmail,
getServerInfo,
getWorkspaceCollaborators: getWorkspaceCollaboratorsFactory({
db: transaction
}),
getUserEmails: findEmailsByUserIdFactory({ db: transaction })
})
return await withTransaction(
requestToJoinWorkspaceFactory({
createWorkspaceJoinRequest,
sendWorkspaceJoinRequestReceivedEmail,
getUserById: getUserFactory({ db: transaction }),
getWorkspace: getWorkspaceFactory({ db: transaction })
})({
userId: ctx.userId!,
workspaceId: args.input.workspaceId
}),
transaction
)
}
},
WorkspaceInviteMutations: {
@@ -1,4 +1,9 @@
import { WorkspaceNotFoundError } from '@/modules/workspaces/errors/workspace'
import {
WorkspaceJoinNotAllowedError,
WorkspaceNotDiscoverableError,
WorkspaceNotFoundError,
WorkspaceNotJoinableError
} from '@/modules/workspaces/errors/workspace'
import { GetUser } from '@/modules/core/domain/users/operations'
import { NotFoundError } from '@/modules/shared/errors'
import {
@@ -6,6 +11,7 @@ import {
DenyWorkspaceJoinRequest,
GetWorkspace,
GetWorkspaceJoinRequest,
GetWorkspaceWithDomains,
SendWorkspaceJoinRequestApprovedEmail,
SendWorkspaceJoinRequestDeniedEmail,
SendWorkspaceJoinRequestReceivedEmail,
@@ -13,6 +19,7 @@ import {
UpsertWorkspaceRole
} from '@/modules/workspaces/domain/operations'
import { Roles } from '@speckle/shared'
import { FindEmailsByUserId } from '@/modules/core/domain/userEmails/operations'
export const dismissWorkspaceJoinRequestFactory =
({
@@ -40,12 +47,14 @@ export const requestToJoinWorkspaceFactory =
createWorkspaceJoinRequest,
sendWorkspaceJoinRequestReceivedEmail,
getUserById,
getWorkspace
getWorkspaceWithDomains,
getUserEmails
}: {
createWorkspaceJoinRequest: CreateWorkspaceJoinRequest
sendWorkspaceJoinRequestReceivedEmail: SendWorkspaceJoinRequestReceivedEmail
getUserById: GetUser
getWorkspace: GetWorkspace
getWorkspaceWithDomains: GetWorkspaceWithDomains
getUserEmails: FindEmailsByUserId
}) =>
async ({ userId, workspaceId }: { userId: string; workspaceId: string }) => {
const requester = await getUserById(userId)
@@ -53,10 +62,23 @@ export const requestToJoinWorkspaceFactory =
throw new NotFoundError('User not found')
}
const workspace = await getWorkspace({ workspaceId })
const workspace = await getWorkspaceWithDomains({ id: workspaceId })
if (!workspace) {
throw new WorkspaceNotFoundError('Workspace not found')
}
if (!workspace?.discoverabilityEnabled) throw new WorkspaceNotDiscoverableError()
const workspaceDomains = workspace.domains.filter((domain) => domain.verified)
if (!workspaceDomains.length) throw new WorkspaceNotJoinableError()
const userEmails = await getUserEmails({ userId })
const matchingEmail = userEmails.find((userEmail) => {
if (!userEmail.verified) return false
return workspaceDomains
.map((domain) => domain.domain)
.includes(userEmail.email.split('@')[1])
})
if (!matchingEmail) throw new WorkspaceJoinNotAllowedError()
await createWorkspaceJoinRequest({
workspaceJoinRequest: {
@@ -4,7 +4,10 @@ import {
createRandomString
} from '@/modules/core/helpers/testHelpers'
import { getFeatureFlags } from '@/modules/shared/helpers/envHelper'
import { WorkspaceNotFoundError } from '@/modules/workspaces/errors/workspace'
import {
WorkspaceNotDiscoverableError,
WorkspaceNotFoundError
} from '@/modules/workspaces/errors/workspace'
import { getWorkspaceFactory } from '@/modules/workspaces/repositories/workspaces'
import { UserWithOptionalRole } from '@/modules/core/repositories/users'
import {
@@ -25,7 +28,11 @@ import {
BasicTestWorkspace,
createTestWorkspace
} from '@/modules/workspaces/tests/helpers/creation'
import { Workspace, WorkspaceJoinRequest } from '@/modules/workspacesCore/domain/types'
import {
Workspace,
WorkspaceJoinRequest,
WorkspaceWithDomains
} from '@/modules/workspacesCore/domain/types'
import { WorkspaceJoinRequests } from '@/modules/workspacesCore/helpers/db'
import { expectToThrow } from '@/test/assertionHelper'
import { BasicTestUser, createTestUser } from '@/test/authHelper'
@@ -36,6 +43,7 @@ import {
createWorkspaceJoinRequestFactory,
updateWorkspaceJoinRequestStatusFactory
} from '@/modules/workspaces/repositories/workspaceJoinRequests'
import { UserEmail } from '@/modules/core/domain/userEmails/types'
const { FF_WORKSPACES_MODULE_ENABLED } = getFeatureFlags()
@@ -100,7 +108,8 @@ const { FF_WORKSPACES_MODULE_ENABLED } = getFeatureFlags()
Promise.resolve()) as unknown as CreateWorkspaceJoinRequest,
sendWorkspaceJoinRequestReceivedEmail: async () => Promise.resolve(),
getUserById: async () => null,
getWorkspace: async () => null
getWorkspaceWithDomains: async () => null,
getUserEmails: async () => []
})({ workspaceId: createRandomString(), userId: createRandomString() })
)
@@ -114,21 +123,14 @@ const { FF_WORKSPACES_MODULE_ENABLED } = getFeatureFlags()
Promise.resolve()) as unknown as CreateWorkspaceJoinRequest,
sendWorkspaceJoinRequestReceivedEmail: async () => Promise.resolve(),
getUserById: async () => user as unknown as UserWithOptionalRole,
getWorkspace: async () => null
getWorkspaceWithDomains: async () => null,
getUserEmails: async () => []
})({ workspaceId: createRandomString(), userId: createRandomString() })
)
expect(err.message).to.equal(WorkspaceNotFoundError.defaultMessage)
})
it('creates a join request and sends an email to all admins', async () => {
const createWorkspaceJoinRequest = createWorkspaceJoinRequestFactory({ db })
const sendWorkspaceJoinRequestReceivedEmailCalls: Parameters<SendWorkspaceJoinRequestReceivedEmail>[number][] =
[]
const sendWorkspaceJoinRequestReceivedEmail = async (
args: Parameters<SendWorkspaceJoinRequestReceivedEmail>[number]
) => sendWorkspaceJoinRequestReceivedEmailCalls.push(args)
it('throws a WorkspaceNotDiscoverable if the workspace has no domain', async () => {
const user: BasicTestUser = {
id: '',
name: 'John Speckle',
@@ -147,6 +149,57 @@ const { FF_WORKSPACES_MODULE_ENABLED } = getFeatureFlags()
description: cryptoRandomString({ length: 12 })
}
await createTestWorkspace(workspace, user)
const err = await expectToThrow(() =>
requestToJoinWorkspaceFactory({
createWorkspaceJoinRequest: (async () =>
Promise.resolve()) as unknown as CreateWorkspaceJoinRequest,
sendWorkspaceJoinRequestReceivedEmail: async () => Promise.resolve(),
getUserById: async () => user as unknown as UserWithOptionalRole,
getWorkspaceWithDomains: async () =>
workspace as unknown as WorkspaceWithDomains,
getUserEmails: async () => []
})({ workspaceId: createRandomString(), userId: createRandomString() })
)
expect(err.message).to.equal(WorkspaceNotDiscoverableError.defaultMessage)
})
it('creates a join request and sends an email to all admins', async () => {
const createWorkspaceJoinRequest = createWorkspaceJoinRequestFactory({ db })
const sendWorkspaceJoinRequestReceivedEmailCalls: Parameters<SendWorkspaceJoinRequestReceivedEmail>[number][] =
[]
const sendWorkspaceJoinRequestReceivedEmail = async (
args: Parameters<SendWorkspaceJoinRequestReceivedEmail>[number]
) => sendWorkspaceJoinRequestReceivedEmailCalls.push(args)
const user: BasicTestUser = {
id: '',
name: 'John Speckle',
email: `${createRandomString()}@example.org`,
role: Roles.Server.Admin,
verified: true
}
await createTestUser(user)
const workspace: BasicTestWorkspace = {
id: '',
slug: '',
ownerId: '',
name: cryptoRandomString({ length: 6 }),
description: cryptoRandomString({ length: 12 }),
discoverabilityEnabled: true
}
await createTestWorkspace(workspace, user, { domain: 'example.org' })
const domain = {
id: createRandomString(),
workspaceId: workspace.id,
domain: 'example.org',
verified: true,
createdAt: new Date(),
createdByUserId: user.id,
updatedAt: new Date()
}
expect(
await requestToJoinWorkspaceFactory({
@@ -154,7 +207,13 @@ const { FF_WORKSPACES_MODULE_ENABLED } = getFeatureFlags()
sendWorkspaceJoinRequestReceivedEmail:
sendWorkspaceJoinRequestReceivedEmail as unknown as SendWorkspaceJoinRequestReceivedEmail,
getUserById: async () => user as unknown as UserWithOptionalRole,
getWorkspace: async () => workspace as unknown as Workspace
getWorkspaceWithDomains: async () =>
({
...workspace,
domains: [domain]
} as unknown as WorkspaceWithDomains),
getUserEmails: async () =>
[{ email: user.email, verified: true }] as unknown as UserEmail[]
})({ workspaceId: workspace.id, userId: user.id })
).to.equal(true)
@@ -169,8 +228,8 @@ const { FF_WORKSPACES_MODULE_ENABLED } = getFeatureFlags()
).to.equal('pending')
expect(sendWorkspaceJoinRequestReceivedEmailCalls).to.have.length(1)
expect(sendWorkspaceJoinRequestReceivedEmailCalls[0].workspace).to.equal(
workspace
expect(sendWorkspaceJoinRequestReceivedEmailCalls[0].workspace.id).to.equal(
workspace.id
)
expect(sendWorkspaceJoinRequestReceivedEmailCalls[0].requester).to.equal(user)
})