fix(server): prevent creating project contributor invite to viewer se… (#4462)
* fix(server): prevent creating project contributor invite to viewer seat member * undo regionConfig change * moar cleanup
This commit is contained in:
committed by
GitHub
parent
385157ac81
commit
93bc55630b
@@ -221,6 +221,12 @@ export type GetWorkspaceSeatTypeToProjectRoleMapping = (args: {
|
||||
}
|
||||
}>
|
||||
|
||||
export type ValidateWorkspaceMemberProjectRole = (params: {
|
||||
workspaceId: string
|
||||
userId: string
|
||||
projectRole: StreamRoles
|
||||
}) => Promise<void>
|
||||
|
||||
/** Workspace Projects */
|
||||
|
||||
type QueryAllWorkspaceProjectsArgs = {
|
||||
|
||||
@@ -14,7 +14,8 @@ import {
|
||||
GetWorkspaceRoleForUser,
|
||||
GetWorkspaceRoleToDefaultProjectRoleMapping,
|
||||
GetWorkspaceSeatTypeToProjectRoleMapping,
|
||||
QueryAllWorkspaceProjects
|
||||
QueryAllWorkspaceProjects,
|
||||
ValidateWorkspaceMemberProjectRole
|
||||
} from '@/modules/workspaces/domain/operations'
|
||||
import {
|
||||
ServerInvitesEvents,
|
||||
@@ -28,12 +29,7 @@ import { logger, moduleLogger } from '@/observability/logging'
|
||||
import { updateWorkspaceRoleFactory } from '@/modules/workspaces/services/management'
|
||||
import { EventPayload, getEventBus } from '@/modules/shared/services/eventBus'
|
||||
import { WorkspaceInviteResourceType } from '@/modules/workspacesCore/domain/constants'
|
||||
import {
|
||||
Roles,
|
||||
StreamRoles,
|
||||
throwUncoveredError,
|
||||
WorkspaceRoles
|
||||
} from '@speckle/shared'
|
||||
import { Roles, throwUncoveredError, WorkspaceRoles } from '@speckle/shared'
|
||||
import {
|
||||
DeleteProjectRole,
|
||||
UpsertProjectRole
|
||||
@@ -51,7 +47,8 @@ import {
|
||||
import {
|
||||
queryAllWorkspaceProjectsFactory,
|
||||
getWorkspaceRoleToDefaultProjectRoleMappingFactory,
|
||||
getWorkspaceSeatTypeToProjectRoleMappingFactory
|
||||
getWorkspaceSeatTypeToProjectRoleMappingFactory,
|
||||
validateWorkspaceMemberProjectRoleFactory
|
||||
} from '@/modules/workspaces/services/projects'
|
||||
import { withTransaction } from '@/modules/shared/helpers/dbHelper'
|
||||
import {
|
||||
@@ -73,10 +70,7 @@ import {
|
||||
getUserSsoSessionFactory,
|
||||
getWorkspaceSsoProviderRecordFactory
|
||||
} from '@/modules/workspaces/repositories/sso'
|
||||
import {
|
||||
WorkspaceInvalidRoleError,
|
||||
WorkspacesNotAuthorizedError
|
||||
} from '@/modules/workspaces/errors/workspace'
|
||||
import { WorkspacesNotAuthorizedError } from '@/modules/workspaces/errors/workspace'
|
||||
import { publish, WorkspaceSubscriptions } from '@/modules/shared/utils/subscriptions'
|
||||
import { isWorkspaceResourceTarget } from '@/modules/workspaces/services/invites'
|
||||
import {
|
||||
@@ -87,11 +81,9 @@ import { getBaseTrackingProperties, getClient } from '@/modules/shared/utils/mix
|
||||
import {
|
||||
calculateSubscriptionSeats,
|
||||
GetWorkspacePlan,
|
||||
GetWorkspaceRoleAndSeat,
|
||||
GetWorkspaceRolesAndSeats,
|
||||
GetWorkspaceSubscription,
|
||||
GetWorkspaceWithPlan,
|
||||
WorkspaceSeatType
|
||||
GetWorkspaceWithPlan
|
||||
} from '@/modules/gatekeeper/domain/billing'
|
||||
import { getWorkspacePlanProductId } from '@/modules/gatekeeper/stripe'
|
||||
import { Workspace } from '@/modules/workspacesCore/domain/types'
|
||||
@@ -601,63 +593,17 @@ const emitWorkspaceGraphqlSubscriptionsFactory =
|
||||
const blockInvalidWorkspaceProjectRoleUpdatesFactory =
|
||||
(deps: {
|
||||
getStream: GetStream
|
||||
getWorkspaceRoleAndSeat: GetWorkspaceRoleAndSeat
|
||||
getWorkspaceWithPlan: GetWorkspaceWithPlan
|
||||
getWorkspaceRoleToDefaultProjectRoleMapping: GetWorkspaceRoleToDefaultProjectRoleMapping
|
||||
getWorkspaceSeatTypeToProjectRoleMapping: GetWorkspaceSeatTypeToProjectRoleMapping
|
||||
validateWorkspaceMemberProjectRole: ValidateWorkspaceMemberProjectRole
|
||||
}) =>
|
||||
async ({ payload }: EventPayload<typeof ProjectEvents.PermissionsBeingAdded>) => {
|
||||
const project = await deps.getStream({ streamId: payload.projectId })
|
||||
if (!project?.workspaceId) return // No extra validation necessary
|
||||
|
||||
const roleSeatParams = {
|
||||
workspaceId: project.workspaceId,
|
||||
userId: payload.targetUserId
|
||||
}
|
||||
|
||||
const [currentWorkspaceRoleAndSeat, workspace] = await Promise.all([
|
||||
deps.getWorkspaceRoleAndSeat(roleSeatParams),
|
||||
deps.getWorkspaceWithPlan({ workspaceId: project.workspaceId })
|
||||
])
|
||||
|
||||
if (!workspace || !currentWorkspaceRoleAndSeat?.role) return
|
||||
const {
|
||||
role: { role: workspaceRole },
|
||||
seat
|
||||
} = currentWorkspaceRoleAndSeat
|
||||
const seatType = seat?.type || WorkspaceSeatType.Viewer
|
||||
|
||||
let allowedRoles: StreamRoles[]
|
||||
const isNewPlan = workspace.plan && isNewPlanType(workspace.plan.name)
|
||||
if (isNewPlan) {
|
||||
const workspaceAllowedRoles = (
|
||||
await deps.getWorkspaceRoleToDefaultProjectRoleMapping({
|
||||
workspaceId: project.workspaceId
|
||||
})
|
||||
).allowed[workspaceRole]
|
||||
const seatAllowedRoles = (
|
||||
await deps.getWorkspaceSeatTypeToProjectRoleMapping({
|
||||
workspaceId: project.workspaceId
|
||||
})
|
||||
).allowed[seatType]
|
||||
allowedRoles = Array.from(
|
||||
new Set(workspaceAllowedRoles).intersection(new Set(seatAllowedRoles))
|
||||
)
|
||||
} else {
|
||||
const roleMapping = await deps.getWorkspaceRoleToDefaultProjectRoleMapping({
|
||||
workspaceId: project.workspaceId
|
||||
})
|
||||
allowedRoles = roleMapping.allowed[workspaceRole]
|
||||
}
|
||||
|
||||
if (!allowedRoles.includes(payload.role)) {
|
||||
// User's workspace role does not allow the requested project role
|
||||
throw new WorkspaceInvalidRoleError(
|
||||
isNewPlan
|
||||
? `User's workspace seat type '${seatType}' does not allow project role '${payload.role}'.`
|
||||
: `User's workspace role '${workspaceRole}' does not allow project role '${payload.role}'.`
|
||||
)
|
||||
}
|
||||
await deps.validateWorkspaceMemberProjectRole({
|
||||
userId: payload.targetUserId,
|
||||
projectRole: payload.role,
|
||||
workspaceId: project.workspaceId
|
||||
})
|
||||
}
|
||||
|
||||
export const initializeEventListenersFactory =
|
||||
@@ -677,16 +623,18 @@ export const initializeEventListenersFactory =
|
||||
const blockInvalidWorkspaceProjectRoleUpdates =
|
||||
blockInvalidWorkspaceProjectRoleUpdatesFactory({
|
||||
getStream,
|
||||
getWorkspaceRoleAndSeat: getWorkspaceRoleAndSeatFactory({ db }),
|
||||
getWorkspaceRoleToDefaultProjectRoleMapping:
|
||||
getWorkspaceRoleToDefaultProjectRoleMappingFactory({
|
||||
getWorkspaceWithPlan
|
||||
}),
|
||||
getWorkspaceSeatTypeToProjectRoleMapping:
|
||||
getWorkspaceSeatTypeToProjectRoleMappingFactory({
|
||||
getWorkspaceWithPlan
|
||||
}),
|
||||
getWorkspaceWithPlan
|
||||
validateWorkspaceMemberProjectRole: validateWorkspaceMemberProjectRoleFactory({
|
||||
getWorkspaceRoleAndSeat: getWorkspaceRoleAndSeatFactory({ db }),
|
||||
getWorkspaceRoleToDefaultProjectRoleMapping:
|
||||
getWorkspaceRoleToDefaultProjectRoleMappingFactory({
|
||||
getWorkspaceWithPlan
|
||||
}),
|
||||
getWorkspaceSeatTypeToProjectRoleMapping:
|
||||
getWorkspaceSeatTypeToProjectRoleMappingFactory({
|
||||
getWorkspaceWithPlan
|
||||
}),
|
||||
getWorkspaceWithPlan
|
||||
})
|
||||
})
|
||||
const createWorkspaceSeat = createWorkspaceSeatFactory({ db })
|
||||
const ensureValidWorkspaceRoleSeat = ensureValidWorkspaceRoleSeatFactory({
|
||||
|
||||
@@ -99,8 +99,11 @@ import {
|
||||
} from '@/modules/workspaces/services/management'
|
||||
import {
|
||||
createWorkspaceProjectFactory,
|
||||
getWorkspaceRoleToDefaultProjectRoleMappingFactory,
|
||||
getWorkspaceSeatTypeToProjectRoleMappingFactory,
|
||||
moveProjectToWorkspaceFactory,
|
||||
queryAllWorkspaceProjectsFactory
|
||||
queryAllWorkspaceProjectsFactory,
|
||||
validateWorkspaceMemberProjectRoleFactory
|
||||
} from '@/modules/workspaces/services/projects'
|
||||
import {
|
||||
getDiscoverableWorkspacesForUserFactory,
|
||||
@@ -205,6 +208,7 @@ import {
|
||||
import { ensureValidWorkspaceRoleSeatFactory } from '@/modules/workspaces/services/workspaceSeat'
|
||||
import {
|
||||
createWorkspaceSeatFactory,
|
||||
getWorkspaceRoleAndSeatFactory,
|
||||
getWorkspaceRolesAndSeatsFactory,
|
||||
getWorkspaceUserSeatFactory
|
||||
} from '@/modules/gatekeeper/repositories/workspaceSeat'
|
||||
@@ -234,7 +238,21 @@ const buildCollectAndValidateResourceTargets = () =>
|
||||
getStream,
|
||||
getWorkspace: getWorkspaceFactory({ db }),
|
||||
getWorkspaceDomains: getWorkspaceDomainsFactory({ db }),
|
||||
findVerifiedEmailsByUserId: findVerifiedEmailsByUserIdFactory({ db })
|
||||
findVerifiedEmailsByUserId: findVerifiedEmailsByUserIdFactory({ db }),
|
||||
getWorkspaceRoleAndSeat: getWorkspaceRoleAndSeatFactory({ db }),
|
||||
validateWorkspaceMemberProjectRoleFactory:
|
||||
validateWorkspaceMemberProjectRoleFactory({
|
||||
getWorkspaceRoleAndSeat: getWorkspaceRoleAndSeatFactory({ db }),
|
||||
getWorkspaceWithPlan: getWorkspaceWithPlanFactory({ db }),
|
||||
getWorkspaceRoleToDefaultProjectRoleMapping:
|
||||
getWorkspaceRoleToDefaultProjectRoleMappingFactory({
|
||||
getWorkspaceWithPlan: getWorkspaceWithPlanFactory({ db })
|
||||
}),
|
||||
getWorkspaceSeatTypeToProjectRoleMapping:
|
||||
getWorkspaceSeatTypeToProjectRoleMappingFactory({
|
||||
getWorkspaceWithPlan: getWorkspaceWithPlanFactory({ db })
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
const buildCreateAndSendServerOrProjectInvite = () =>
|
||||
|
||||
@@ -57,7 +57,8 @@ import { WorkspaceInviteResourceType } from '@/modules/workspacesCore/domain/con
|
||||
import {
|
||||
GetWorkspace,
|
||||
GetWorkspaceBySlug,
|
||||
GetWorkspaceDomains
|
||||
GetWorkspaceDomains,
|
||||
ValidateWorkspaceMemberProjectRole
|
||||
} from '@/modules/workspaces/domain/operations'
|
||||
import { WorkspaceInviteResourceTarget } from '@/modules/workspaces/domain/types'
|
||||
import { mapGqlWorkspaceRoleToMainRole } from '@/modules/workspaces/helpers/roles'
|
||||
@@ -72,6 +73,7 @@ import {
|
||||
} from '@/modules/workspaces/domain/logic'
|
||||
import { GetStream } from '@/modules/core/domain/streams/operations'
|
||||
import { GetUser } from '@/modules/core/domain/users/operations'
|
||||
import { GetWorkspaceRoleAndSeat } from '@/modules/workspacesCore/domain/operations'
|
||||
|
||||
export const isWorkspaceResourceTarget = (
|
||||
target: InviteResourceTarget
|
||||
@@ -122,6 +124,8 @@ export const createWorkspaceInviteFactory =
|
||||
type CollectAndValidateWorkspaceTargetsFactoryDeps =
|
||||
CollectAndValidateCoreTargetsFactoryDeps & {
|
||||
getWorkspace: GetWorkspace
|
||||
getWorkspaceRoleAndSeat: GetWorkspaceRoleAndSeat
|
||||
validateWorkspaceMemberProjectRoleFactory: ValidateWorkspaceMemberProjectRole
|
||||
getWorkspaceDomains: GetWorkspaceDomains
|
||||
findVerifiedEmailsByUserId: FindVerifiedEmailsByUserId
|
||||
getStream: GetStream
|
||||
@@ -191,31 +195,42 @@ export const collectAndValidateWorkspaceTargetsFactory =
|
||||
return [...baseTargets]
|
||||
}
|
||||
|
||||
const workspace = await deps.getWorkspace({
|
||||
workspaceId,
|
||||
userId: targetUser?.id
|
||||
})
|
||||
const [workspace, workspaceRoleAndSeat] = await Promise.all([
|
||||
deps.getWorkspace({
|
||||
workspaceId
|
||||
}),
|
||||
...(targetUser?.id
|
||||
? [
|
||||
deps.getWorkspaceRoleAndSeat({
|
||||
workspaceId,
|
||||
userId: targetUser.id
|
||||
})
|
||||
]
|
||||
: [])
|
||||
])
|
||||
if (!workspace) {
|
||||
throw new InviteCreateValidationError(
|
||||
'Attempting to invite into a non-existant workspace'
|
||||
)
|
||||
}
|
||||
|
||||
// If inviting to workspace project, disallow workspace guests to become project owners
|
||||
const workspaceRole = workspaceRoleAndSeat?.role.role
|
||||
|
||||
// If inviting to workspace project, validate target role
|
||||
const projectTarget = baseTargets.find(isProjectResourceTarget)
|
||||
if (
|
||||
workspace?.role === Roles.Workspace.Guest &&
|
||||
projectTarget?.role === Roles.Stream.Owner
|
||||
) {
|
||||
throw new InviteCreateValidationError(
|
||||
'Workspace guests cannot be owners of workspace projects'
|
||||
)
|
||||
const projectRole = projectTarget?.role
|
||||
if (projectRole && targetUser) {
|
||||
await deps.validateWorkspaceMemberProjectRoleFactory({
|
||||
workspaceId,
|
||||
userId: targetUser.id,
|
||||
projectRole
|
||||
})
|
||||
}
|
||||
|
||||
// Do further validation only if we're actually planning to invite to a workspace
|
||||
// (maybe the invitation is implicitly there, but user already is a member of the workspace)
|
||||
const isInvitingToWorkspace =
|
||||
primaryWorkspaceResourceTarget || (workspace && !workspace.role)
|
||||
primaryWorkspaceResourceTarget || (workspace && !workspaceRole)
|
||||
if (!isInvitingToWorkspace) {
|
||||
return [...baseTargets]
|
||||
}
|
||||
@@ -236,7 +251,7 @@ export const collectAndValidateWorkspaceTargetsFactory =
|
||||
}
|
||||
|
||||
// Only check this on creation, on finalization its fine if the user's already a member
|
||||
if (workspace.role && !finalizingInvite) {
|
||||
if (workspaceRole && !finalizingInvite) {
|
||||
throw new InviteCreateValidationError(
|
||||
'The target user is already a member of the specified workspace'
|
||||
)
|
||||
|
||||
@@ -6,16 +6,18 @@ import {
|
||||
GetWorkspaceSeatTypeToProjectRoleMapping,
|
||||
IntersectProjectCollaboratorsAndWorkspaceCollaborators,
|
||||
QueryAllWorkspaceProjects,
|
||||
UpdateWorkspaceRole
|
||||
UpdateWorkspaceRole,
|
||||
ValidateWorkspaceMemberProjectRole
|
||||
} from '@/modules/workspaces/domain/operations'
|
||||
import {
|
||||
WorkspaceInvalidProjectError,
|
||||
WorkspaceInvalidRoleError,
|
||||
WorkspaceNotFoundError,
|
||||
WorkspaceQueryError
|
||||
} from '@/modules/workspaces/errors/workspace'
|
||||
import { GetProject, UpdateProject } from '@/modules/core/domain/projects/operations'
|
||||
import { chunk } from 'lodash'
|
||||
import { Roles } from '@speckle/shared'
|
||||
import { Roles, StreamRoles } from '@speckle/shared'
|
||||
import {
|
||||
GetStreamCollaborators,
|
||||
LegacyGetStreams,
|
||||
@@ -42,6 +44,7 @@ import {
|
||||
upsertWorkspaceFactory
|
||||
} from '@/modules/workspaces/repositories/workspaces'
|
||||
import {
|
||||
GetWorkspaceRoleAndSeat,
|
||||
GetWorkspaceRolesAndSeats,
|
||||
GetWorkspaceWithPlan,
|
||||
WorkspaceSeatType
|
||||
@@ -296,6 +299,69 @@ export const getWorkspaceSeatTypeToProjectRoleMappingFactory =
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that the specified workspace member can have the specified project role
|
||||
*/
|
||||
export const validateWorkspaceMemberProjectRoleFactory =
|
||||
(deps: {
|
||||
getWorkspaceRoleAndSeat: GetWorkspaceRoleAndSeat
|
||||
getWorkspaceWithPlan: GetWorkspaceWithPlan
|
||||
getWorkspaceRoleToDefaultProjectRoleMapping: GetWorkspaceRoleToDefaultProjectRoleMapping
|
||||
getWorkspaceSeatTypeToProjectRoleMapping: GetWorkspaceSeatTypeToProjectRoleMapping
|
||||
}): ValidateWorkspaceMemberProjectRole =>
|
||||
async (params) => {
|
||||
const { workspaceId, userId, projectRole } = params
|
||||
|
||||
const roleSeatParams = {
|
||||
workspaceId,
|
||||
userId
|
||||
}
|
||||
|
||||
const [currentWorkspaceRoleAndSeat, workspace] = await Promise.all([
|
||||
deps.getWorkspaceRoleAndSeat(roleSeatParams),
|
||||
deps.getWorkspaceWithPlan({ workspaceId })
|
||||
])
|
||||
|
||||
if (!workspace || !currentWorkspaceRoleAndSeat?.role) return
|
||||
const {
|
||||
role: { role: workspaceRole },
|
||||
seat
|
||||
} = currentWorkspaceRoleAndSeat
|
||||
const seatType = seat?.type || WorkspaceSeatType.Viewer
|
||||
|
||||
let allowedRoles: StreamRoles[]
|
||||
const isNewPlan = workspace.plan && isNewPlanType(workspace.plan.name)
|
||||
if (isNewPlan) {
|
||||
const workspaceAllowedRoles = (
|
||||
await deps.getWorkspaceRoleToDefaultProjectRoleMapping({
|
||||
workspaceId
|
||||
})
|
||||
).allowed[workspaceRole]
|
||||
const seatAllowedRoles = (
|
||||
await deps.getWorkspaceSeatTypeToProjectRoleMapping({
|
||||
workspaceId
|
||||
})
|
||||
).allowed[seatType]
|
||||
allowedRoles = Array.from(
|
||||
new Set(workspaceAllowedRoles).intersection(new Set(seatAllowedRoles))
|
||||
)
|
||||
} else {
|
||||
const roleMapping = await deps.getWorkspaceRoleToDefaultProjectRoleMapping({
|
||||
workspaceId
|
||||
})
|
||||
allowedRoles = roleMapping.allowed[workspaceRole]
|
||||
}
|
||||
|
||||
if (!allowedRoles.includes(projectRole)) {
|
||||
// User's workspace role does not allow the requested project role
|
||||
throw new WorkspaceInvalidRoleError(
|
||||
isNewPlan
|
||||
? `User's workspace seat type '${seatType}' does not allow project role '${projectRole}'.`
|
||||
: `User's workspace role '${workspaceRole}' does not allow project role '${projectRole}'.`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export const createWorkspaceProjectFactory =
|
||||
(deps: { getDefaultRegion: GetDefaultRegion }) =>
|
||||
async (params: { input: WorkspaceProjectCreateInput; ownerId: string }) => {
|
||||
|
||||
@@ -59,6 +59,7 @@ import { getFeatureFlags, getFrontendOrigin } from '@/modules/shared/helpers/env
|
||||
import { getDefaultSsoSessionExpirationDate } from '@/modules/workspaces/domain/sso/logic'
|
||||
import {
|
||||
getWorkspacePlanFactory,
|
||||
getWorkspaceWithPlanFactory,
|
||||
upsertPaidWorkspacePlanFactory,
|
||||
upsertWorkspaceSubscriptionFactory
|
||||
} from '@/modules/gatekeeper/repositories/billing'
|
||||
@@ -82,9 +83,15 @@ import {
|
||||
} from '@/modules/workspaces/services/workspaceSeat'
|
||||
import {
|
||||
createWorkspaceSeatFactory,
|
||||
getWorkspaceRoleAndSeatFactory,
|
||||
getWorkspaceUserSeatFactory
|
||||
} from '@/modules/gatekeeper/repositories/workspaceSeat'
|
||||
import dayjs from 'dayjs'
|
||||
import {
|
||||
getWorkspaceRoleToDefaultProjectRoleMappingFactory,
|
||||
getWorkspaceSeatTypeToProjectRoleMappingFactory,
|
||||
validateWorkspaceMemberProjectRoleFactory
|
||||
} from '@/modules/workspaces/services/projects'
|
||||
|
||||
const { FF_WORKSPACES_MODULE_ENABLED } = getFeatureFlags()
|
||||
|
||||
@@ -377,7 +384,21 @@ export const createWorkspaceInviteDirectly = async (
|
||||
getStream,
|
||||
getWorkspace: getWorkspaceFactory({ db }),
|
||||
getWorkspaceDomains: getWorkspaceDomainsFactory({ db }),
|
||||
findVerifiedEmailsByUserId: findVerifiedEmailsByUserIdFactory({ db })
|
||||
findVerifiedEmailsByUserId: findVerifiedEmailsByUserIdFactory({ db }),
|
||||
getWorkspaceRoleAndSeat: getWorkspaceRoleAndSeatFactory({ db }),
|
||||
validateWorkspaceMemberProjectRoleFactory:
|
||||
validateWorkspaceMemberProjectRoleFactory({
|
||||
getWorkspaceRoleAndSeat: getWorkspaceRoleAndSeatFactory({ db }),
|
||||
getWorkspaceWithPlan: getWorkspaceWithPlanFactory({ db }),
|
||||
getWorkspaceRoleToDefaultProjectRoleMapping:
|
||||
getWorkspaceRoleToDefaultProjectRoleMappingFactory({
|
||||
getWorkspaceWithPlan: getWorkspaceWithPlanFactory({ db })
|
||||
}),
|
||||
getWorkspaceSeatTypeToProjectRoleMapping:
|
||||
getWorkspaceSeatTypeToProjectRoleMappingFactory({
|
||||
getWorkspaceWithPlan: getWorkspaceWithPlanFactory({ db })
|
||||
})
|
||||
})
|
||||
}),
|
||||
buildInviteEmailContents: buildWorkspaceInviteEmailContentsFactory({
|
||||
getStream,
|
||||
|
||||
@@ -49,7 +49,10 @@ import {
|
||||
} from '@/modules/core/repositories/userEmails'
|
||||
import { markUserEmailAsVerifiedFactory } from '@/modules/core/services/users/emailVerification'
|
||||
import { createRandomPassword } from '@/modules/core/helpers/testHelpers'
|
||||
import { WorkspaceProtectedError } from '@/modules/workspaces/errors/workspace'
|
||||
import {
|
||||
WorkspaceInvalidRoleError,
|
||||
WorkspaceProtectedError
|
||||
} from '@/modules/workspaces/errors/workspace'
|
||||
import cryptoRandomString from 'crypto-random-string'
|
||||
import { grantStreamPermissionsFactory } from '@/modules/core/repositories/streams'
|
||||
import {
|
||||
@@ -469,6 +472,13 @@ describe('Workspaces Invites GQL', () => {
|
||||
ownerId: ''
|
||||
}
|
||||
|
||||
const myProjectInviteTargetWorkspaceWithNewPlan: BasicTestWorkspace = {
|
||||
name: 'My Project Invite Target Workspace w/ New Plan #1',
|
||||
id: '',
|
||||
slug: cryptoRandomString({ length: 10 }),
|
||||
ownerId: ''
|
||||
}
|
||||
|
||||
const myProjectInviteTargetBasicProject: BasicTestStream = {
|
||||
name: 'My Project Invite Target Basic Project #1',
|
||||
id: '',
|
||||
@@ -483,6 +493,13 @@ describe('Workspaces Invites GQL', () => {
|
||||
isPublic: false
|
||||
}
|
||||
|
||||
const myProjectInviteTargetWorkspaceNewPlanProject: BasicTestStream = {
|
||||
name: 'My Project Invite Target Workspace New Plan Project #1',
|
||||
id: '',
|
||||
ownerId: '',
|
||||
isPublic: false
|
||||
}
|
||||
|
||||
const workspaceMemberWithNoProjectAccess: BasicTestUser = {
|
||||
name: 'Workspace Member With No Project Access #1',
|
||||
email: 'workspaceMemberWithNoProjectAccess1@example.org',
|
||||
@@ -497,7 +514,19 @@ describe('Workspaces Invites GQL', () => {
|
||||
|
||||
before(async () => {
|
||||
await createTestUsers([workspaceMemberWithNoProjectAccess, workspaceGuest])
|
||||
await createTestWorkspaces([[myProjectInviteTargetWorkspace, me]])
|
||||
await createTestWorkspaces([
|
||||
[myProjectInviteTargetWorkspace, me],
|
||||
[
|
||||
myProjectInviteTargetWorkspaceWithNewPlan,
|
||||
me,
|
||||
{
|
||||
addPlan: {
|
||||
name: 'teamUnlimited',
|
||||
status: 'valid'
|
||||
}
|
||||
}
|
||||
]
|
||||
])
|
||||
await assignToWorkspaces([
|
||||
[myProjectInviteTargetWorkspace, myWorkspaceFriend, Roles.Workspace.Member],
|
||||
[
|
||||
@@ -505,13 +534,21 @@ describe('Workspaces Invites GQL', () => {
|
||||
workspaceMemberWithNoProjectAccess,
|
||||
Roles.Workspace.Member
|
||||
],
|
||||
[myProjectInviteTargetWorkspace, workspaceGuest, Roles.Workspace.Guest]
|
||||
[myProjectInviteTargetWorkspace, workspaceGuest, Roles.Workspace.Guest],
|
||||
[
|
||||
myProjectInviteTargetWorkspaceWithNewPlan,
|
||||
workspaceGuest,
|
||||
Roles.Workspace.Guest
|
||||
]
|
||||
])
|
||||
|
||||
myProjectInviteTargetWorkspaceNewPlanProject.workspaceId =
|
||||
myProjectInviteTargetWorkspaceWithNewPlan.id
|
||||
myProjectInviteTargetWorkspaceProject.workspaceId =
|
||||
myProjectInviteTargetWorkspace.id
|
||||
await createTestStreams([
|
||||
[myProjectInviteTargetWorkspaceProject, me],
|
||||
[myProjectInviteTargetWorkspaceNewPlanProject, me],
|
||||
[myProjectInviteTargetBasicProject, me]
|
||||
])
|
||||
|
||||
@@ -626,9 +663,22 @@ describe('Workspaces Invites GQL', () => {
|
||||
]
|
||||
})
|
||||
|
||||
expect(res).to.haveGraphQLErrors(
|
||||
'Workspace guests cannot be owners of workspace projects'
|
||||
)
|
||||
expect(res).to.haveGraphQLErrors({ code: WorkspaceInvalidRoleError.code })
|
||||
expect(res.data?.projectMutations.invites.createForWorkspace.id).to.not.be.ok
|
||||
})
|
||||
|
||||
it("can't invite someone with a viewer seat to be a contributor", async () => {
|
||||
const res = await gqlHelpers.createWorkspaceProjectInvite({
|
||||
projectId: myProjectInviteTargetWorkspaceNewPlanProject.id,
|
||||
inputs: [
|
||||
{
|
||||
userId: workspaceGuest.id,
|
||||
role: Roles.Stream.Contributor
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
expect(res).to.haveGraphQLErrors({ code: WorkspaceInvalidRoleError.code })
|
||||
expect(res.data?.projectMutations.invites.createForWorkspace.id).to.not.be.ok
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user